<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[SoundCloud Backstage Blog]]></title><description><![CDATA[SoundCloud's developer blog.]]></description><link>https://developers.soundcloud.com/blog</link><generator>GatsbyJS</generator><lastBuildDate>Wed, 08 Apr 2026 11:39:49 GMT</lastBuildDate><item><title><![CDATA[Moving to Modern Streaming: Introducing AAC HLS on SoundCloud APIs [Deadline Extended]]]></title><description><![CDATA[At SoundCloud, we’re always looking for ways to improve the listening experience for our creators, partners, and listeners. One of the…]]></description><link>https://developers.soundcloud.com/blog/api-streaming-urls</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/api-streaming-urls</guid><pubDate>Mon, 15 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At SoundCloud, we’re always looking for ways to improve the listening experience for our creators, partners, and listeners. One of the biggest improvements we can make is at the very core: the audio streams themselves.
Over the years, our API has returned a set of streaming URLs based on MP3 and Opus formats. While these formats served us well, it’s time to modernize. To provide higher quality, more consistent playback, and better support across devices, we’re moving to AAC-based HLS streams.&lt;/p&gt;
&lt;h2&gt;🚀 What’s changing?&lt;/h2&gt;
&lt;p&gt;Starting today, the streams endpoint (/tracks/{track_urn}/streams) includes new HLS AAC transcodings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hls_aac_160_url (standard quality, preferred format and bitrate)&lt;/li&gt;
&lt;li&gt;hls_aac_96_url (alternative quality, depending on availability and can work as fallback to the 160k stream)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a transition period, existing fields like http_mp3_128_url, hls_mp3_128_url, and hls_opus_64_url will remain available.&lt;/p&gt;
&lt;h2&gt;⏳ Deadline Extended: Deprecation Timeline&lt;/h2&gt;
&lt;p&gt;We plan to remove Progressive HTTP, HLS MP3 and HLS Opus transcodings from our APIs on December 31, 2025.
The following fields will no longer be supported after that date:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http_mp3_128_url (Progressive HTTP MP3) &lt;/li&gt;
&lt;li&gt;hls_mp3_128_url (HLS MP3)&lt;/li&gt;
&lt;li&gt;hls_opus_64_url (HLS Opus)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;preview_mp3_128_url will remain available for preview use cases.&lt;/p&gt;
&lt;h2&gt;✅ What you need to do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Migrate from MP3 / Opus to AAC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace http_mp3_128_url, hls_mp3_128_url, and hls_opus_64_url with the new AAC HLS streams.&lt;/li&gt;
&lt;li&gt;Use hls_aac_160_url (preferred) or hls_aac_96_url where available.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Migrate from Progressive HTTP to HLS&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Progressive MP3 playback (http_mp3_128_url) will no longer be supported.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test your integration with the new URLs to ensure a smooth migration.&lt;/p&gt;
&lt;h3&gt;🔍 Example Response as of now&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
&amp;quot;http_mp3_128_url&amp;quot;: &amp;quot;https://cf-media.sndcdn.com/trackid.128.mp3?...&amp;quot;,
&amp;quot;hls_mp3_128_url&amp;quot;: &amp;quot;https://cf-hls-media.sndcdn.com/playlist/trackid.128.mp3/playlist.m3u8?...&amp;quot;,
&amp;quot;hls_aac_160_url&amp;quot;: &amp;quot;https://playback.media-streaming.soundcloud.cloud/trackid/aac_160k/uuid/playlist.m3u8?...&amp;quot;,
&amp;quot;hls_opus_64_url&amp;quot;: &amp;quot;https://cf-hls-opus-media.sndcdn.com/playlist/uuid.64.opus/playlist.m3u8?...&amp;quot;,
&amp;quot;preview_mp3_128_url&amp;quot;: &amp;quot;https://cf-preview-media.sndcdn.com/preview/0/30/trackid.128.mp3?...&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;🔍 Example Response after Dec 31, 2025&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
&amp;quot;hls_aac_160_url&amp;quot;: &amp;quot;https://playback.media-streaming.soundcloud.cloud/trackid/aac_160k/uuid/playlist.m3u8?...&amp;quot;,
&amp;quot;hls_aac_96_url&amp;quot;: &amp;quot;https://playback.media-streaming.soundcloud.cloud/trackid/aac_96k/uuid/playlist.m3u8?...&amp;quot;,
&amp;quot;preview_mp3_128_url&amp;quot;: &amp;quot;https://cf-preview-media.sndcdn.com/preview/0/30/trackid.128.mp3?...&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;💬 Feedback&lt;/h2&gt;
&lt;p&gt;We’d love to hear how the transition works for you. If you run into issues or need additional support, please open &lt;a href=&quot;https://github.com/soundcloud/api/issues&quot;&gt;a discussion on GitHub&lt;/a&gt;.
This change sets the foundation for better audio quality and playback reliability across SoundCloud integrations. We encourage you to migrate early and share your feedback.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[📣 Action required: Upcoming Update to SoundCloud API id fields]]></title><description><![CDATA[To all developers using SoundCloud’s public API — we’re introducing an important change that will affect how you reference resources like…]]></description><link>https://developers.soundcloud.com/blog/urn-num-to-string</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/urn-num-to-string</guid><pubDate>Tue, 29 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To all developers using SoundCloud’s public API — we’re introducing an important change that will affect how you reference resources like tracks, playlists, users and comments.&lt;/p&gt;
&lt;h2&gt;🧠 What’s Changing?&lt;/h2&gt;
&lt;p&gt;SoundCloud’s resource identifiers (id) have always been numeric. As the platform grows, some of these numbers will soon exceed the maximum value of a 32-bit integer.
To ensure forward compatibility and prevent broken integrations, we’re deprecating the id field and introducing a new field: urn — a string-based unique identifier.&lt;/p&gt;
&lt;p&gt;🚨 As a result, the id field in API responses is now deprecated. We strongly recommend switching to the new urn field — a string-based unique identifier.&lt;/p&gt;
&lt;h2&gt;🗓️ Timeline&lt;/h2&gt;
&lt;p&gt;Effective immediately: The id field is deprecated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By June 30th, 2025:&lt;/strong&gt; Clients must switch to using the urn field.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After June 30th&lt;/strong&gt;: Services relying on the id field may experience degraded behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partial data loss (e.g., new resources may not appear)&lt;/li&gt;
&lt;li&gt;Full API response failures in some cases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ What You Should Do&lt;/p&gt;
&lt;p&gt;Update your integrations to rely on the urn field instead of the id. The urn is a stable, unique string that will not be affected by integer limitations.&lt;/p&gt;
&lt;h2&gt;💡 Example Changes&lt;/h2&gt;
&lt;p&gt;Here’s what the change looks like for different resource types:&lt;/p&gt;
&lt;h3&gt;🎵 Track&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  {
    &amp;quot;id&amp;quot;: 12345678,
    &amp;quot;title&amp;quot;: &amp;quot;My Track&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  {
    &amp;quot;urn&amp;quot;: &amp;quot;soundcloud:tracks:12345678&amp;quot;,
    &amp;quot;title&amp;quot;: &amp;quot;My Track&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;📁 Playlist&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  &amp;quot;id&amp;quot;: 98765432,
  &amp;quot;title&amp;quot;: &amp;quot;My Playlist&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  &amp;quot;urn&amp;quot;: &amp;quot;soundcloud:playlists:98765432&amp;quot;,
  &amp;quot;title&amp;quot;: &amp;quot;My Playlist&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;💬 Comment&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  {
    &amp;quot;id&amp;quot;: 87654321,
    &amp;quot;body&amp;quot;: &amp;quot;Great track!&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  {
    &amp;quot;urn&amp;quot;: &amp;quot;soundcloud:comments:87654321&amp;quot;,
    &amp;quot;body&amp;quot;: &amp;quot;Great track!&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Accessing resources via URN&lt;/h3&gt;
&lt;p&gt;All the endpoints are ready to take &lt;strong&gt;either&lt;/strong&gt; id &lt;strong&gt;or&lt;/strong&gt; urn!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Before:
&lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/tracks/12345678&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;After: &lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/tracks/soundcloud:tracks:12345678&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We recommend to move always URN going forward. Accessing via id is deprecated immediately! &lt;/p&gt;
&lt;h2&gt;📦 Bonus Tip&lt;/h2&gt;
&lt;p&gt;To extract the numeric part of the URN for legacy support or internal mapping, you can parse the URN string — but we strongly discourage from doing, isntead treat the URN as the primary key moving forward.&lt;/p&gt;
&lt;h2&gt;💬 Need Help?&lt;/h2&gt;
&lt;p&gt;If you have questions or run into issues, please reach out through our developer support channels, open issues on GitHub or check the updated documentation &lt;a href=&quot;https://developers.soundcloud.com/docs/api/explorer/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s make this transition smooth — thanks for building with us ❤️&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New artist name field on our API]]></title><description><![CDATA[A while ago we made an improvement to how track and artist information is displayed on SoundCloud, making it clearer and easier to…]]></description><link>https://developers.soundcloud.com/blog/api-artist-metadata</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/api-artist-metadata</guid><pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A while ago we made an improvement to how track and artist information is displayed on SoundCloud, making it clearer and easier to understand.&lt;/p&gt;
&lt;p&gt;Previously, it could be confusing when content wasn’t directly uploaded by the artist. One example might be tracks that are coming in as official label content. Another one would be artists collaborating.&lt;/p&gt;
&lt;p&gt;This often lead to long track titles containing artist names, that were difficult to read on mobile devices. With our changes we cleaned those titles up.
Now underneath the track we show the correct artist instead of the profile name.&lt;/p&gt;
&lt;p&gt;For user uploaded content, we now display the information that users give us in the ‘artist’ metadata field during upload. If they leave this field blank, we will display their profile name.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/0ab52/creepin.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.85714285714285%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAADjElEQVQ4y42TW0zbVRzHa7KoMcZLNJnRxESNmxKzByMPm3sQW5hyGdSxAoPR0gItGws43SwbY4hjlMuGdKztGGxyc5eWlWthBh0bY5TrtgfMXtSNSwEfHOMidz+e/58sUZ88yS/nnP//n8/vnHw/f8Wf8/M8GB5mZHSUkZFRJicnmZubY2Zm5l81OzsrP3/8bmFhgf+O5eVlFBMTE9zINrEvNpLQCDUajYbo6Gg0UVGo1WrCIyIIDQ1FvWMHUTExJBuNmFJSqKmp4f7933BeceNyu+n2evGNjaLw+XwcP5CGyaAnLj6epKQk9AYD8Votu2Jj18ACFhYezqfBwXIDVVAQtQLY0NTE0y+9zLoXXiQ5dR+PHv6xBkz74kv2pKaSIjonCJgEjhanidy5k3BxypCwMBmmDAwk4GMl723aRHV1FS0tzWiCP2D9669hTEtjSgKOjY1xODOTw0eOyLP50CHMGRkcNJtJ37+fFNFoz969chOd3kBs/G42b/1QBrrEdTe+vR7Fk0+xOzGZ6amHKIZFII5TVpw1VXxXXkGZ4ww2+xmqz1XI+8qzZ3HYSyk9eQJ7STEVp8tI1JsosZbQ199PjDaBXVodZeXlTIz7UEip3e7rZaDHi7e7m1tdXXR2duIV862bnXJd77hGx4/tXL/2E50dN/C0XGVoaIi/VldZEckuLy2xurLC9PQjFEtiMzW/zNLqWvSLYv9/xz9VktaLi4tCm8nfaS/LpyjrKw6YMygoKKCwqEiugsJC8iwWjuXm8nVODplZWeSK9dHsbOrq6rh372fu3B5kUNTdu3dobWtFMSJCqTYbSFR/QkBgkOxeqEg1bPt2ooSPnz1WJiQEpUol76XU3e4rNLe24h+gYotSRVVtLT093UKb8XE+P5hBulAnPT0do8mEITERrU5HbFzcGjQykghJHyG4BH7f35/GxkYqq87j986rPPHsM+RY8ujv7xXaCA8lPYwpJpIFTC/BEhKE2Dr5z5BgkouSzKpt2/hIqWSDnx/uejff11YSuHUD6557nmP5+fT1elE8+PUXbLlHaW9rwyO6NjU08EOrh6seDy3NzbicTloaG9a8c7q4fOmi+JP0XBKzS1z7zXc38sobb1Fqt9MrTJHFLj7+DZcdNs7bHdisVspl705y+lQpBXl5WItEUPkWvj1RjEV8azIlYXPYGRgcoF4col407Oq6yYWLF/gbKz4GFAPmh/UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;example&quot;
        title=&quot;example&quot;
        src=&quot;/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/8ff1e/creepin.png&quot;
        srcset=&quot;/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/9ec3c/creepin.png 200w,
/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/c7805/creepin.png 400w,
/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/8ff1e/creepin.png 800w,
/blog/static/67fb1b1c7ecb70113354cc23c4ddeed6/0ab52/creepin.png 1050w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now this field is, &lt;strong&gt;finally&lt;/strong&gt;, also availabe on our API.&lt;/p&gt;
&lt;p&gt;Look out for &lt;code class=&quot;language-text&quot;&gt;metadata_artist&lt;/code&gt; &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Creepin&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
..
    &lt;span class=&quot;token property&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Metro Boomin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
..
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
..
    &lt;span class=&quot;token property&quot;&gt;&quot;metadata_artist&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Metro Boomin, The Weeknd, 21 Savage&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Upcoming changes to SoundCloud Access Tokens]]></title><description><![CDATA[tl;dr: If your app doesn’t store SoundCloud access tokens outside of the context of a user’s client (i.e. a database) the following should…]]></description><link>https://developers.soundcloud.com/blog/access-token-changes</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/access-token-changes</guid><pubDate>Mon, 02 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;tl;dr: If your app doesn’t store SoundCloud access tokens outside of the context of a user’s client (i.e. a database) the following should not affect you.&lt;/p&gt;
&lt;p&gt;There will be upcoming changes to Access Tokens important for third parties integrated with SoundCloud. Access tokens were previously dispensed as &lt;a href=&quot;https://www.oauth.com/oauth2-servers/access-tokens/&quot;&gt;opaque strings&lt;/a&gt; and going forward will be transitioning to a &lt;a href=&quot;https://jwt.io&quot;&gt;JWT&lt;/a&gt; format. &lt;/p&gt;
&lt;p&gt;Ideally, clients should &lt;em&gt;not&lt;/em&gt; rely on the shape/format of these tokens as this is transitive PII user data, but for example, embedded apps that are perhaps storing this information in a schema based environment, the size of the tokens will no longer fit in a basic 255 varchar. There will be no guarantees about size, as JWT’s are varying by nature and are dependent on the contents encoded inside of them.   &lt;/p&gt;
&lt;p&gt;Please update your app accordingly so that users do not incur any issues with your app’s integration.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Migration to Oauth 2.1]]></title><description><![CDATA[Earlier this year, we updated our API Guide to let developers know that SoundCloud authentication is now operating on the OAuth 2.1 protocol…]]></description><link>https://developers.soundcloud.com/blog/oauth-migration</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/oauth-migration</guid><pubDate>Tue, 23 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, we updated &lt;a href=&quot;https://developers.soundcloud.com/docs/api/guide&quot;&gt;our API Guide&lt;/a&gt; to let developers know that SoundCloud authentication is now operating on the OAuth 2.1 protocol, a popular open standard used by many API providers. This change needed to be made to ensure that we are staying up-to-date in maintaining the highest level of security possible to protect our platform from potential bad actors.&lt;/p&gt;
&lt;p&gt;As a result, we must ask that our developer community adhere to this new standard as well. We are giving developers who already implemented SoundCloud authorization using OAuth 2.0 a grace period to transition their integrations to OAuth 2.1 and ask that these updates be made by &lt;strong&gt;October 1, 2024, at the latest&lt;/strong&gt;. We will deprecate the OAuth 2.0 protocol after October 1, so your integration will no longer function properly should you fail to meet this deadline.&lt;/p&gt;
&lt;p&gt;For reference, you can see the differences between these standards &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-10&quot;&gt;in the RFC&lt;/a&gt;. The most notable change is that &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7636.html&quot;&gt;PKCE is now required&lt;/a&gt; in order to securely exchange the auth code.&lt;/p&gt;
&lt;p&gt;Please understand that we will not be making exceptions to this requirement. We will be notifying all developers of this update via email, and will continue to send reminders over the next months. As always, also be sure to keep an eye on this blog and our &lt;a href=&quot;https://twitter.com/SoundCloudDev&quot;&gt;Developer Twitter account&lt;/a&gt; for other updates to come.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android Large Screen Optimization]]></title><description><![CDATA[Large Screen Devices - The New Frontier SoundCloud large screen optimized Recently, the Android team at SoundCloud took on a project to…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-android-large-screen</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-android-large-screen</guid><pubDate>Mon, 31 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Large Screen Devices - The New Frontier&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/blog/49926d7e44d44ca3f84a67148412ba7b/after_vid_.gif&quot; alt=&quot;SoundCloud large screen optimized&quot;&gt;&lt;/p&gt;
&lt;p&gt;Recently, the Android team at SoundCloud took on a project to optimize the Android app for large screen devices. With the increasing use of devices like tablets, foldables, and Chromebooks, we decided to provide better support for them.&lt;/p&gt;
&lt;p&gt;The data showed us users were adopting these devices rapidly. What is more, both Google and Samsung had been vocal about the importance of optimizing for larger screens.&lt;/p&gt;
&lt;p&gt;Google is even launching a new &lt;a href=&quot;https://android-developers.googleblog.com/2023/07/introducing-new-play-store-for-large-screens.html?m=1r&quot;&gt;Playstore&lt;/a&gt; for large screen optimized apps only.&lt;/p&gt;
&lt;p&gt;In this blog post, we’ll go through some of the challenges we faced, the UX and UI changes we made, and the code implementation. We hope our experiences will help you in your own large-screen adventures.&lt;/p&gt;
&lt;h2&gt;Why did we invest in Large Screens?&lt;/h2&gt;
&lt;p&gt;We saw a trend among our users - more and more of them were using SoundCloud on their tablets, foldable devices, and Chromebooks. And it wasn’t just happening in &lt;em&gt;our&lt;/em&gt; user base. Google and Samsung have been going all-in on large screens, bringing them more into the mainstream.&lt;/p&gt;
&lt;p&gt;When Google rolled out their Pixel Tablet, well-known tech reviewer Marques Brownlee pointed out that SoundCloud stood out from the crowd. He appreciated that we didn’t just stretch our phone app to fit the larger screen, but instead, we crafted an experience that fully utilized the extra space. If you’re interested, you can check out the video &lt;a href=&quot;https://www.youtube.com/watch?v=aTf7AMVOoDY&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Our Investigation&lt;/h2&gt;
&lt;h3&gt;Where and how is music on large screens heard?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Fun fact&lt;/strong&gt;: People listen to music on their Tablets mostly while cooking.&lt;/p&gt;
&lt;p&gt;Imagine you’re in the middle of preparing a meal, your hands are busy, but you can still easily control your music - that’s convenience at its best.&lt;/p&gt;
&lt;p&gt;But it’s not just about individual experiences. Tablets come into play during social events as well. The larger screen makes it simpler for everyone to engage with the music, whether it’s to see what’s currently playing or to add their favorite track to the queue.&lt;/p&gt;
&lt;p&gt;Through understanding these usage scenarios, we can better tailor our services to enhance the music listening experiences of our users. Be it cooking dinner or hosting a gathering, SoundCloud is there to make those moments even better with great music.&lt;/p&gt;
&lt;h2&gt;Google’s and Samsung’s Large Screens Guide&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/large-screens/&quot;&gt;Google’s&lt;/a&gt; and &lt;a href=&quot;https://developer.samsung.com/one-ui/foldable-and-largescreen/intro.html&quot;&gt;Samsung’s&lt;/a&gt; large screen guides are roadmaps for developers wanting to optimize their apps for large screens. It encourages using scalable images and responsive and dynamic layouts for a consistent user experience across different screen sizes.&lt;/p&gt;
&lt;p&gt;Google also highlights using the extra space for features like split-screen views, and emphasizes touch target sizing, clear text, and intuitive navigation. Following these best practices ensures apps are user-friendly and competitive in the large screen devices market.&lt;/p&gt;
&lt;h2&gt;Google’s Large Screen Compatibility Checklist&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/docs/quality-guidelines/large-screen-app-quality&quot;&gt;Google’s Large Screen Checklist&lt;/a&gt;, available in their Android developer documentation, provides developers with a &lt;strong&gt;tier-based framework&lt;/strong&gt; for ensuring high-quality app experiences on large screen devices. The checklist encompasses not only UI design but also considerations for navigation, keyboard/stylus input, and overall app usage.&lt;/p&gt;
&lt;p&gt;The checklist is divided into tiers, offering developers a structured approach to prioritize and implement essential features and optimizations based on the level of support desired for large screen devices. The tiers range from basic support to more advanced features and functionalities that fully leverage the potential of large screens.&lt;/p&gt;
&lt;h2&gt;The SoundCloud App&lt;/h2&gt;
&lt;p&gt;When we reviewed Google’s checklist it was clear that the SoundCloud app fell short in certain areas, particularly in terms of adapting the screen layout. The app’s tendency to merely stretch the phone layout to fit larger screens resulted in a less than optimal user interface. This realization was our call to action.&lt;/p&gt;
&lt;p&gt;In the upcoming sections, we’ll take you on a visual tour of these components, accompanied by code snippets and technical explanations to provide you with an inside look at our optimization process.&lt;/p&gt;
&lt;h3&gt;Bottom Navigation Bar&lt;/h3&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Horizontally stretched phone layout bottom navigation bar&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c475858b665c881f250318b6bfdb3de9/2e21e/nav_before.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.18904726181545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB6ElEQVQoz41S227bMAzN961JkL7kJV+7t2HACqTZCl+Spr7EtS0pthzLN1k6kxwYA9YOGAHiyOQhTR5pMY6jtgbgg39mf+fms1Jq8oWN9X2PYRhgmuNuGnfuR/s8/ie2sBNmWabjONZxGOvwLdIlTTQngRacaZanum0aXVdcc86153k6DEMtbrUWVaWLZtSXctBt1+u+bfRCSonj8QjXdZHECYK3AEl4QnT6CZbFCH0H5ZWBlyWyPMfhcIBpCpITVIzhclN4SkbQgiMjKaaVq6qCEMKs3aNtW3Sd8baG/VnTNBN2XWekGSaeMDErkzIStb0EFwOk0hBdf284yyLliP819Q+NrYaIowhJckHFr2AFxXtKEAQR3hmHn3GkrEQZBeCEgAmJqpFmSoHUSFBQU5Mz1LUAITkWdnTffYHr/EIQnuEdXZxeX7H/8YRDnOIrq/EcJfC/f0PguXjOFZxsgOe7eNrvcT6d4b94iKIYjuOYCZUCYxRFcUXJucHCaMpBCUVZ18hFg9LoxvLMbMBBbgNYLUEpNRMRU1OBmSntPVBK7iubBzm9rxntJchRTqIrOUwoZ56S0CZnOfZbqXt87rNYr9fYbDawuFqtsFwusd1usdvt8OXhAZvHxyk38yzHonXLnWssWv8NF/0nBHflsLMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before&quot;
        title=&quot;Before&quot;
        src=&quot;/blog/static/c475858b665c881f250318b6bfdb3de9/8ff1e/nav_before.png&quot;
        srcset=&quot;/blog/static/c475858b665c881f250318b6bfdb3de9/9ec3c/nav_before.png 200w,
/blog/static/c475858b665c881f250318b6bfdb3de9/c7805/nav_before.png 400w,
/blog/static/c475858b665c881f250318b6bfdb3de9/8ff1e/nav_before.png 800w,
/blog/static/c475858b665c881f250318b6bfdb3de9/6ff5e/nav_before.png 1200w,
/blog/static/c475858b665c881f250318b6bfdb3de9/2e21e/nav_before.png 1333w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Traditionally, SoundCloud’s Android app utilized a bottom navigation bar in a phone layout, which was stretched horizontally to fit larger screen sizes. This approach was straightforward, but it proved ineffective for larger screens. The extended navigation bar made it difficult for users to navigate the app, as it required them to move their focus from one end of the screen to the other.&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Nav bar rail optimized for large screens&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/13e9bbfb388b3e17460827288d4cb7a0/2e21e/nav_after_home.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.18904726181545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABzUlEQVQoz51S13LbMBDk//9YHmIpUmyNWEWKRewFBHu5DUCLscfJ5CE7swRxBywOe1CSJJnHcVwB0N8o8f79wOfcjnVdNypCTGYgR8l5nvHUEJTn/AmxceOOjzMARQjQsiwUxzG5nkuu45Jl2JRFHhWhSbxMKA0DahtOnFVUFAXpuk6maVJdMarLgspuISefiHcjKV3XQQjCtm2oqorw8cDddRH6DnzrDUUSwLMMlFmKqqogLIKm6YIasjQDy3Mk7YLDY0HK+q3CreS+7zEMwyYuY9M0YRoHcbUFo/iX8ek5DsO4rZXzVcbnBe0gcitB2T2QC6UvUuyzP//C5vOXmLKb7HsuXNdBVaZIsghBFMMwbvCTAmrE4GUVMttCEfhImhmFIG8bBGGIJEqQPmLUNX8XnEV1d9uEoV03Ud3ShY93vJ3OUP0Q31iHn1EG4/AdzvUCrVihpSsM64bj6QT75sBUTYRhDGV/R5zXWye7vqOmbUg0a+sob1vK2454P1CV59TUjKp2orKdqRRdzmWsaaksq22Pst+970Y0vEfT9OC8wyhM/h8oQRAwycvlyg6HMzsez+zl5Qd7fb0w3/OYJ+n7zN+5x550Xfc35fwXsgZJBbheLVAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After&quot;
        title=&quot;After&quot;
        src=&quot;/blog/static/13e9bbfb388b3e17460827288d4cb7a0/8ff1e/nav_after_home.png&quot;
        srcset=&quot;/blog/static/13e9bbfb388b3e17460827288d4cb7a0/9ec3c/nav_after_home.png 200w,
/blog/static/13e9bbfb388b3e17460827288d4cb7a0/c7805/nav_after_home.png 400w,
/blog/static/13e9bbfb388b3e17460827288d4cb7a0/8ff1e/nav_after_home.png 800w,
/blog/static/13e9bbfb388b3e17460827288d4cb7a0/6ff5e/nav_after_home.png 1200w,
/blog/static/13e9bbfb388b3e17460827288d4cb7a0/2e21e/nav_after_home.png 1333w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To remedy this, we adopted a vertical navigation rail for large screen layouts, aligning it to the side of the screen. This is recommended by Google as it provides a more comfortable browsing experience. The menu options are now neatly stacked in a column, making them accessible without the need for extensive eye or cursor movement.&lt;/p&gt;
&lt;h3&gt;Code Implementation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before: BottomNavigationView&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously, the SoundCloud Android app used a &lt;code class=&quot;language-text&quot;&gt;BottomNavigationView&lt;/code&gt; to create the bottom navigation bar. This worked well on smaller devices but led to a stretched UI on larger screens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After: NavigationRailView&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;To address this, we replaced the &lt;code class=&quot;language-text&quot;&gt;BottomNavigationView&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;NavigationRailView&lt;/code&gt; for larger screens. Both these classes implement the &lt;code class=&quot;language-text&quot;&gt;NavigationBarView&lt;/code&gt; interface. This allows for a smooth transition with minimal code changes.&lt;/p&gt;
&lt;p&gt;To differentiate between smaller and larger screens, we created two separate layout files. One uses the &lt;code class=&quot;language-text&quot;&gt;BottomNavigationView&lt;/code&gt;, and the other uses the &lt;code class=&quot;language-text&quot;&gt;NavigationRailView&lt;/code&gt;. In Kotlin, we dynamically set the layout file based on the size of the device.&lt;/p&gt;
&lt;h3&gt;Identifying Large Devices&lt;/h3&gt;
&lt;p&gt;We identify whether a device is large or small by using a boolean variable, &lt;code class=&quot;language-text&quot;&gt;is_tablet&lt;/code&gt;, defined in three separate XML value files based on the screen width.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// res/values-sw600dp/booleans.xml
&amp;lt;bool name=&amp;quot;is_tablet&amp;quot;&amp;gt;true&amp;lt;/bool&amp;gt;

// res/values-sw370dp/booleans.xml
&amp;lt;bool name=&amp;quot;is_tablet&amp;quot;&amp;gt;false&amp;lt;/bool&amp;gt;

// res/values/booleans.xml
&amp;lt;bool name=&amp;quot;is_tablet&amp;quot;&amp;gt;false&amp;lt;/bool&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In Kotlin, we check the &lt;code class=&quot;language-text&quot;&gt;is_tablet&lt;/code&gt; boolean value and set the layout file accordingly. This is done using the following code:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;val isTablet = context.resources.getBoolean(R.bool.is_tablet)

if(isTablet) {
    setContentView(R.layout.tablet_layout)
} else {
    setContentView(R.layout.phone_layout)
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hardcoding resources is not the best solution. Google’s documentation provides some great insights on supporting different screen sizes, including the concept of Window Size Classes. Window Sizes allow the UI to adapt to various window configurations on foldable and large screen devices. Here’s the link for more details: &lt;a href=&quot;https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes&quot;&gt;Window Size Classes&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Unit Testing and A/B Testing&lt;/h3&gt;
&lt;p&gt;To enable testing, we added a wrapper class around the &lt;code class=&quot;language-text&quot;&gt;isTablet&lt;/code&gt; boolean. We also leveraged feature flags to conduct A/B testing of our new layout. We introduced a class &lt;code class=&quot;language-text&quot;&gt;NavRailExperiment&lt;/code&gt; to check both the device type and the feature flag state:
Here’s a look at our implementation of this approach:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class NavRailExperiment(private val appFeatures: AppFeatures) {
    val isEnabled: Boolean
        get() = context.resources.getBoolean(R.bool.is_tablet) &amp;amp;&amp;amp; appFeatures.isNavRailEnabled()
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Mini Player&lt;/h2&gt;
&lt;p&gt;Transitioning from a Stretched Phone Layout Mini Player to a Right-Aligned Mini Player&lt;/p&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Horizontally stretched phone layout Mini Player&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/db8d1462c7be8c5a7e441da0c1c48304/2e21e/before_mini_player.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.18904726181545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB60lEQVQoz41Q227aQBT0ryZK1KhSpIpLgErNE//Wl0ZN1KgxGBcwBhtfdtldQ3w9010jKhrlIUealT07e86csaqqQl3XICK8Vy2vQWc4vzuhaZoWljkMURRFCzPgxBE1x4dvhhgDRvN2qCnr1D0MQ3ieB2/hwXXmSEIPbONAsS3izRp7pSAFB2MMk8kEs9kMcicg9T8/1FikJdShODo0mM/nsG0bwSbQjVcI/AV89xks2mDlOuBpDLHbIYqituF0OkUSJ5Bpimhf42FTIxGvR4fGbJ7n7cpmHbN2VZUoi7wdVpRly5flMW+jyzVKzTeGr2occq1pCNYpAyOs6+Zfhh+pk5nzahuaBitvieVyAc5jREmIdbDF1HHhRwwvoYAXcyTzGdjaR6QqsExnlmXwgw2iMEK03kIIdWxYaXdLLXYmLzq/BRx3iqUe8Pz4BNsP8F1n8yuM4Tz8wML+jQlrMIkbrXPx8+kR8z9LzOwZQm3C0iuSgRA7klJQts9IKklZpihJEhJKUSwVif2eWByT3O0oVa8aBaWMUZzEJKSkNGX6TUbW/6EcQR+L8N2y7u+/8fF4zG9vv/CLi2t+dfWJX15e85ubz3wwGLQYDod8NBrxrxoj/T0YDPnd3R3v9/u81+vxTqfDu91uy/0FTnAwR+vK36wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before&quot;
        title=&quot;Before&quot;
        src=&quot;/blog/static/db8d1462c7be8c5a7e441da0c1c48304/8ff1e/before_mini_player.png&quot;
        srcset=&quot;/blog/static/db8d1462c7be8c5a7e441da0c1c48304/9ec3c/before_mini_player.png 200w,
/blog/static/db8d1462c7be8c5a7e441da0c1c48304/c7805/before_mini_player.png 400w,
/blog/static/db8d1462c7be8c5a7e441da0c1c48304/8ff1e/before_mini_player.png 800w,
/blog/static/db8d1462c7be8c5a7e441da0c1c48304/6ff5e/before_mini_player.png 1200w,
/blog/static/db8d1462c7be8c5a7e441da0c1c48304/2e21e/before_mini_player.png 1333w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the past, SoundCloud’s Android app featured a Mini Player stretched horizontally across the screen. While this design worked for smaller screens, it resulted in a suboptimal user experience on larger devices due to the extended reach required to interact with it.&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Small right-aligned Mini Player optimized for large screens&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/498099e599a4bc9f37ca666f575b477e/2e21e/after_mini_player.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.18904726181545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABlklEQVQoz5VSW2/TMBTOvwaENFGB2AO88pP2wERbNqEsTZs49ya2c7GT2HF8KU66h0pIk/hkH8nn/h0fR0qplDLGXP4H1l8b42htpRFCzPOstFpzmMs/ucyKW4W9zpJDawRhbpHmMUhbVBAYMVLXsOJsHHrSdV0QBACAgfYDIYTrrJXjJF+DrcHzvDzLoygpkjA9PddVlp78FkMbCiv4sgJB1CEMR7UtFCLcuTYzrrD8p2kSYpr4qJTknC+ahZBkK2Yxa6XErCiTUhvnlphS2lzM23O6fS7BdmJFkedZRmlTt7iEOARJWXenihYNadO4q8pmlLbayHmJYI3rpsLDwJy1oIrC48FzkwT4pwOI46fd3k2Lh5Y9nfHx10/g/vEa7UF9DMD29z4MQPByPJ8r5zr1ntKeEku77yljQ1PX/Thixgmf2hpbUzvMiAqEcVlVXUcQskr6ypnziTF7uJViEld+199+Y3+WJbFt+36027r7nfv4+OwfQhtgh7eYVqv1i6Pw2/3m+/3m8927L3fvv376+GPz4S/Qa2vUPHCubwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After&quot;
        title=&quot;After&quot;
        src=&quot;/blog/static/498099e599a4bc9f37ca666f575b477e/8ff1e/after_mini_player.png&quot;
        srcset=&quot;/blog/static/498099e599a4bc9f37ca666f575b477e/9ec3c/after_mini_player.png 200w,
/blog/static/498099e599a4bc9f37ca666f575b477e/c7805/after_mini_player.png 400w,
/blog/static/498099e599a4bc9f37ca666f575b477e/8ff1e/after_mini_player.png 800w,
/blog/static/498099e599a4bc9f37ca666f575b477e/6ff5e/after_mini_player.png 1200w,
/blog/static/498099e599a4bc9f37ca666f575b477e/2e21e/after_mini_player.png 1333w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To provide a more user-friendly interface we opted for a small Mini Player on the right side of the screen. This ensures controls are comfortably within thumb reach.&lt;/p&gt;
&lt;h3&gt;Code Implementation&lt;/h3&gt;
&lt;p&gt;The Player is a BottomSheet with a ViewPager, the Mini Player is a view inside each page of the ViewPager. We use a &lt;code class=&quot;language-text&quot;&gt;BottomSheetBehavior&lt;/code&gt; to support  collapsed and expanded states. In the expanded state the player shows the full width and height of the ViewPager.
In collapsed state it shows only the Mini Player view, and the height is collapsed to 50dp. The width was always &lt;code class=&quot;language-text&quot;&gt;match_parent&lt;/code&gt;, but since we want it to be small, we set the width of the &lt;code class=&quot;language-text&quot;&gt;BottomSheet&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;200dp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The class &lt;code class=&quot;language-text&quot;&gt;BottomSheetBehavior&lt;/code&gt; provides us with a method &lt;code class=&quot;language-text&quot;&gt;setMaxWidth&lt;/code&gt; which we use to set the width while animating the collapse and expansion of the BottomSheet Player.
The other issue here is that we also have to set the width on the ViewPager and the ViewPager’s visible page.
Additionally, we must move the player to the right side thus setting the left margin on layout params on each player transition.&lt;/p&gt;
&lt;p&gt;The&lt;code class=&quot;language-text&quot;&gt;BottomSheetBehavior&lt;/code&gt; also has a &lt;code class=&quot;language-text&quot;&gt;BottomSheetBehavior.BottomSheetCallback&lt;/code&gt; field.
The &lt;code class=&quot;language-text&quot;&gt;BottomSheetBehavior.BottomSheetCallback&lt;/code&gt; has an onSlide method which allows us to set the width and left margin on the &lt;code class=&quot;language-text&quot;&gt;BottomSheet&lt;/code&gt; and the &lt;code class=&quot;language-text&quot;&gt;ViewPager&lt;/code&gt;’s visible page.&lt;/p&gt;
&lt;p&gt;Below is the code: &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bottomSheetBehavior.bottomSheetCallback = BottomSheetBehavior.BottomSheetCallback() {         
    val miniplayerWidth = screenWidth * offset
    val leftMargin = screenWidth - miniplayerWidth
        
    setLeftMargin(viewPager.views(), leftMargin)
        
    setWidth(viewPager.views(), bottomSheet, miniplayerWidth)
};&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Comments &amp;#x26; Playqueue on Player&lt;/h2&gt;
&lt;p&gt;The SoundCloud Android app hosts two main features on its player screen - the comments and the Playqueue. Before, when a user accessed either of these features, a new screen would open, occupying the entire display and obstructing interaction with the player.&lt;/p&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Horizontally stretched phone comments layout&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/19374224f0bdc7ef165020ac60d56db3/8b984/before_comments.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACgklEQVQoz22S60vTYRTHn+lvmrt42X7b7EK2nPut0mU2JdTyUplOu1Dgi8JaRQVR9CIoDKLoVTfy7n67qX9BKBkEEobihbZcpk5oOGEvE/+Gb+f3bJhCLz6c8zwPfM/3nPOwc7UVqHSW4OSJclSU2lBoyIEpTw1zfjZHzBVg1GXAqFVB1DKImv+h2sqZ07oHcn83Eok1fB4fgy6bQVAxZAkM6kyGAr0Ai2EXzFTEkpsJE4maSPwfbFskwSPSAciDvVhdjWFs9CM8N67C47kGz80O3Lp9HVYqyBiJE9mEmBba6Wybw+J9IkIBGZFwGHOzM4jFYlghkskkNjc30dvXzYs8uH8HHe1t3KW43aEm5cykSblklrwsPHl0F/Pzc5glQYVwJAw56Mfjzqd4/f4NvL5BBEN+vHr5DLuN1D7NVRG26Hdi1meQoF6Dy62NmJr6xkUVlpaX0PniOepbmnDqbAMYzVNpW0FLM9aqiYw0qhQaestTK0uxHcRQwI/IjzCmp6dIcB7fqf3FpV+Ix+OYmZvFpfYraL3oRtuFZribG+FuqoP7TC3cp2s4LY3VOE93lWU2MJfThmFqJ7YSQzS6wNuN/oxiIRqlPMJjIpFAYn0da0rcYo2zTnk8/ht/NjbQ++EtWHXVUfh9A1heXMYCCdQ1VKP4cAkOuY7BUeXi2CvKYS8vhVQmQXJYIdn2QrJaIBWZYN8vchyUF5l1YPU1x9HT9Q6Tk18xMfEFrkonGM1EXaCHYMiHIBogGAsg5NNZlwUh/U8FtpNMIkeZc3FRIQLkcPzTKGd4JMi36gsGIIdCkIeGCIqhIOSAD7LPC9nbz/+uPNBDdMPb10V7GMTDex78BRrBtISq+QlaAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before Comments&quot;
        title=&quot;Before Comments&quot;
        src=&quot;/blog/static/19374224f0bdc7ef165020ac60d56db3/8ff1e/before_comments.png&quot;
        srcset=&quot;/blog/static/19374224f0bdc7ef165020ac60d56db3/9ec3c/before_comments.png 200w,
/blog/static/19374224f0bdc7ef165020ac60d56db3/c7805/before_comments.png 400w,
/blog/static/19374224f0bdc7ef165020ac60d56db3/8ff1e/before_comments.png 800w,
/blog/static/19374224f0bdc7ef165020ac60d56db3/6ff5e/before_comments.png 1200w,
/blog/static/19374224f0bdc7ef165020ac60d56db3/2f950/before_comments.png 1600w,
/blog/static/19374224f0bdc7ef165020ac60d56db3/8b984/before_comments.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Horizontally stretched phone playqueue layout&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7a887c6c3962884e45e3367c0309b415/8b984/before_playqueue.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB1klEQVQoz3WSy3KbQBBFwbYsSwLBzHSDeA2vOFZcziZVySbxJvv8/+fcXBBIlVSy6EI10hzOva3g5/ev6EuD3Gxh9gFsdA8XT/OwDD9Hd3CHEHIIIPu/J4TbBSh5v7JPCM6twVgmUF4yvGSPhKRbiNlxniDpI4RnEt/xNyGUUD2Ef85+PQsQfHtTnOsERbpBRkgmEZzEcFkC0SOcO8BN4AUq0WoaLnOBXZ4E+tc3fBgq9JVFXVhUtcKdLFypkFIgeQqRA223M1QX6NV0H16BGb8LPr//wHms0LLH9PiI1EawhcDWJ9gqhyXU5clsOkH1P/GvwF/vX/DSZuhqQXFKobTM2wr50CIfW4gv+AIHmSqgqa6m0Qq9WF6B49BhaBRNkcLz2Y416ucOzaePaF5fkA2etvkSn8tz+3lRulpGtw4zviQYGO/ZZ/BlioY9FuzQ8Mz4CqZrYDsP19YQVqCTqcZL9Ptb7GVRM7DXFEPNhZyOMMkGZuqq4kI6QmgvQw/pGZ016HTO6Mr/m86x/wFMdxuMXtihYZwdcvY4xyNgBo4DlFClrTC6Tlu3O2jycIu8AuMQQUtDT7umSOArg4Y9ChcjTQHt/cVwAhOoU5cE6gqMwyt0Bf4GECAKwKktQbsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before Playqueue&quot;
        title=&quot;Before Playqueue&quot;
        src=&quot;/blog/static/7a887c6c3962884e45e3367c0309b415/8ff1e/before_playqueue.png&quot;
        srcset=&quot;/blog/static/7a887c6c3962884e45e3367c0309b415/9ec3c/before_playqueue.png 200w,
/blog/static/7a887c6c3962884e45e3367c0309b415/c7805/before_playqueue.png 400w,
/blog/static/7a887c6c3962884e45e3367c0309b415/8ff1e/before_playqueue.png 800w,
/blog/static/7a887c6c3962884e45e3367c0309b415/6ff5e/before_playqueue.png 1200w,
/blog/static/7a887c6c3962884e45e3367c0309b415/2f950/before_playqueue.png 1600w,
/blog/static/7a887c6c3962884e45e3367c0309b415/8b984/before_playqueue.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We revamped this experience in the following ways: Instead of taking up the whole screen, the Comments and Playqueue sections now open on the right side of the player. This allows users to engage with these features and interact with the player simultaneously.&lt;/p&gt;
&lt;p&gt;Additionally, the buttons to access the comments and Playqueue were originally at the bottom of the screen. To increase accessibility and free up space at the bottom, we moved them to the left side.&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Right-aligned comments optimized for large screen&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3184394bbe2a94f7516f6486199f2b0a/8b984/after_comments.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAC/ElEQVQoz1WSWUxTeRTGL73ViRjNvMwDmhiMUeMSXCoOggpItaKDLNIyVZZKWdpbQVGqxAeMcYmTcRnKmCgmrgQ3BieTiQouyRgkLg8aH4wmFWIRsRQQqFQolJ9/L8kknuR7PL9853yfFDVzKrFzJpO+VEvafAlzTgz2ri8Uvu3A+Pte1hqmkVm8EmVvGvmLJHKWSJwoXYf/82c6Ot7T3t5OIBBgeHiYoaEhpKWzphAVOYWUxTImncQW/Y/Y37ixuD2s+fMIseV5bCxMwv7kGbsrzWyLlji9Lx33m+f8d6+e0BjfjWTUhWGK1pC1QhbSYE6YQHZtFfmPH2NquEZqQTzp5iiUWzcpf/mKssMWqssNjI6O0tnZga+7G7/fz9jYmCopc7mWrBgZ82qhBJlcvYbCm5cp+hhk+6tWSh4147hUhXLmMCW368kvM+DakaS6+Xa2f2CAYDCowlSHcZESyTqZHL0Ax2soSA1n15VqlBevsb3rwfbag6OhjpJzv1H6dy22ig1U7UhUl71dXvp6+giNhsbvFUxpbsQPJOomkJesxbhCwvbrNJw1ldivX8TRdAflYQuOW/9S2ngbx43zGFMiOLnLoO739vbS39+vBjIyMjLucM70ScQv1pJr0LL5Z4lCUyQVAlj8TwP2+w9Qmp9ib34mQnmB/ep5LM5kXAc2qW683o+0tbXh8Xjw+XzjwNkRE1m1UGZropYMkXJB5gzKKtMpOHVI/KwR5U4Tyt0HFAvZ/6rDenIPZy7sU5c/ferD7Xar0P+BkT/JxM0TwHiZjOgwLOvD2elMwmScj9WagEVJIW+nkWKnGZtQriWOPw5Z1eXAlwC9PT3q2d+CUYH6BRpSlokOxsriZAFeE8bu/Wlkl5nQi1JnJ4VzwTwd13EnZ2uOcvp4OU31NQwPBfnQ3smASHlwcFD9oVqbLAExiQ5uXi4cCpkTNThKY7C6jpFx8CBF21NxFS3D+ouOo8JpdcUWGq+dorW1jZZHLXT5usQvvXSLPoZCIb4CPUdHk8s+rcoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After Comments&quot;
        title=&quot;After Comments&quot;
        src=&quot;/blog/static/3184394bbe2a94f7516f6486199f2b0a/8ff1e/after_comments.png&quot;
        srcset=&quot;/blog/static/3184394bbe2a94f7516f6486199f2b0a/9ec3c/after_comments.png 200w,
/blog/static/3184394bbe2a94f7516f6486199f2b0a/c7805/after_comments.png 400w,
/blog/static/3184394bbe2a94f7516f6486199f2b0a/8ff1e/after_comments.png 800w,
/blog/static/3184394bbe2a94f7516f6486199f2b0a/6ff5e/after_comments.png 1200w,
/blog/static/3184394bbe2a94f7516f6486199f2b0a/2f950/after_comments.png 1600w,
/blog/static/3184394bbe2a94f7516f6486199f2b0a/8b984/after_comments.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Right-aligned playqueue optimized for large screen&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/72076ee0eca4a4ae0974d95592447184/8b984/after_playqueue.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAC70lEQVQoz12Sa0iUeRSHX+edioqiL33IIozouuGWZqldRm3KLCSv405lxjjjzDvjBRvD2G3tolNZ0m6rolZbVJNRLbq7bBYl0RWNICIoCioqCHPGEHV0mvHy9N83iurAj/Pt4TznHCl8xkRiZ48nNUJLynwJY3Y0iseP5cVbMg+VsjoxlAzrcuylKZh+lMheJHG4cA0D/gBer5f+/n4CgYDa/48UMXMC4WETSF4oY4iU2KifhPLsOVufvyGhZh+xJTmst6xCuXcfZ5mR3KUCWKSn4+0rBvp6+b6kzMgQDFEasmJkEQ3GuFFsdh/B1N6Ooek8G8w6Uo3h2FuacT56TEmlmX3WGC64q/F1dTIY+EBwcJChoSGGh4eRMpZoyYqWMa4UiZPZotdgaT5D3rsg+U9eUnD3Do7TR7A3uMi/fJG87esoNy2lxv0XHR3v6OnpUVX9/g+fgMvCJJIiZbL1AqzTYN4wjm3nqrE/fIrt9XtsT9/gaGqk4EQlhc1u8ncms8cUxemWa3g9HhX4uVTgnCljiI8cRU6SlswYCdtPoWw/WoZy4RSOa1ew327D0fIfhVcvY794kqyUaVQqy7l56W86vV287+pSj/JFefbUsegWatmSqCVdLNxiCGOHAFr/aUJpbRXAdpTb91DaHmBrPI65dB2ukgTq6o/R6enE5/N90VaBs6aMZsUCmU3xWtLElc0Z0ykuS8VcW0F+6w2U67dQbrZhvdWO7d8m8hr2UlVbRMPvhwXQS29vrwoNBoOMjIwghU2WWTZPAHUyaVEhbE0aR5EzjsyN88k94CS3thLTyVqsjcfIczdg+m0Hrv1W6qsO4hG6/oGBb3eo/0FD8mLxg7GyUBbghBCcu1LYXGxgbdJUEnVjKU8PpeZQMfX1Lk5U/8w50ct37xZX7lCnG/z6bbIExCB+MH2JmFDEGK/BURhN7h9VGCoqSDcn8sumuVjWR+AqSKXu1xzOVu/iz7o6uru78fX1qdp+v19V/ghs10H/CzAx1QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After Playqueue&quot;
        title=&quot;After Playqueue&quot;
        src=&quot;/blog/static/72076ee0eca4a4ae0974d95592447184/8ff1e/after_playqueue.png&quot;
        srcset=&quot;/blog/static/72076ee0eca4a4ae0974d95592447184/9ec3c/after_playqueue.png 200w,
/blog/static/72076ee0eca4a4ae0974d95592447184/c7805/after_playqueue.png 400w,
/blog/static/72076ee0eca4a4ae0974d95592447184/8ff1e/after_playqueue.png 800w,
/blog/static/72076ee0eca4a4ae0974d95592447184/6ff5e/after_playqueue.png 1200w,
/blog/static/72076ee0eca4a4ae0974d95592447184/2f950/after_playqueue.png 1600w,
/blog/static/72076ee0eca4a4ae0974d95592447184/8b984/after_playqueue.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Repositioning the UI elements in this way contributes to a more ergonomic and intuitive user experience on larger screens, reinforcing our commitment to delivering the best listening experience across all devices.&lt;/p&gt;
&lt;h3&gt;Addressing User Experience (UX) Challenges&lt;/h3&gt;
&lt;p&gt;Our changes to the UI introduced some UX challenges, especially related to the comments feature. Two major questions emerged:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When a track changes, should the comments update to correspond to the new track, or should it continue displaying comments from the previous track?&lt;/li&gt;
&lt;li&gt;What should happen to a comment a user is currently writing if the track changes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To tackle the first issue, we opted to update the comments to align with the new track. We believe this adjustment is more in line with user expectations, as it helps keep the entire screen contextually relevant.&lt;/p&gt;
&lt;p&gt;For the second scenario, we implemented a solution where the comments section doesn’t update if the text field is currently in focus. This prevents any in-progress comments being lost, thereby providing a more user-friendly commenting experience.&lt;/p&gt;
&lt;h3&gt;Code Implementation&lt;/h3&gt;
&lt;p&gt;To integrate the comments and Playqueue screens over the player, we introduced a new &lt;code class=&quot;language-text&quot;&gt;FrameLayout&lt;/code&gt; named &lt;code class=&quot;language-text&quot;&gt;player_side_fragment_holder&lt;/code&gt;. This frame holds the additional features and ensures they are displayed without disrupting the main player.&lt;/p&gt;
&lt;p&gt;This side fragment holder is positioned above the player and is assigned a transparent background along with a width of 400dp. Consequently, when the user taps on the comments or Playqueue button, the respective screen appears over the player. This is made possible by a secondary &lt;code class=&quot;language-text&quot;&gt;FragmentManager&lt;/code&gt; which adds either the &lt;code class=&quot;language-text&quot;&gt;CommentsFragment&lt;/code&gt; or the &lt;code class=&quot;language-text&quot;&gt;PlayqueueFragment&lt;/code&gt; to the &lt;code class=&quot;language-text&quot;&gt;FrameLayout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s how the layout is structured:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FrameLayout&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Player&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FrameLayout&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@+id/player_side_fragment_holder&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;400dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_gravity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;FrameLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this layout configuration, the &lt;code class=&quot;language-text&quot;&gt;player_side_fragment_holder&lt;/code&gt; is specified to align on the right side, allowing it to overlay the player when comments or the Playqueue are invoked. This approach ensures that the main player remains interactive while users are interacting with these features.&lt;/p&gt;
&lt;h2&gt;Search&lt;/h2&gt;
&lt;p&gt;In our drive to optimize the large screen experience, we revamped the search screen to offer both search suggestions and top results side by side. The updated design splits the screen into two sections when users begin a search. One side displays real-time search suggestions, while the other showcases the top results. This allows users to compare suggestions and top results instantly, leading to a more seamless and enriched search experience.&lt;/p&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Horizontally stretched search suggestions&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c298f1017b36e2595b16b92b46094967/8b984/before_search.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAA0UlEQVQoz62PbQuCMBSFb/T/yd4MjQoKgr72i6IgIsQS26ZOPW0LLbDwhQ48497t7OyORBTBOx/BvQt4FCNSfReEEAZigmG/m+ByOuDBOKSUyPO8M5RlGTzvCsZ5+Zo+aKs0Td+Bt9sdYRhC11ptp9IqfmYCfd8HVxN+hhV1nQpPOaFegiBAHMeVwCaqBH47bKvPeyRlgiR5oeufJA1QPsKfRevFDJuVA2c6gGtbcGdDzA2jd21rLIOrfPOpVfZmbzLA0hlj61qgHhH6CmpInfcJf5IRxR3x7zAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before&quot;
        title=&quot;Before&quot;
        src=&quot;/blog/static/c298f1017b36e2595b16b92b46094967/8ff1e/before_search.png&quot;
        srcset=&quot;/blog/static/c298f1017b36e2595b16b92b46094967/9ec3c/before_search.png 200w,
/blog/static/c298f1017b36e2595b16b92b46094967/c7805/before_search.png 400w,
/blog/static/c298f1017b36e2595b16b92b46094967/8ff1e/before_search.png 800w,
/blog/static/c298f1017b36e2595b16b92b46094967/6ff5e/before_search.png 1200w,
/blog/static/c298f1017b36e2595b16b92b46094967/2f950/before_search.png 1600w,
/blog/static/c298f1017b36e2595b16b92b46094967/8b984/before_search.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Split search suggestions and search results&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/8b984/after_search.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABiUlEQVQoz31SyUoDQRCdv1YQBONBBTEYA/oBghBB1IMHURBzUDRecglm05iAyWSWni2Z/dnVYw+TiCkoeqlXr15XlxKGIUzTxGg0hKpOMNU06Lq+6IYOwzBy/xPnblmWcCWOYwSc9Pr8GMNBH47rgookSQKKkdm2jW63C8YsfpeImPQ0TREEgSCjs8IYE4m6NoXnuSLo+74AEoCMcQWdZgOTqY65H0IaYcioWLVaFUJyQsYrOI6TqyJwFEVi3xsMcF9/FK0hJa7rCdVUmIwKExnlKJREFwT2PE9cSo/CTM3n6wtuyof4nqggAYSXeUUThHJDlWVFSRj/KjSab3g+KcO0Hcxns1x5ESv7qcg+LPcl22cK2t0GapcHGI/H0PgU0HOLrSkSK8XDclA+6eujj4uzU/5xGdkyboHwv2BxVTUH9acWmJnNG5HSNNDTqc9RFIpPoVXBCkulwn4PD3e3nIwJolW2klA++b3VwFXtCB4fFzFGnTYq5T3s75ZQ2lzDNvedrQ1USuv4AdCiSMmMqo/bAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After&quot;
        title=&quot;After&quot;
        src=&quot;/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/8ff1e/after_search.png&quot;
        srcset=&quot;/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/9ec3c/after_search.png 200w,
/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/c7805/after_search.png 400w,
/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/8ff1e/after_search.png 800w,
/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/6ff5e/after_search.png 1200w,
/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/2f950/after_search.png 1600w,
/blog/static/ed5a8acfc859406cbe248bb2dca3e1d4/8b984/after_search.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Code Implementation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Code Implementation for Split Search Screen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In order to implement our split-screen search feature, we use &lt;code class=&quot;language-text&quot;&gt;ConstraintLayout&lt;/code&gt;. This offers the ability to create layouts based on percentages, which we use to divide the screen between search suggestions and top results.&lt;/p&gt;
&lt;p&gt;To split the screen, we defined a width constraint of 40% for the search suggestions, leaving the remaining 60% for the top results:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;app:layout_constraintWidth_default=&quot;percent&quot;
app:layout_constraintWidth_percent=&quot;.4&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here is the full code for the side-by-side layout:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FrameLayout&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@+id/search_container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintWidth_default&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintWidth_percent&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;.4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintBottom_toBottomOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintStart_toStartOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintTop_toTopOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;androidx.fragment.app.FragmentContainerView&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@+id/search_suggestions&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;FrameLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;androidx.recyclerview.widget.RecyclerView&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@+id/section_results_top_items&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wrap_content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintBottom_toBottomOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintEnd_toEndOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintStart_toEndOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@id/search_container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;app:&lt;/span&gt;layout_constraintTop_toTopOf&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This layout allocates 40% of the screen width to a &lt;code class=&quot;language-text&quot;&gt;FrameLayout&lt;/code&gt; which houses the search suggestions, while the remaining space is assigned to a &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt; for displaying top results. The constraints ensure the proper positioning and responsiveness of these elements, providing a dynamic, user-friendly search interface.&lt;/p&gt;
&lt;h2&gt;Artist&lt;/h2&gt;
&lt;p&gt;Our previous design for the Artist screen wasn’t well-optimized for larger screens. The result was a horizontally stretched layout with excessive padding and white space around the artist details. As a consequence, users had to scroll vertically to access tracks or albums from the artist.&lt;/p&gt;
&lt;p&gt;Before: ❌ &lt;strong&gt;Stretched artist profile&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/da9926b27e5fefb202eeea7723d2dc2a/8b984/before_artist.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACZElEQVQoz4WRXUiTURjH3838YNEyh1oXhhgSVhp9oHVT0IdQN0EpBCZI2icpSaFEQR+YXtRNXgjqNtEwsIuuukgECQVbFNRFgVko6iZrm+4j37m9e9/319mcuAupP/zP8z/POc//nPMcyV97hQtlzUjHh7ldXI+caQKDxIJpB9tLB0mvHyd7MMS5yl6QJJQsEyXlw1RUjTFbc5Xagnakg06MRZ+peuJC0vfsY+D0US42PqPsyEsq97fQVVLNojmfzoIzVJTc4tTuRkZy90LWJqYsebzbWYFWWMTH6lYspaNsOfyVjOIv1LULQ4IhOlqb6LzfRDAQYc6vMB2E0NIyeFzgjXNe6AV0n4/wog/cTvB5+CNreJai+PwRfvuihGRVGApMz8zwY+onTpeTSCTKGrRk1JNaT8lrbAxJ1zTUWAxVVVGUGLqo0sWga3FqybiqScy1dR3fJ6iqGhMTE8hyWBiKxGIgyFIgQDgcJqZEUeOHiLwW35watcRpiZuumZHUDocjUS9pojjo9KB4RE+Wwyj8G/qqw/o8RSeeLMdUbGEFe0Tl0/gws+Nv+OZ0MeqWmfTLjHkj/Jr38t4dYnLGjb6ygiLaExNtSmU0GhUtU1Y/5carNq4PddDf3YD1+QHuDFgxDc1xc3KF8hEn5z942fX6O4feTvM/SNbuLtpOFHCvPIfmmlJaLuVxtr4BY91DTj56QXFTG8fuPiXn8gO2XXtMj81Of18fdrsNu82GzWalt6cHq9WaoCTAZks+mVtzSDNmkGbIIjfbTKHFTIZYM6cbMRklstMNWDLT4gUYBQ2C0gb8CymgqlhbgO+AAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before&quot;
        title=&quot;Before&quot;
        src=&quot;/blog/static/da9926b27e5fefb202eeea7723d2dc2a/8ff1e/before_artist.png&quot;
        srcset=&quot;/blog/static/da9926b27e5fefb202eeea7723d2dc2a/9ec3c/before_artist.png 200w,
/blog/static/da9926b27e5fefb202eeea7723d2dc2a/c7805/before_artist.png 400w,
/blog/static/da9926b27e5fefb202eeea7723d2dc2a/8ff1e/before_artist.png 800w,
/blog/static/da9926b27e5fefb202eeea7723d2dc2a/6ff5e/before_artist.png 1200w,
/blog/static/da9926b27e5fefb202eeea7723d2dc2a/2f950/before_artist.png 1600w,
/blog/static/da9926b27e5fefb202eeea7723d2dc2a/8b984/before_artist.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b3626ab90587e09b1591b266cab88e68/8b984/before_artist_1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABMUlEQVQoz62Ry0rDQBSGpwWlVvQRfYE+glsXWi+LQgm5LsWl++5cCoILQWpURBcx5EJCJiGZNPmdGWqIFTWKBz5O5sw/f86ZIUVRwPM8OI4jyfMcIuq6lnSJto5UVYUwjDihNGOMoSzLRvSdafun72dIXS/weHOBl+cnvLou4jgGpfTXhqIxaZgXGcZ7u3iw75Fm2QejLiOv6gnLGU6Ox5jfzUETKkdum/1k+smwWpS4urxGFMWy7a8uu/PIjJU4n83g+h7vMEHC+csdNo+S0hT7kwlubRuB70vD9mt3RegFRLgbZ6dwgwD/EURTNeyMRjg4OoQyncKyTBi6Dl3TYBo86yIbsEyzQe7zutBoqgpFUaDyLCA8sL05wFq/JxaS4WAdW8MN+d1b1gT9ZW7XVnkD4wkKwRX5DTQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before Page 2&quot;
        title=&quot;Before Page 2&quot;
        src=&quot;/blog/static/b3626ab90587e09b1591b266cab88e68/8ff1e/before_artist_1.png&quot;
        srcset=&quot;/blog/static/b3626ab90587e09b1591b266cab88e68/9ec3c/before_artist_1.png 200w,
/blog/static/b3626ab90587e09b1591b266cab88e68/c7805/before_artist_1.png 400w,
/blog/static/b3626ab90587e09b1591b266cab88e68/8ff1e/before_artist_1.png 800w,
/blog/static/b3626ab90587e09b1591b266cab88e68/6ff5e/before_artist_1.png 1200w,
/blog/static/b3626ab90587e09b1591b266cab88e68/2f950/before_artist_1.png 1600w,
/blog/static/b3626ab90587e09b1591b266cab88e68/8b984/before_artist_1.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To improve this, we took advantage of the space available on larger screens. We moved the Artist details to the left side of the screen and displayed them more compactly. This makes all the necessary information immediately visible and reduces unnecessary scrolling.&lt;/p&gt;
&lt;p&gt;Simultaneously, the right side of the screen is dedicated to displaying the artist’s tracks and albums. This clear separation of content means that users can quickly view and interact with the artist’s music. Through this design, we’ve been able to optimize space and present a well-structured layout that enhances the navigation and interaction for our users on large screen devices.&lt;/p&gt;
&lt;p&gt;After: ✅ &lt;strong&gt;Compact artist profile&lt;/strong&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/8b984/after_artist.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.2109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACX0lEQVQoz21RS2gTYRBeLy0iVsHeFCRErR4qigh68aC3+jxFhB68iQqiWKiiqTQV7EE9qAQ9iLV4qlo9NGA0JcVqIy2a0KKk9dkmTcxjs9nsbpJ9fs7+2Q0UHJj9Z+ef//u+meGKZRHC1Rs41XEe3P4xBDwnYba0Aqs4jLYfpFwY6wfn0H4nixcbDgEcB7/3MlqP/sTE6eu466V3uxbBeT7jhD8NTlI1oOsw3nZsQdeFx+jcN4TjnZcwvO0Y0m0bcc7bjQPbz+Kix4f62nXQ2tbgzebdyGzaAfGIDzv3hrB6TwItW+Po7idATSNAWcb9vh7cPONDPssjowC/yhbkkgTkUkCevJCGmlrC8vx31JeXKJ+BIlaRE3TwQg3ZfA0V2QBXrVZh29zsLKKRdygWC6gqjZxtJrnlnJqpo8T/RYHGVKhIkBQZpqE6lRb7MoWmYYDneUiyQoBF1FUdlmXBMgmGTsPQWfF0LIznw0EIggiFurLvTJPcOe03nKtEkiTI5AaBMz6rwZhMJhEKhVgcHR9HMPgAFVJnd8ZInTo35mypdsDzJaZOFIldUYjRZIX1ep0AKixeSC7gfXSScjWWd80Fayq0SG62pIAvV2hGfLPYZXctm8vhx+IfGpEJVVWh63qT2I4bgM6j0W8zmPm9gEwqBcVZlMuqO2OY+BDDw2dPUSgUkSNwQShRNzKbsWYT0D6aM4w86UFi8hUEUSY2bYVCw2io+Dgdx9DL183//xlnORsPRx5h/usULafK2lkJ2FA49SmGkbERJOJfcHswwDzg78VAXy9uBa7h3sAV/AOwuf3TsPl18gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After&quot;
        title=&quot;After&quot;
        src=&quot;/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/8ff1e/after_artist.png&quot;
        srcset=&quot;/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/9ec3c/after_artist.png 200w,
/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/c7805/after_artist.png 400w,
/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/8ff1e/after_artist.png 800w,
/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/6ff5e/after_artist.png 1200w,
/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/2f950/after_artist.png 1600w,
/blog/static/b752ae17a90a4cfe30dba209d5ad8ed7/8b984/after_artist.png 2560w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Keyboard Tab Navigation&lt;/h2&gt;
&lt;p&gt;We needed to support keyboard tab navigation for devices that have keyboards attached. You can easily test your apps by connecting a bluetooth keyboard.&lt;/p&gt;
&lt;p&gt;If you are using &lt;code class=&quot;language-text&quot;&gt;AppBarLayout&lt;/code&gt; you might end up not navigating through the toolbar icons.
We had to set &lt;code class=&quot;language-text&quot;&gt;touchscreenBlocksFocus = false&lt;/code&gt; to our &lt;code class=&quot;language-text&quot;&gt;AppBarLayout&lt;/code&gt; in order to allow navigating through the toolbar icons.&lt;/p&gt;
&lt;p&gt;At first, we had problems navigating the player using a keyboard. The default navigation did not work correctly so we had to force set the navigation flow.
To do this we use the &lt;a href=&quot;https://developer.android.com/develop/ui/views/touch-and-input/keyboard-input/navigation&quot;&gt;xml attribute&lt;/a&gt; &lt;code class=&quot;language-text&quot;&gt;android:nextFocusForward&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;For better understanding, here’s the specified order for tab key navigation in the player:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, the focus lands on the artist’s name.&lt;/li&gt;
&lt;li&gt;Then, it moves to the track name.&lt;/li&gt;
&lt;li&gt;Next, it goes to the option to minimize the player.&lt;/li&gt;
&lt;li&gt;After that, it directs to the ‘Follow artist’ option.&lt;/li&gt;
&lt;li&gt;It subsequently moves through options like ‘Like’, ‘Comments’, ‘Share’, ‘Playqueue’, and ‘More’ buttons.&lt;/li&gt;
&lt;li&gt;Lastly, it settles on the ‘Play/Pause’ button.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Space bar toggles playback - Only for media playback apps&lt;/h2&gt;
&lt;p&gt;One of the requirements from Google is to support playback toggling through the space bar key. This feature allows users to play or pause music just by hitting the space bar on their keyboards, irrespective of their current position in the app.&lt;/p&gt;
&lt;p&gt;The implementation of this functionality can be achieved by overriding the &lt;code class=&quot;language-text&quot;&gt;onKeyUp&lt;/code&gt; method. On detection of a key press, it verifies if the pressed key corresponds to the space bar, and if so it toggles the playback and returns &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In all the &lt;code class=&quot;language-text&quot;&gt;Activity&lt;/code&gt; classes where the play/pause button is present, the &lt;code class=&quot;language-text&quot;&gt;onKeyUp&lt;/code&gt; method is overridden as shown below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyCode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KeyEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;takeKeyEvents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyCode &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; KeyEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;KEYCODE_SPACE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onKeyUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyCode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s worth noting that we used the &lt;code class=&quot;language-text&quot;&gt;takeKeyEvents(true)&lt;/code&gt; method to ensure that the current activity can handle key events.&lt;/p&gt;
&lt;p&gt;Finally, to make sure the play/pause button is the default focus, we set &lt;code class=&quot;language-text&quot;&gt;android:focusedByDefault=&amp;quot;true&amp;quot;&lt;/code&gt; on the button. This is done to prevent other views from gaining focus by default, which could intercept the space bar key press event.&lt;/p&gt;
&lt;h2&gt;Testing on Emulators and Devices&lt;/h2&gt;
&lt;p&gt;When it comes to testing on large screens, we found the Desktop emulator to be the most suitable option. The key benefit is its ability to swiftly resize the app window, mirroring the actions performed on a typical device. This allows us to test responsive behavior on different devices like phones, tablets, and desktops.&lt;/p&gt;
&lt;p&gt;Google also offers support for emulators designed for foldable devices. These emulators can be useful for preliminary testing. However, we mostly relied on a physical device, the Samsung Fold, to conduct tests for foldable devices, as it proved to be more accurate.&lt;/p&gt;
&lt;p&gt;Lastly, to ensure our app supports hover states and keyboard operations efficiently, we used the Samsung S7+ equipped with an S Pen and Keyboard Bundle. This allowed us to simulate an environment similar to a laptop or a tablet with a keyboard.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Our journey to optimizing for large screens was filled with exciting challenges and insightful discoveries. We realized that providing a seamless experience across a variety of devices - from smartphones to tablets, desktops, TVs, foldables and more - was more than just stretching our existing user interface. It was about reimagining and reconfiguring every element and interaction to deliver a superior music streaming experience.&lt;/p&gt;
&lt;p&gt;In addressing the various UI and UX challenges, we found that each element of our service had the potential to be adapted and improved. We reworked our Player and Search screen, redesigned the Artist screen layout, and made sure that keyboard navigation and hover states were smoothly implemented. This process of continuous improvement reinforced to us how important it is to stay flexible in the face of evolving trends.&lt;/p&gt;
&lt;p&gt;As technology continues to change and expand, we are committed to staying ahead, making sure that SoundCloud remains the leading choice for music streaming, no matter the device.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[An update on our API: Changes to access for inactive apps]]></title><description><![CDATA[We’re working on improving our API offering. In order to do that, a few changes are coming: We need to revoke access for some of you with…]]></description><link>https://developers.soundcloud.com/blog/an-empty-blogpost</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/an-empty-blogpost</guid><pubDate>Fri, 02 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re working on improving our API offering. In order to do that, a few changes are coming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to revoke access for some of you with &lt;strong&gt;inactive apps&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We’ve reached out directly to those who will be affected via email and we’re working to mitigate impact where we can.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need to do this for two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recently, we haven’t been able to provide developers with the level of support that you deserve. That includes being able to help you when things go wrong and supporting you when you build amazing things.&lt;/li&gt;
&lt;li&gt;More pressingly, we haven’t been able to sustain the level of oversight needed to make sure our bad actors aren’t abusing our API. After analyzing trends in abusive API calls, we’ve determined that change is necessary in order to protect the artists and fans that use SoundCloud’s services.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Developers have been and will continue to be an important part of the SoundCloud ecosystem. Rest assured, this isn’t the final state of our API access – just a temporary reset so that we can implement additional security mechanisms. We’ll also be using this opportunity to redesign our API program so that there’s more internal support and better optimization for individual developers.&lt;/p&gt;
&lt;p&gt;In the meantime, we’re working hard to reopen the doors as soon as possible for developers that want to use our API. Be sure to keep an eye on this blog and the &lt;a href=&quot;https://twitter.com/SoundCloudDev&quot;&gt;SoundCloud Developer Twitter Account&lt;/a&gt; for updates to come.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[What I Learned in One Year as an SRE Trainee]]></title><description><![CDATA[I recently celebrated my one year anniversary as a Site Reliability Engineering (SRE) trainee at SoundCloud. Looking back, I had very little…]]></description><link>https://developers.soundcloud.com/blog/sre-trainee</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/sre-trainee</guid><pubDate>Fri, 06 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently celebrated my one year anniversary as a Site Reliability Engineering (SRE) trainee at SoundCloud. Looking back, I had very little idea of what I was getting into. I studied politics, and had done various teaching and translating jobs over the years before deciding to learn programming. Now I had a coding bootcamp under my belt, but no SRE experience. That said, I was drawn to the idea of troubleshooting complex systems and automating away repetitive tasks. I was also incredibly excited to work at SoundCloud, the world’s largest music and audio platform that lets people discover the greatest selection of music from the most diverse creator community on earth.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Site_reliability_engineering&quot;&gt;SRE&lt;/a&gt; is an approach that uses software engineering concepts to solve operations problems. Its principles work to align the goals of development and operations to create reliable and scalable systems.&lt;/p&gt;
&lt;p&gt;My team, Production Engineering, is responsible for ensuring the availability of SoundCloud. We make sure that systems are resilient and running smoothly, whether by taking care of it ourselves or enabling other teams to own what they build.&lt;/p&gt;
&lt;p&gt;In this post, I want to share my experiences as a new SRE. I’ll reflect on the lessons I’ve learned and challenges I’ve faced over the past year. I hope to give you an insight into the role of an SRE, and perhaps even inspire others to consider a career in this field.&lt;/p&gt;
&lt;h2&gt;The scope is huge&lt;/h2&gt;
&lt;p&gt;The first thing I noticed was the scope of the work that we do. Everything from &lt;a href=&quot;https://www.redhat.com/en/topics/automation/what-is-infrastructure-as-code-iac&quot;&gt;Infrastructure as Code&lt;/a&gt; to Monitoring and Incident Response training potentially falls under the SRE umbrella. This has been overwhelming at times. However I remind myself that I’m not going to learn everything in one day, and take it one step at a time.&lt;/p&gt;
&lt;p&gt;On the other hand, this huge scope is also why I love it. There’s definitely no shortage of things to learn. Some things I’ve worked on so far include upgrading Kubernetes and decommissioning part of our infrastructure. I’m currently in the process of migrating a service from our data center to Google Cloud. This is part of the exploratory phase of a bigger migration, and what I learn from it will benefit other teams when the time comes for them to do the same.&lt;/p&gt;
&lt;p&gt;I’ve also taken on the role of First Responder. Supporting other teams is a big part of our job, and the First Responder is the dedicated person for this. It means it’s clear who to ask for help, and the rest of our team can work without interruptions. Part of the role is also looking into the alerts our team gets. This can be daunting because it involves getting to know the nitty gritty details of each specific system. However it’s no coincidence this is also when I learn the most.&lt;/p&gt;
&lt;p&gt;On the cultural side of things, I lead the SRE Collective. This is a space where we get together to talk about SRE topics and share knowledge. The format is flexible, and leading means anything from facilitating a discussion to inviting someone to give a presentation.&lt;/p&gt;
&lt;p&gt;If this year has been about getting a high level overview, I’m looking forward to diving deeper into some topics in the year to come.&lt;/p&gt;
&lt;h2&gt;Systems fail all the time&lt;/h2&gt;
&lt;p&gt;Another thing that stood out to me is the way we view failure at SoundCloud. Failure is &lt;a href=&quot;https://how.complexsystems.fail/&quot;&gt;inherent in complex systems&lt;/a&gt;, what matters is how we deal with it. At SoundCloud we practice &lt;a href=&quot;https://postmortems.pagerduty.com/culture/blameless/&quot;&gt;blame aware&lt;/a&gt; postmortems. This means we carry out incident reviews with a focus on learning rather than pointing fingers. When failure is viewed as something normal, not as something to be swept under the rug, then we can examine what happened, learn from it, and make our systems stronger.&lt;/p&gt;
&lt;p&gt;This outlook has also challenged how I think about cause and effect. Often in life we look for the root cause of something. However there is rarely (if ever) one root cause, but rather a series of contributing factors. One of the most interesting yet challenging aspects of my traineeship has been understanding how these factors interact to result in the functioning or failure of a system.&lt;/p&gt;
&lt;h2&gt;Kubernetes is not so scary&lt;/h2&gt;
&lt;p&gt;Perhaps the most intimidating technology I’ve had to learn this year is &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;. I’m not sure if it was its reputation for being complex, or the fact that it comes with a whole new vocabulary (pods, replicaSets, etc.), but I put off learning it for a while before finally diving in. And it’s true - it &lt;em&gt;is&lt;/em&gt; complex and it took some time to get my head around it. However the process of learning it taught me something that can be applied to any technology.&lt;/p&gt;
&lt;p&gt;Basically, it’s essential to understand why that technology exists in the first place. What problem does it solve? In the case of Kubernetes, it solves the problem of having a running application tied to physical infrastructure. This is a problem because if a server crashes then the application goes down with it. While it’s true there’s more that can be said about Kubernetes, understanding this made it click for me and from that point on it became a lot less confusing. I still have a lot more to learn, but knowing its basic purpose makes new concepts fall into place easily.&lt;/p&gt;
&lt;h2&gt;It’s all about asking questions&lt;/h2&gt;
&lt;p&gt;There have been so many new concepts to grasp and technologies to learn, and asking questions has been key to improving my understanding. It’s true that reading and googling have their part, but asking questions and chatting with my teammates is when it really comes together.&lt;/p&gt;
&lt;p&gt;I’m also lucky enough to be working with some incredibly smart and talented people who are more than happy to share their knowledge with me. It’s an opportunity that’s not to be missed!&lt;/p&gt;
&lt;p&gt;As a team, we decided to set aside a dedicated time for questions, and we now have a &lt;em&gt;‘Coffee and Q&amp;#x26;A’&lt;/em&gt; session twice a week. It started with me as a trainee in mind, but quickly turned into an informal knowledge sharing space for the whole team. It’s a time where anyone can bring questions that have come up during the day, or ask things that are outside the scope of their daily tasks. It’s also a great team bonding moment.&lt;/p&gt;
&lt;h2&gt;The journey continues&lt;/h2&gt;
&lt;p&gt;When I look back to when I started it’s hard for me to believe how far I’ve come. I’ve improved my technical skills, worked on some very exciting projects, and learned a lot about SRE culture along the way. However all this was only possible because of the support I got from my colleagues. Whether as pair programming sessions or informal chats, the time they have given me and the kindness and enthusiasm they have shown me has been astounding.&lt;/p&gt;
&lt;p&gt;I’m also happy to share that I’ve accepted a permanent position on the team, and I’m excited to continue my journey as an SRE at SoundCloud.&lt;/p&gt;
&lt;p&gt;Look out for my next post, where I’ll share my experiences on migrating to the cloud.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[What's New with SoundCloud, November 2022]]></title><description><![CDATA[We recently made updates to our SoundCloud iOS and Android apps to address key user experience (UX) fixes according to user feedback. We’ll…]]></description><link>https://developers.soundcloud.com/blog/new-with-soundcloud-november-22</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new-with-soundcloud-november-22</guid><pubDate>Fri, 11 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We recently made updates to our SoundCloud iOS and Android apps to address key user experience (UX) fixes according to user feedback.&lt;/p&gt;
&lt;p&gt;We’ll be updating this blog regularly as we actively work to improve the SoundCloud experience for our users, and we’re so excited to share these improvements with everyone.&lt;/p&gt;
&lt;p&gt;Here are our most recent updates across iOS and Android as of 11 November 2022.&lt;/p&gt;
&lt;h2&gt;New Creator Subscription Names (Android, iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/f83b7065fef10daa1c46730436a0ac34/c7805/new-creator-subscription-names.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 108.74999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFRElEQVQ4y2WU51NUVxjG7+7SlyIICiggbdmlrDQRZFlY2kKQYkJZQAxYUBGMiYBSZaJIAmOhLIIgiBpRgcHEFmOSST44OrFnJk7MlyT/Qf6DX85ehcjkwzPvOec+523Pe4/0dI+VF43neSbs0x1nebFzhGe1QwKDMmxnT2rP8mzvKM/3j4n9IM/F91e7xb5umBd1Q/Lexn2ycwjpceckv1iv8XPvJA+6R5nd28e91rPcbh3mVssw9zrH+KFxiMfWr3g4v8j9sYvM9Y8yeKCHhb4RLh+3Mts7xrX+CR4cGkG6PTjOpa+n+fHhbW7cuUpLx1HauzsZnBjn1Ogopy9OstArOH0DTC1e4NGr77ly8xLNnUfoPt6Ddfo8ZwT3zMwkt9qsSLcGRunobePqjXHufnuduYUrTI6c4t7CDFMzFxifnebOwBQ3+gdpO9mCdeJL5hcvMTd3mWuTQ9ydv8jE9ATT12f4rmscafLICQrrSsgtyaLhaCNNTY007K9lR2U5DTs/pqOzmZmmHlqq6zEUpZNTkMGBliby8/Iw52WRsSWFnR9tY1ddDbN1XUjVCWaS0+KxHt1HtyC6uKiRJEnGWpWSUE81jfpMtiVms7e6gMs9BzlYV7XMsUHn6oKfqxNnMmuQymMyyEoIZ2BPGUZtsExQKhUoFArslEp5vz3GRIE2mfxNEewyG/B2cUQS31Xiu1LATqxtvGMZ25F2JJoJCg9Fp9UREhaOSqVajqx657AyKoPq5DzC9dGEaSJknuK9DBXvO6zamMmqtd74eHvj7uaGWq3GyclJxtKFCp2Rkpg03LxX4eHuLnPUalfRHpcVvC6jaIVFlCM5qHC0d5DTt7e3l2Fba8LD6R88TYu5GnNoApLSlrVqOSvluwqKCou4OneNvrzdbzOU7JU4CCd2dnZyyUs2QqPh2MnjfJZdSW5IPJJKwtnREXsHhxWBN+r1lFVV0GMSouwQKvsGBRAdFU1kZCThIiudTodGONNGRGDv6owl0ki5rde6KKKiYuRzrVZLhLA2ni2wyslBlFyJVKHPRunmjZu7t6yu8p1ytpLsVXZySdWiLYXRJhy9fPHw8EKpeDsFS2IoFcr/RKnUm1B7uhMRrsFRlPO+craLtr7Vxpsp1CXj5O5MwPr1K2Zw2aHgySXXJOTi7uPFOj9/AgMDCQzagNdqnxUXioM3URpnwme9H+v8/QkJCUEv+ubp6bWC12GoQNoel02wNgyjwUBAQADmrC3kZ20mJiIATWgwnce7uXT4C/I1SeKPSiHdkEaQCFxaZKK0IJWE6CBys7OYnZ/jfvs5pLrNBZiMKVjMmSTExWNI0mHtrKGroZjtFQX8/Q/cPvWSqqR0Psg0UJ6biS4yipLcRGZ6d1FvyaSnq5lHr+GbtvtvHaYLhxX5OcTE6LEUprI4+AknD1dQW1XEqz+Fw4FHfKhPxpxjosScw4bgYNr2FnL9VCOt9Vtpbz3Eb3/BTyfuiMdhYxahWg1pqQb8/daRkRRNTXEmZQVGUhNjGR4f4V7fefLCNqGNiSIxPgHfNWvJ2RJLgSkFszGRpLhYPj/Rzc1D/aKHYr68g/wJCglmlc9qHFzVeKzxwdnDQwyyGCEPZyrFa1O2OQePQF/8NgSgUjuiEi+My2pPJDF/kkKI4qSi3TaHU8WHaUuz0JlWRXuqRaBCqGWhQ6w7DJW0pJRyYeun9Jl3Ux2bjUVrpCoyQ0alLl22NdEioNbAxeJmpJf1o7zeNyFwntf7J/6H3xsmeVV/jl/rx3jTcIE/DkzJ9s0B2/qtXTp/sWeUfwHqNzi0rVvi5wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;New creator subscription names&quot;
        title=&quot;New creator subscription names&quot;
        src=&quot;/blog/static/f83b7065fef10daa1c46730436a0ac34/c7805/new-creator-subscription-names.png&quot;
        srcset=&quot;/blog/static/f83b7065fef10daa1c46730436a0ac34/9ec3c/new-creator-subscription-names.png 200w,
/blog/static/f83b7065fef10daa1c46730436a0ac34/c7805/new-creator-subscription-names.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;With the rollout of our Next, Next Pro, and Next Plus subscription names as part of our SoundCloud for Artists evolution, we’ve replaced old creator tiers on profiles to align with these new plan names, resulting in a more streamlined experience. This will be available to all users.&lt;/p&gt;
&lt;h2&gt;Sheet for Playlist/Album Descriptions (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1e2cd82b4bc8e3c778f16ffeeea86ba6/c7805/playlist-album-description.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 112.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE90lEQVQ4y21VWW9bRRS+jmPHS7xf+17b17tjJ47XeEmTZnGWpk1xSdKkKWkrQdOQkLokXdRWglKJRRRQBapQH+AJIdFH+AU8sUmIqiAhQSn8lo9vbuK0ojwczdy5Z7455zvfmZF+ubiFH15dx09bG8/Zj5sb+H5jneMF/Ly9yfl5fS7sqc/e93fiH3Gka4dHkIpGkZR9CNhsUHp7dQvYbdDcbpSyGRwpl7CaH0Qxk0FclqE6xH87/Lq/HVGfF0MDA2jVqpDao6OQHC5IkvS/ZrRYMBCLYS6dgoFzC0EsNjsCqgolFILD5YKppwc2Hl5OpSB9cHIGV1bzuHupgVpWRUAJIRqJIMaoPXQSoDGvF8cyfQeHGAwGqIqCRDwOB7MRa90ErfJQ6cF2C19eHMfDb95H+/QRyIEQBvr7kUgk4HI6dWfF7sDacB6D5QGMHR5DtVqFz+d7LpswqZDun5rCp+0qvv5oGfO1DMzWXvSSP7fbBbPJpDs6uwzYGp/BxMQMpppNTE9NY3Z2FuVyGcFgEHFSIiKMOB2Cw0Mw2iyQPU7YSPSzJ3abzbj19jvY2djAwmABQ/VhFAsFqORPAMUIJDLJZrMwElBzEHBnZBhGbpa5IFstcBm7YOe3GL1mE+68dQs319exmMugUCqiUa/rQOl0GgOsrADuBBBlVtK7M01KI4tmdQgTQxWMFgsYr5T1cYwAlf4sDqkBrBXzCLMI/fQVEfWT53w+jyiL52PRpK4upkzOb09OQlEzyCZyiFBPIY9bHzWvRx9llxP1gIwzPCRJEFGwDqBI106aOmrYA5xr4fJL65grllEdzKFCIVe4oc65EG8XHdNMZZniDgo5MV1hyWQSKeouzqgFcGi/OaQ35uaxeOQc8tFBOLhRCLXHaoWZIu5wo5DsJVIQCIUR2i+GSFXTNB1QVNnPQukc3l5ZxJ17H2Ph5CL6SHSap4oNbtfT7lHZXmuNOsosSJMU5XI5vdICNMFIxR4709WEDj88vYrPP7uPFxZaCPgDiDAtE/UnuqEDGKTjKouWKxaRY2WdjNhms8JLnp3Unps8S0YjNKHDu61ZfLU1j+2ZKvrzRWR5ATiEnkSL7QOGeLpI2RtQEAxpenuGI3GEtRhCNC2SYEPY93R4fWIMN042sTlVhNXphoV6/G9LiQhXKhXIsQK0vhEosRK8ShK+YFof/eEsJJN1L+U3j7Zw6/Y9vHz2nE6yqF7PPqjhGQ5PVevwJ2sIZybhj1Vh92gH5pTj1KFpr/XeO7WGLx58i932DolWdED3vq6kg5QdOqCSbkDLNqEkG3AQxOGLESwBdyBNDju9PNyAgc2vUkOiekIGojACWOZl2olwpTIEd5jajNXgDedhd4dhcwX10Unggwjbw3X9UpApkxClYBOXKC8FOy9Sq6VH/5fy+ynsCryRQajxKmQC97qDcHrCcHhCcHk1HTAmqn2e/SsZuvY466GYKZlugpnFxdndDZPTBdXjwYulCjypBvx9k3BFa+j2JCBZAzDYVVi85FAy8gmxQvrk+DEc7UtjkZfAhXFR8SVcX1rE7vF5LOX4TrAVX6mUcIWCPlQYweFSEyeaCzh74gxW5xawPNPCaK6GnBrGdILAf+xewj9Xd3X76/Lr+HOnrdtjrv+9vy7G3y5t4yFfyId8/X6n35MbV/Hk5jU8vn4Fjy5u4lH7NfxKn38B3/WyvSJinXkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Playlist/album descriptions&quot;
        title=&quot;Playlist/album descriptions&quot;
        src=&quot;/blog/static/1e2cd82b4bc8e3c778f16ffeeea86ba6/c7805/playlist-album-description.png&quot;
        srcset=&quot;/blog/static/1e2cd82b4bc8e3c778f16ffeeea86ba6/9ec3c/playlist-album-description.png 200w,
/blog/static/1e2cd82b4bc8e3c778f16ffeeea86ba6/c7805/playlist-album-description.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We know that what artists and curators have to share about their music is important — so important that we’ve given playlist and album descriptions their own dedicated space with room for 4,000 characters.&lt;/p&gt;
&lt;p&gt;Previously, when expanded, descriptions would take up the entire screen. To accommodate both artist expression and ease of platform use, we separated these two elements.&lt;/p&gt;
&lt;h2&gt;Reduced Carousel Sizes (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e5579d133dc1ee4c410c63b162302853/c7805/reduced-carousel-sizes.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 110.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFnUlEQVQ4y1WUWVBb5xXHL0hggVaQ0IYWtADahYyEEEjISGCBcTCyxRIBdvBCsI1jFseBOLGZJNhxvI/jpnXTCXHtNmmnmUw6k07TvrR97DJ9SF/ayUxnOombpnX60NfOrx9Kmkke/nPP951zz73nf87/SKduf8iJ67/jxI3fc1w8v46Fa7+tYOm1P/PUq38SMV/6rn095ovz4s0/sPztvyD5kvvQN5mQK9TIauqo2VFfgby2jgaDmRank5Gj14lmpzEYmqhVaASUFf923Latb7LgcrkYffI2Uld3iazJwR6Xg3pZFZIkIauuQq9vpKmpCY2ylvzsJvn8MfIWJwWnrRJjNpvRqNWo1SoRZ0SpkDNy5DpSsn+KvsROwq3eSuD/UV0to7ZWUbHz05ukCk/Qam/C73HjsNtpaXHh9XhQa9RUffnOwKErSNF0EaezGbPNjkqjRa3VoVRtly//Knnf5IZIeIyQzUt/Ms7JyQKjuR4ury/wWG6MuclNXA4rT73wNpJfcGjQ6egM+inmU+SSMY6Wi/TEEqRjYxg0SqaW7jA8fJxyNIPHasXjthD1udk/1EtXrJepuQu43Q6OrN9D6hVkz6ceo9cfYCATYaA7yoXTMxQL+zm7/haxjjAnn79HJDuFRrODerUGo8mMrqEBk9mCJPiuqvqikuG5q0iR1D7abBa0olSjIFqlUmG1mKndoUBvMiIXwYUnXmGgeJxkZ4hMuodoOEQ4FMLn86Oqr0NrduJNDzEhfkCK56awic45HE7aRYDb4yUQCFCnUHzFYfLABVaXN/jowXk+ff9Virvi6O1O9pUnsNhsHD69yq8++iunrv4cqTAyze2lSbY2V9jaOEG2K4LRYsXd5sfZ2k4i5mfm2QeUDp7h/qXTvPPaS6R2RmkJR1k8t4TT7WYgn+f1Ozc5d+sDpMnpk/ztJy/z39+8waNfvk55bw5bMMLFuzfp3zNEPhPnzNX36OgvEwz7SKbTOMSwWywWPN5WUZGHBl0DFp2G0slvIXXvnmF2fIQX1k7x5NGD5DLdWFwexg5P0y5KTyfCzK7eJTV0GIfVIPi1igQ6MYctBIXfJkpuFiMXiQR5fPV7SJ3ZEkXxV7PT48wfLrOrN0FMOEcLA4wMDTKYjrFn7jKJwUMikRpdowG73YHX+00hbGNw5hJSon+SSKAVX1srXneLUEgV8c6d9KaSopthaquFAmYuMnzguJjTLqb3FYiF/EItDrQNejQqJa5wnN3zZzj04k+FUvpKaET9Km0jWn0TdlFKlUz2jS8nis/x9NlNHr57mf/8+k0O5LsxtfpYWFvGKvicX3maP378CSs3foE0kJ9gKBGi2J+mlOulO+jDqtNiqK8XROto1tZRPHKVyclFXjlV5va506QC7WK0ghybnyUgmtIfj7O5vMj6y+8iTY0fEfO1waMPvsM/3r/D1GAPDm8bxXKZWE8vhWyC1SvvEctMYG82EgyGsFktWMQmclhtQsMOGrVa9EoF+xduIe0ZX+CdK8t8+OMb/OzuRcYKu2gOhrm0dYvsYI6+VAeLL/6I+MBBzMYG9GInqjUa0VmRzO3CaDRVJOj1tjCx9F2kDsFhdyrO3rFR8rsH6YxFMInRCHXGsTs9JDp8jB67RmbvPP42N7GdnQSCQbqSSTxCVVJVNTJ5DTIh0aFtLXujOZQ1EjWim6o6OYra6oqt1yqFLcPUWEd24jwdfeM0qOUY9Dp0YgM1aFXU7ZCjVddTr6jFpFdV9qbUV1rHHcnRHt9LZmSO3aUTDE8sksg9jjc2TEswy/jKAzIH1jC7YjjFuTs/SWZohuzIQSJiUzkDfdjakpRW7iOt3fuU9e9/VsFzP3jE8z/8vIJzD/7F2vb9/X/yzNZDzm59wjNv/r2C8299zsbb/67g2fufCd/Dyv3ZNz7mf+XPDhzsrx4UAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Reduced carousel sizes&quot;
        title=&quot;Reduced carousel sizes&quot;
        src=&quot;/blog/static/e5579d133dc1ee4c410c63b162302853/c7805/reduced-carousel-sizes.png&quot;
        srcset=&quot;/blog/static/e5579d133dc1ee4c410c63b162302853/9ec3c/reduced-carousel-sizes.png 200w,
/blog/static/e5579d133dc1ee4c410c63b162302853/c7805/reduced-carousel-sizes.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Following our initial reduction of track, playlist, and album cell sizes, we’ve now also reduced the size of carousels across the app to fit even more music and content you know and love.&lt;/p&gt;
&lt;h2&gt;Combined Share and Additional Options Icon (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/aa6c80eb5532dac073f092b599baa0d0/c7805/combined-options-icon.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 107.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFVUlEQVQ4y22V+0+TZxTHX4RyEwptsaUt9CLXIkIRKBbKTa6lAztpK1QQsYIITrwNFuYmi1GTuehEF00wys0NdTE6FxPHsrm4LUvGL1s0MSYyF7Mt+ys+e/qCzB/84eQ5z5vnPed7zvd7nkcKtc8y0DFPqH2Onq3TwmbY5Z1Z9tum2dk6xc62a+zxzTEYvEl/4Avhf07fa7a8F9/bryMNdM7w7r7rHN07z74d0wx2zzAQvMZQ9yxDO2c5FJrnQM8NerxX6O+8TFfbBP6mswSa/7fw3td4hs6WC0hHhy7zwYdjXLlylbm5OY4fP87Y2Bijo6PMzs7x3fcPuH/nNyqL9hMTq0CSpDfYGmJjo8nLrEXaHzrHpcsXuHjxIlNTU0xMTPDgwQMmJye5d+9rpqYnuXT+K7Y4DuIPeBk+MExObg5qtZq0NCMajXo1cEb6ZqTxUYFq/H38vgBdXV3cvn2bpaU/WFhYYE+oj/MTnzBx5kvqnUfwtDaLBDMicIDCggJMJpMImoZSmSTQR5FhciIdGviMI0eH2bKljvz8fB49esTLly9ZXFykt3c3nUEfBwfP0Fh+FIM2maA/gNfjxmTUo1BEk5iYSEJCgozQlFqMFAqext3SgMNRRmZmJocPH+bp06eMj49TUlxCKNTN6RNzeB2iVFUcmqgotOLn2Nd6qFKpuXptkqBvGGlw91kqXGXYbHnk5ORQU1PDsWPHKC0t5dy5T7lz9xb37/5OvX0QszoeQ1ISFlUyuuRk1gpkSqWStfFr8fnfptrpR9rmHqG4pBB7YZHcD4fDQWtrq7w+f77Ewrf3+ebeYyqKhkizWrFtsAtScrHlLQPIFb7RmCYjNaTYkfq7P5YRZmVlC8Y0lJeX09PTg06nY2RklL/+fsHPD//EWbAPvSBBpdETESERGRlJlCg/bHFx8XJAq9GB1BM4QVa2Fa1WJ8MP9zEYDMoBT548xcMfFrg5+yMu+xB9/bvo2tFFamqqXI1Wq5XPmUxmFNERywErS3aTmWXBbi+iuLhYPhgbG0t8fLxgUMmaKAm7zUNL1XvUVNpxN9bJ38PVxMXFERMTs0qO1ViGVFvWh0GvITo6Rg4U8YZJsOhddLae5tKYj6FONzpDOutSNKSkpAiGVavn1oeF3VzzDt3d24Vot9Lm9VLmdFJUUkJdQwO1Qpu19VVUODppco0Q2BGg3FUlt2bdunUrVSQKtClErBGJDaVIHc0jPFn8heePn/Dy2TN+EhNy7+YN/lla4vGvi/z74hlH9pyiXPSwzufHvrmSjPVWeUrCgg4jVKs1REatlNxe2os1QYFJZDInJmCMjUEl4GsEkylrIkkSfrOtmUbXe8QlpwqWDaSLPuv1epmUcNmvAlrCpNSX7saiVZEnNFYgGM4X2fMsZnLNJrQJiSgVEo4ctwg4SlKykoREDWazWQ6oUChk2YT7vzp6laUhnEKHuWJStEICRpHdJH6wiATGdDOR0RKZ5lramz4Sol4vKyF/Q74897k2myxus9lCWrqesk2tSG5BSnhsPJ5WysQ8h82Wa8NqzaBQ/JCmS2ZT/lY6PCeprHLS0NBEVWUVVVXVuFyVuN0tVFfXstlZRnPdTqQGVz+GNDUbN+aJccoSCHXoUjVk52SQk2HCaFAKlv3iPhxelUeY0Ve+JiUZlVpJlGhNtlVcX211xwRUDyUFb1FqFyg3teEoahPZeqgu76C4sBlP7WG8dafINFWQbanFYffiadgjW7Wzg2zRkoz0CqGEXnF9iYelzzdPn//G8rpivd7rhLaFH58bYr0uP2J7A7eE3ZTXge3C376yruz7/PP8BwQfF2UpSlVTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Combined icons&quot;
        title=&quot;Combined icons&quot;
        src=&quot;/blog/static/aa6c80eb5532dac073f092b599baa0d0/c7805/combined-options-icon.png&quot;
        srcset=&quot;/blog/static/aa6c80eb5532dac073f092b599baa0d0/9ec3c/combined-options-icon.png 200w,
/blog/static/aa6c80eb5532dac073f092b599baa0d0/c7805/combined-options-icon.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To streamline and simplify users’ experiences, reduce clutter, and prioritize key actions, we’ve added sharing to the “more” icon.&lt;/p&gt;
&lt;h2&gt;Bug Fix: Creator Profiles in Recently Played (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/37172c6b417fe601792af3b1991f45f2/c7805/bug-fix.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 108.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFaklEQVQ4y2WV+0+b5xXHX9s4YIK5lTs4ARwuKWBjMPiGbewEbIyxMYSQcPGFBIyBACFpPEq7OCxtrqSla5pWalX1h15WtWuzpNu0TZOWpV20ZZM2daqi/jGfPn6bsbX74avHOv4+5z3POd9zjpT5dI5Xf7/BlfuLZD6PceluQkbm8zgvfjrLxc9ibH40xUt3k9z4zYrMydyN7/Gy+OlnURmbH04jRTM+Bo7bGI71YQs9i2XkMNbgYZxjBvyzNjzHukm87Gd6cxDfjAtbuB3LcCvd/iZ6hlroCbTgHu+UsXI7guSa7ECSJAHF0/O/KNKWoJRyCSz24gybaCuu3/svR5W3d0erKRLQyh+Wzu/Ocf3mLnMrN1jbfIWtq++SztwhlrrCazvb/Hz3OkvXTuCNiWh7jLQ2VtBWX4XfbMTS1oLT3MhBfTnV1aWkXg0hZd5e5dqVXW7eeoPf/fERd//wDQ8e/ZOP73/NF79+xJMn/yb9xgJHF72YPA4sLhOp+SAnpwZILI4yGTHR0FRKbV0JZ7JP3rgVZ3xijrfeeZvH//gbX371FX96+BcePP6ahw+/5O+P/yo7dB430fzsIQ611dBlO8CJFR8T8146TbXUNZSjb65iKRvh2LILr8OF2+PB6/0e/Z5+LFYLVmsvhnYTE88dIZJwcKC8nP3a/Wg0+eRrNOwXZ1FxEQXaApRKBfM3hpEC89b/K8aPETpjwx7KFk+FtlBLQb6GfWo1CoUC5VNkeaevC4ehRQfdXV043H30uxyY9I1U7s+nRlysyMtFLZyMrjnxxay06pvobjxAn6mLw7qDFAgnz+SoKBRnqUJicUc8OXlphNc2oiQGLOycO8mMx8783Cl8R4/iNXdRUVTG5GaQ5GUvLnsNutp6bKOrTM0uU1JShpSjoabZgMPUy/ruONLSlTHejYe4fz7Av+5fIzU2zHQ0RofRiLm9HVunicvvnWE1PcCRrlqc/T7euvoyT97f5qjdQovZSyp9maXkOks3RpDimz6+uLPFkz/f4dsH75AY8uAZ9NMmHFo6jRiaWkjfThC/4Mdi1KOvLiO9MMt2coJnCvOxeoYIHo/S1dTMyq0w0vCCDa/dit9rx3BIR1+9DpvBINBBe3UF7Q1NxDNhBqdtHKyo40CemobiYg4WFlGpUtFeWY6huhJ9QT7JmyJCX6LnBxXNF8ktVikpFDLQCqilHCbOe+T+lVtOrdrjKn6kBlk2waSdmiodtbo6GhoaqW/UU1mn+wExuGxhcMZBp9FMa2szebn7yMvLQa2WhCbz2KdSy7zEtQBSONXHyICP5fko4WEf/Q4L8fERTg4P4rb3oFbmEjnrJjRpolTaR1ldA6XV9aLd+ulzx0Ugejp9bsZd41zYEa0XSwfYubjF6vIcv/3gNuuno9zJbPH682meX09RU1PDlBhdA2MmSso0VByqoLa+ieWFXa5evMdUJM7YpUE2Ake4+YqQzQnx5NXREFvLp0hPRVgIB/jZ2WUyaynWp45hrq0l9eIQwwt+OsSEcR6x4nLYOR2b42xyjYnRcSJLHgbcnZzbGUNaWOpjqknPKVs3kUYdoapiAuVaghVFhKtLGK8qZ+mcmxNrI4z4Q7icTrnPzb1muoTwLTYLxg4TNoeT1K5w6BQDVlfVKJKqlAemUlKjUuTKffufogwle3GEjGiU2j1blpe9o3jKyxH5lQds/CU/1pAY+RMdhBfczGwEZYycdso2e6RNyCHI5E88Ms89aeT4qo/oc6PELowyFLfTd6wD14SBtTdFDrc+nmH73hyXfpUQCykqFtOMjOzvrG1bYOsXwvaJ6I572aUkltcvZ3lBcF74RPD+Z7FtfjDNd7l4HTO+XNBwAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Bug fix&quot;
        title=&quot;Bug fix&quot;
        src=&quot;/blog/static/37172c6b417fe601792af3b1991f45f2/c7805/bug-fix.png&quot;
        srcset=&quot;/blog/static/37172c6b417fe601792af3b1991f45f2/9ec3c/bug-fix.png 200w,
/blog/static/37172c6b417fe601792af3b1991f45f2/c7805/bug-fix.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We fixed a bug that caused creator profiles to be displayed as playlists on Home and Library views.&lt;/p&gt;
&lt;h2&gt;Changed Background of Comments (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4664319d58ad9f61855b5422db9e746e/c7805/changed-background-color.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 111.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEzklEQVQ4y42U2VOaZxSHEa1xgWjcIApoBBRc2LcPBUOaqBFFJUoctSriVk1BMKup0WxqdBJr2un0pjedzvSi151mmtq0+ceevmB00kym7cVvvnfOnHm+c877/o6s7/ISTUYrOm0r59VG1GqDkPiqjBiNTsxmOyORNXovJTA1W9BpTJxX6VHXNIpvI7VqPU16K06Hn89im8giV8ZoMZRja9FiaVLjbNHhEmeroQabUUWk109iLIPT2os8X0ZR0RnOFBVSVqbk7FkFhYUFlJQUU1GpZKjvC2SJic8Jes8TdGvocqgZvNjCUKCZLpMSqb6A9MxV1pc38dgHkclk5OXJKS4upqG+Aa1WR35+AXJ5PgpFMdH+VWSp1COWkteZX55gITFIPNrBWI+V2KftTIU9dOjzGbsywlB4FXOrE0ny4fd34PP5xFkiGAwi+aTcz/q7lwXw9gtm7yVZ3tlheeMmCwvXmImKeYRdAtqG1ChjNjKD2xFBebYIj9uDy+XKyWg0UlOjQqVS54ADPSvIVu5/y+TTR2Refs3S2iLLyQkWRZvxIR/DXc04NAI4GMfnGubcuVI6O4MEAoFchSqVKgc6UaT3BrKJlW0Cc9PMP3nISCLG+Nwoi7dXmBwJEnJp8OjlTPRPMh5J0Wm34HZbaW9ro729nQsXLvwTmK1w6dYhsdtpEqLl8dUlxgR0eecJ8cwS3WKOkrmI6aFZerpG0aqVOF1u7HaHkE08KTPl5eVUV9fwSaH8GHhLtLz2bJfUd9+T2d8l+eAWq483SO9vk16docdTzfTwLKHOYerryokOhRkZvkpfXz8DAwOEw2G6u3tot7QwmG15JrVL/901Nn/8mY0ffmL98ID1/Sfc3H3IzfQU/R21zFxLYG8P59pqMTULI+gxGAzo9XrxdLTU1dWJCys5nuHi6jbXUgkyLw5yF5Pa2yO5u838vQyxaIBLzgriI3N4xC2/P6+P6XiGy/e4Pj9E+uAlqYND0off5LSydZ/44igBd5UAzuO2HwOzjzjvA1A2dgqcmbtDaOQysRtLjKZuMJpOiu8K0bkJhq53IzkriY9mgQOnTvmwspNYDhifzSD12OjoC+I/UY+EP2TFLxmFt0vfVfg/gdNTSbwhEx3dXnwhO97OVnxeg6hMi99Wi8NQIOyYOG35P4FTYjm4OxqQuqy4pSacdo3YNpV4msvwNZfjqJcJ18zmrJeb10eAcvl7wMnYLHZnFV7Rnsupw9FajdOowN1YjM+gwK4V1hPv0GnrfwfI+/dbnohOY20RgGx7bQJmqsBlVArLlQrgWRwCOCO8HJDGRHX5YnWVoChViB1YKlSSW2WKUiWyvHdeXpjIiEMEyabGZ1Fj0yuwaApw6AoFsAyv/gzjV8cZi97FYrsk/Kuj2dREQ0M9Op0m9zWbTdTWVYttI9ZXPLZC0GUg5BbWuRIiNtBPLNwnFm0Hl+1mutp0zI8mCQWmUCrPUV2lw9LuQfJeFK8gJFxjoapCg1JRSWzwDrLdjd/YXv+F7fu/srf5O88f/yX0lv2tI3ZELKvdjdfsfPmap+uvRN4rnj96w8HTt3wltLd5dBrP5sn2Nt+wt/Wn0BuePfhD6IhnG0e5czaW06Y4i7z9rWNlc3Y3jrUn8k7i2by/AUTS+h4yyVlmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Changed background of comments&quot;
        title=&quot;Changed background of comments&quot;
        src=&quot;/blog/static/4664319d58ad9f61855b5422db9e746e/c7805/changed-background-color.png&quot;
        srcset=&quot;/blog/static/4664319d58ad9f61855b5422db9e746e/9ec3c/changed-background-color.png 200w,
/blog/static/4664319d58ad9f61855b5422db9e746e/c7805/changed-background-color.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In an effort to create added consistency across our app, we removed the blurred overlay in the comment section.&lt;/p&gt;
&lt;h2&gt;Reduced Subtitles on Home (Android, iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/43aca0b41fd2cef93932114616e2afa0/c7805/reduced-subtitles.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 130.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGb0lEQVRIx3WVWVCb5xWGMQIJgYTQgpCQhFb21ZCAzW5ALMZIYhMCJIJZjdhsE3ZwiglOHAfb4KVNabNiG7tJPLUnF2kmM13Gk171rmmmvclFZ9q73vT+6SeRZJrp9J858y9zvvN/3/u+5z0xm4+Huf58hjeehbn6dJSt/4qffDrClV8Ns3H0CteeTbHz60nxfeR/cl775DybImfj0SvELL4fYGlvjItvDxHcdBNYO0P/egP9Gw0ErzQT3vOx9FEfl26GmLkeILB+RkRDNC8SwStuhrfbWHjXz8qDfmKm9j0kKhOIjY0lJiaGuLh4JJI45PIEUhQamvrLWRQFVRoFMqk0miONj0cmk0VDp9VitWUwudvB+uMgMdN3fUjiJSIxVtylGI1GtBoN8qSk6OKy1izWjoJIpJLoeySSFApUqmQS5HJkCYnExccxdqOd9Sei4MTtDrp8zfS0N9HVWkd+XgEFhWWYlVJMSiX+aTeX3++hsamacw21VJQWYknTodenkyKXkiqTYLObmL/Xw+qjAWLGb7bj723nXHM9VUWZOF3ZFJyspNCgwqnTE5huZu4XnYQvBAmfD9DfVkNRbjbV7m4KrSZOpWuprn6J1YN+lh+KgtP7XrTaFJRJCnHXkarTkpaaikJgGD1yi4tlAbbRlIZSqSIlRYVWp8NstaFJEesSZEhiY7hw03OM4dK+D09hNp2lJXSdLKQtJ4tGh53KNC0GrY364UZWDwO4clw4M7NxOJ047HYcNhsJMkGSJAGNLYvwfrfAelAc+dY53pj2889n+1wKtGG0OWj1tmO3W3nwm6/YeXidyXttXLnQzdfi+e35IJpUPXVtbaSbjIwtrnH44k/M3OsTGPZHSPHQ52vicHcDj7sOW34BXaE+ccR0JofHWbsxxfTPvUwGO/jyYIfF0T5STRZGFmbIElh6PV7euX+HV98ZYOWhKBgWGGaI3RjN1qhUMsxmXE4XFosJhSyZ5oHTLB32YXXZyC0qEdhZUavV2B0OrOI5gr3ZYGBmr+v4yOE7Pkw2i9CTPEpCYnIySSoVJ6TxGIQ0WkOno8Iue7lE4GaP5uj1evLy8jAYDbhcLmrqqgjf9h0XnL3bSZGg3yo0Z5DGoY4IV0TaiWMR5ze4WHs0iF3sUC5E/L24fxxxjL71nbDn7nhpKcqjNstFYaoWv6+Ti7Pz+JubmBKSWbjawsV3eyguzifblUmcJJbcU1X4Z+Zx+4PUVNjZulTF8kHvsQ4nbrWTI7ZfXXkatdDYla11vvnjH5hf2+DFUR+/ezHP6J0OTgpZtbib0GlSGB4b5m9ffcEHH37K9upZ/v2XJV5/FOLVjyLmsOdBK1op25WFKllJ88RF3nz8Mf2DHo522/jk+ShTP+tCk6ahIDef5KREyn0Bdp5+zuLWBpfHKvjrb4WtHQ6weCgKTgtSrIIUrUYr3EOKXTCcYbWgUyfhzLDRNV4fJcWZaSNNn4ZUOE66xSJYd5GmS8acrqe2upi5u92sRkiJFExQyomTyqIApwoJqEUPxwiLslpcnB2qEbIJoDfo0Ki1nBA5JpNZYFpMokKJTTRCbV09U7cEy49FwRlhX1niLxF2VSLU34VJeuyPufUZrAv7UqiV/4fh4xh96ywbT0JihwLDlZ4OtkbGCZSUcqY1SJ1nlNKcAhb6+lnbDTMrOmUi0Mns6BRNeUWYSxrI8c2T4CjmdHkFr2+ssPLLkGA5ECGlg73XNvni4Bk3PCFG1nYZXN3nZEUD//j6G/787ZeM7Lk52Nni8/c+44OhOYYml7l8/yl5NV3cuLpN5Hrz41kWPuyNtJ6H4YkQ2+u3Gav20ewN0DU0Q1l+Ls8fHPHZ79/jwv2zhOfGuLZ9n82OKVoa2zi/sE1FcRmL4Sn+9fdvufYkLGTjj7SeV3SBVYBuIFYiwZGVS1FZOY5MJ3pNGk2BcjGkAuQX55CZlYMiMQmTxUZpZT15RUVkZWZSVJh/3HoRUiIzRaNVoVIkkSiX4RQDx2jQCwaPe/uUJ0/Y0iB6o45k0eeRb5YMsyiSJ8zBglqjJlkQ9sOQmhOyqRS6qs/MpSEnn1pnNi654gf2St2ZxwWFB1qMNhwZTqxmO0a96Ucsj38/pC4f9NA98DKDI7WEhIj9oVN4Ay/hFuOzylNMYLkhOgKagxV0jjfQG3bjHa2jLVRJo8g701tKbXcJcz/tjv74P4VTrtt+49GUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Reduced subtitles&quot;
        title=&quot;Reduced subtitles&quot;
        src=&quot;/blog/static/43aca0b41fd2cef93932114616e2afa0/c7805/reduced-subtitles.png&quot;
        srcset=&quot;/blog/static/43aca0b41fd2cef93932114616e2afa0/9ec3c/reduced-subtitles.png 200w,
/blog/static/43aca0b41fd2cef93932114616e2afa0/c7805/reduced-subtitles.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We’re continuing to simplify screens by removing redundancies on our Home screen, including only introducing subtitles where necessary. For US users who previously had subtitles attached to almost every module, this change results in a more organized experience and faster scanning that clearly conveys the content they’re seeing.&lt;/p&gt;
&lt;h2&gt;More to Come&lt;/h2&gt;
&lt;p&gt;We’re excited to unveil future updates to SoundCloud, and we couldn’t do it without your support and feedback — stay tuned for what’s next!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Bar Raiser Interview]]></title><description><![CDATA[In an earlier blog post, we talked about how we changed the interview process during remote working as a way of improving our recruiting…]]></description><link>https://developers.soundcloud.com/blog/bar-raiser</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/bar-raiser</guid><pubDate>Tue, 25 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;a href=&quot;https://developers.soundcloud.com/blog/changing-interview-process-during-remote-working&quot;&gt;an earlier blog post&lt;/a&gt;, we talked about how we changed the interview process during remote working as a way of improving our recruiting efforts. In this post, we’ll talk about the training process we defined for the Bar Raiser interview.&lt;/p&gt;
&lt;p&gt;The Bar Raiser interview is a critical part of the final round of engineering interviews at SoundCloud, and it’s focused on increasing the quality of hiring, which ultimately has an impact on the products we develop for our users.&lt;/p&gt;
&lt;p&gt;Bar Raiser interviews are well-known in the industry. Amazon popularized the concept of the &lt;a href=&quot;https://www.aboutamazon.eu/news/working-at-amazon/what-is-a-bar-raiser-at-amazon&quot;&gt;Bar Raiser role&lt;/a&gt;, which is a unique part of its culture, by focusing on interviewing candidates and overseeing the hiring process so that every new hire is “raising the bar.”&lt;/p&gt;
&lt;h2&gt;What Is a Bar Raiser at SoundCloud?&lt;/h2&gt;
&lt;p&gt;A Bar Raiser is an interviewer at SoundCloud who’s brought into the hiring process to contribute an outside perspective to the hiring panel. They represent the technical excellency, culture, and mindset of SoundCloud. Bar Raisers have a history of hiring effective engineers, experience running interviews, and technical depth in multiple areas. With each new hire, we seek to improve and raise our standard.&lt;/p&gt;
&lt;h2&gt;Who Are Bar Raisers?&lt;/h2&gt;
&lt;p&gt;Bar Raisers are experienced interviewers who have usually worked in more than one area at SoundCloud. They carry the mindset of continuous improvement and empowering one’s self and others. They focus on value for our users and have built and operated systems at scale. They value and promote workplace diversity and have seen the long-term effects of hiring decisions.&lt;/p&gt;
&lt;p&gt;To join the group of Bar Raisers, a candidate should show a desire to exhibit cross-engineering awareness, the ability to identify the best engineers in the industry, and a willingness to put SoundCloud’s hiring needs above a single team’s hiring needs. If they see themselves as a good candidate for this role, they go through a training process to observe how these interviews are conducted and further practice themselves.&lt;/p&gt;
&lt;h2&gt;What Is the Goal of the Bar Raiser Interview and How Do Bar Raisers Contribute to the Hiring Process?&lt;/h2&gt;
&lt;p&gt;The goal of the Bar Raiser interview is to determine the attitude and instincts of a candidate to understand whether any given team at SoundCloud would culturally and technically benefit when working with a specific person, and if the person would in turn thrive in any team they’re assigned to at SoundCloud. By bringing these critical signals to the hiring process, the Bar Raiser helps assess to what extent a potential hire could raise the effectiveness of every engineering team.&lt;/p&gt;
&lt;h2&gt;What Is the Training Process to Become a Bar Raiser?&lt;/h2&gt;
&lt;p&gt;To become a Bar Raiser at SoundCloud, we’ve defined a training process that comprises multiple steps. This includes shadowing established trainer Bar Raisers for multiple interviews over the course of several months. Prior to this, the candidate Bar Raiser must attend other training sessions, including one on removing unconscious bias.&lt;/p&gt;
&lt;p&gt;Trainer Bar Raisers assess candidate Bar Raisers through a series of real Bar Raiser interviews. Each candidate Bar Raiser first shadows a trainer conducting one Bar Raiser interview, and then is shadowed by the trainer for several interviews. After each shadowing interview session, the candidate and trainer debrief about the methods of the interviewer but not the content. Each individually writes an interview scorecard, and together they compare these in a follow-up debrief. During the debrief, they discuss how the interview process went and what observations and conclusions were covered or missed.&lt;/p&gt;
&lt;p&gt;After these sets of interviews, the trainer Bar Raiser decides whether the candidate Bar Raiser is ready to conduct these interviews on their own, if they require more shadowing, or if they should step back from the Bar Raiser pool at this time.&lt;/p&gt;
&lt;h2&gt;What “Shadowing” an Interview Means in Practice&lt;/h2&gt;
&lt;p&gt;During shadowing, the primary Bar Raiser interviewer leads the interview. The shadow Bar Raiser has a passive role and isn’t expected to actively contribute to the session. Instead, they observe and take notes. After the interview, the primary Bar Raiser fills out a scorecard. At this point, the shadow and primary interviewers can reflect on how the interview itself went without discussing the scorecards. During the hiring sync, the shadow Bar Raiser will share their feedback for the candidate.&lt;/p&gt;
&lt;h2&gt;How Do We Level Up Bar Raisers?&lt;/h2&gt;
&lt;p&gt;We have a session every few months with all Bar Raisers to share experiences, learnings, and interesting cases using anonymized data to see how we could further improve the process by iterating on it. Moreover, we continue to do cross-pairing among all Bar Raiser interviewers, which involves shadowing each other to learn from each other’s techniques.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We strongly believe that finding the right candidate is crucial for the future impact of our business and what that means for the products we’re creating for our users. Furthermore, we believe that Bar Raiser interviews play a significant role in this.&lt;/p&gt;
&lt;p&gt;If you’re interested in finding out more about current roles at SoundCould, please visit &lt;a href=&quot;https://careers.soundcloud.com/&quot;&gt;SoundCloud jobs&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[What's New with SoundCloud, October 2022]]></title><description><![CDATA[We recently made updates to our SoundCloud iOS and Android apps to address key user experience (UX) fixes according to user feedback. We’ll…]]></description><link>https://developers.soundcloud.com/blog/new-with-soundcloud-october-22</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new-with-soundcloud-october-22</guid><pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We recently made updates to our SoundCloud iOS and Android apps to address key user experience (UX) fixes according to user feedback.&lt;/p&gt;
&lt;p&gt;We’ll be updating this blog regularly as we actively work to improve the SoundCloud experience for our users, and we’re so excited to share these improvements with everyone.&lt;/p&gt;
&lt;p&gt;Here are our most recent updates across iOS and Android as of 4 October 2022.&lt;/p&gt;
&lt;h2&gt;Track Cell Size Reduction (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 446px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8b2447df95e05166237651a8a8407673/07e28/track-cell-size-reduction.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.79820627802691%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAEn0lEQVQ4y11UXVMTVxjeX8BFvWwve1N70aqgtXXaaW8606mdTsd6I6hjhYomYkgQFAjJ5guSCpLwEUIM5AMJCYRkYxRSqp3OqKVDoA0YkoJaPiTBkA3Zr2Szm2xPEsZqzzyzs+c97/M+z3vOmQMRiJy9p8pNqTh/B+dXlwCmIEh6FbhHTvtUzL0CsneVWZ8SfJm7KgBAhChE/lt3nbeDZ289Y206NdpcOdFS6WuvfXCDt+sU0z5lZKjhjz7BnL5+3iBaMDQEBoSBAVHQUE8iMsin4ZvM1rnAfJ1A+O577x88cOBMVdVcIKA3DrvVVyJGkbytdXp6uqqysqysbN++tw4frnBPunp7dS6NADLBl6OJXY7jcjk2uR3dfP40TVFgupMi7d1Sl+Lii60YmNLZ7MrqajAYTKBoIZnj+ttbIZucP2kcnOnqDP88lU9EifUVNhHlSCyFJpE+mVlSY+7qC/n9OElkMhlAy+fzLMPkOc7UCUNjjZUn336nESqzS9UplsFwAqyDJAwjfDpxX+Np3v6KabkmgWMUWXBUGHkgzBlvSCGr+ELz8W8Gjn0+azFzTJpG4zkK5ygMSyS82lZTS3WrQv/L/ccYlsIwbI9bJN8C5EG58KbR3aEbmQ2EuDybBd5yOVAbJ9MTvTKT5OLl5h7k3kMstZtKvUE2aKSQEb5y6pLyi69rrQ5/Ir4d294uZeAUPdajMIkvmPX650t/4SSZpWmWZV+RB36CIZOivn8Y6e8b/XPpGQixbC5f7BlPZ509cpO4Rq26GVp8kkyiOzs7wDlN07liCYNGApmkvCHT0LxjaOvZKkFSTDbLMEyJPK6T2ODayWn/P+tr0a2taDSaKg6QAxIGNW3Q7XaBqE5Qc7o6HFrCCQJF0dKRFJS1klG4NuCb2VoKERTFMuzrPQ8CZbOMb7ntevgomExipWN8ZXu8Bx5pOT/+Q8Oaw/MSRYHh14/KAJQtiiu+Kb97fDS1u7u2tra5uQl6202iiRQx0QNbWs9fqmuYcDrBtcNx/M3dboNsqvrQ+qbd6w4uh0AzwHlJGUtnXb2ywaYzAqHQZjHH4zto8WL+R1a3QWaYv6yzkFoLObeYLV7xPdsZZlwntUp+dLjcs78/jsViGxsbwNTLeDyZTLB7yhK++9zVya++j40hL5JomiRL5TGKdnS3jcC1euOtSDhM09nSRr5pW8rXKvvPVp1fmPkVhMBNKCmnyAzo2dBYdba62utFwpFwKBQC+rHtWCwajcUTWlkjZBDXCoSik999ax+zA04mky6VJzKMR6+6yT+x/8MPPv3sWPmR8kMVBz/65AjAkaPlhz4+eu7El9CogidXd83cfxBZfbq1HQclXyaSOEk9ml+0q0Uu5cWO7t7gk9CTyMricngpHFlYXFr+e+X+w1lVwwUo5pRYZTyb5vpYZ4tN3TSiuWbtuGrTXBuWXV4baY45JHq4fljbbtG1m7UqS0/ha+vVDLS3zA82QeCVY+4q0x4p5ZGmERgg44UpBC4E7yjAKogknc2vA3Vcx13izB0FBN7HlFuGI4r/AfPIwRKOFEB6lQXcKYAo/hCIEvPI/gVg6QPvHi9wRAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Track cell size reduction on iOS&quot;
        title=&quot;Track cell size reduction on iOS&quot;
        src=&quot;/blog/static/8b2447df95e05166237651a8a8407673/07e28/track-cell-size-reduction.png&quot;
        srcset=&quot;/blog/static/8b2447df95e05166237651a8a8407673/9ec3c/track-cell-size-reduction.png 200w,
/blog/static/8b2447df95e05166237651a8a8407673/c7805/track-cell-size-reduction.png 400w,
/blog/static/8b2447df95e05166237651a8a8407673/07e28/track-cell-size-reduction.png 446w&quot;
        sizes=&quot;(max-width: 446px) 100vw, 446px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We made the size of tracks, playlists, artwork, and creators smaller so you can access more music with less scrolling.&lt;/p&gt;
&lt;h2&gt;Labels in the Navigation Tab Bar (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 447px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/98d5c823baae1c86e765e26df4d460d1/aa6ba/navigation-tab-bar-labels.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.5413870246085%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAErklEQVQ4y22U2W8TVxTGrx3Hjj2b7RnbYycEIaUND4DU/gNNbSeoqA22Z8aOQybj8Rp7vCdeSIKjmEgNWWoIhAYRwtoEaF/6CDz1pVXVqlLfq0oVAmUpdHlpEUt6HF7aiqNPo3Pv73z3nrkPB22trv9+7e6f17/4n2Bz+8rG1pWNN9I/rn8OFD25eufb+fP362fuTc/er4POgB7MzH+3uAzot2t3f1hcflCfuz89e28PQc2D03Nfz539de02Wq+ecvf1+Xzc293dGI6Burq6PF5vj8v5ZW3mq9mGy+XyBwKHDh82YEDxfZ2dPMf3HHVdLIyhCTk6HI1Uy+VMJjMykoRIZzJjY2MBUVxQ8ucyBaDlUgloci/S6fRosRhXUkrwBJqKJo5zXCQcBlwoFPL5fDabzefyJ+RQI1s8nyl4A/54LJbL5fJ7AUk2k0koSk6UUDUUeb/XnUopSQUOTTedhWJprCTFYmBupPM9fb3JJFyUBZoBWiyOFkfTcEzTLIWdbteg3y/Af/u8Aucb4PmgX+g79sFCKgtm19G+IM8D5bmmgAZ43sN5U4EgygiBNo2qgyKsmIHGMStusJG4zUSoVagmyvOpNG7QtRPNfRo3QI2NwFgTodeo/S43SvuEthbksJosJHHASNpNJGMiHWZKo1ZNSuH5hIK3tdppiqGIAxTZbqJoI2E3Gw3aVsHVi9K8X6/XsozRBrsEbidxK4nTJInU6km4eUTBScxGU1Yj6WhSDChDElqtlne6UVYI6DCdjSItmL6zne10sEaD3gJmjQbaXkykMRNhJQkLZuiwW/Z3OEyYwULgWp1OcPWhLC9o21o6jBQLXdFGxkwxONxAtLSoakOhxREFI/UOkmApwsEYLTRFYxhLEnpdq+B0oSwntGpUNsZEm4x2eBWD3ozjVopUvzbHU3qDzmKmaLORxTGLXm/CcehLp90zZzwcUiGGoY0QFMWyrMVqsZjNCCEwL0STWp2WMZuNpj1qs1mtjJWhW1s1fI8T5biASq2G96BN5nbH/q7ug6zDZmcYXZthSpQX4kqLXk8RBG02s+y+t7oP2ttZG00TOBFw9qKJUNTDc+FQSBwakoYlORQKy3I0Ej0uCHPJ9DklB4kshURRHBYlSIBG5PCJ4eGkP4hqkbgcj4+Pj5fK5XKlAiqVK7VqNTMSW0okL8aTyXBoarQwXiyUS6OlCtSUK9VKsVLKDQ2jYmDw0JHDAwLv53yclxvw+yWelzz97l73x9EReLAj77wb5ANBDyf3eyUPPzgghgYlzueHhlFdkgf6j+Ujkuj5UPYeTwSE1IA/5On/yPnemWhiWclLPmFCyZfiaWVQLgzFRsV4YSgR40N5fhDdmqxtbm3u7u5ubW4+e/Y3JC9fvoDvw8eP1k9N352YevjLz7B8srO9s70FyYsXz3dfvfrr+bPr03X0/fzSUuXkSn3mwqla4+REY3wCkpX66XPlyo+Lyz9dXFsqVT6tn75Qmzo7Ptk4OXl+EuhMo1L9ZvYT9BQG3eX1R5duPFq58fjSzdeCfHt1HWYYDKqd1Y1/oya9dHP78mdPr95BMD231zZ2oOi/ao7O1fWmrryJrt0G9A8/qwTQmUFTYwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Labels in the navigation tab bar iOS&quot;
        title=&quot;Labels in the navigation tab bar iOS&quot;
        src=&quot;/blog/static/98d5c823baae1c86e765e26df4d460d1/aa6ba/navigation-tab-bar-labels.png&quot;
        srcset=&quot;/blog/static/98d5c823baae1c86e765e26df4d460d1/9ec3c/navigation-tab-bar-labels.png 200w,
/blog/static/98d5c823baae1c86e765e26df4d460d1/c7805/navigation-tab-bar-labels.png 400w,
/blog/static/98d5c823baae1c86e765e26df4d460d1/aa6ba/navigation-tab-bar-labels.png 447w&quot;
        sizes=&quot;(max-width: 447px) 100vw, 447px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We updated the navigation and included labels, making it easier to understand the purpose of each menu item, in turn streamlining your experience.&lt;/p&gt;
&lt;h2&gt;Stream Is Now Feed (iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 449px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5a8eb100c806c274758c641e6c2f8f15/a5369/stream-to-feed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.03118040089086%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAFEUlEQVQ4y11Ua0xTZxg+f5cs8TLa055z2p6WAm3thQK9SJHpwqYIQy4igvPPFkUjZBHEgk4zcKARWloqyECh3CpWzJxuKhShF22BFopY5ozIZcMNvFLGrbSlO2VozL48efK+z/O9eb/v5D0fsPyszDep8D1X+P5S+tkPpW+y3PdnOWYtj5RiweoG5SoUazypcI/KAMzuu5XXrc1pbznS3pLlhybLcD3PdlvqGin1TsiHOvK7r+XqNNntzUc6MG7J0rflmn7KmX18DrjRcOyyutlgNKWmpeNBAgQSdycmtd+5c0F1sfNq/lDnKZlcbrXacnKO4UEiBCObN0t0us7Ga601FUeBhgunnM45n8/nmpuddzqXFha8Hg+Wvn47f11dqK08MjH2HEu9Xu+Sfy263cu+lRVMqauRA9rq03brwNjI08VXL/9TV1b5rXPxhqZEW5PV09v/8sW0793CXI/HjQXN9UrgYtnxtta2N69mnDNzHo/Hu7JWPDvnunnlrKb6W3WD5vXM7Lxrxe1xY/1X6/3coq4AVLLjv/x8a8Bs7jGZjPpui8HQf99kfWDq6jJo6041V2VrW9swxazXmwz6+3q9zWTqMxl6Lb1VikIgLzuVBeIkZIgPgWyEEA6BAiocRoco6zaU5GfUqw5zUYqEBEYgBDYEhiGgkEQUMMjU9et2x0qAvENJLDxOxKOHUskJQdTIEBqPQRUyUdon+DPSffWKTA5MErOpXDo5loZuYwRyg1ARmxaMw6XsiALyDqewA6lhnEBhENYBiiRD4SjCQBEoACrCOsszeQxm+CZaWAgaRYYjsbYowqKRYByYGLsFkB7dy2DQQ1ESduyYraIvooTBIJ5Nhgl4UvGJrxpVh9h8XiiNFEoEoyM48TFbGBCBTUZgHDH5y21YcUowFY6ikkU0ylZ+CI8TxIUgARlG8cTigvSmykwWiy6mImKUHM0NFvAZLBgSkZFAHD45bguQn5VEJxL4HDqHESRBiBwQz0QQHgVBAggl+elNqoMslMJjUbnMYDEFCQdxITAcipLJAbiUnf4PlkAiwNxQNoPJYNLpErFIKIzgczZBG/ElBXvV8q/pJCqXw2SwsMvRRRHhYqEgIoxHwoHJOyKB/OzU9R99HEzcSKOggojouOQUbhiHRaOiIO1cQUZjRebGABgM2BCIUtlsQXxiSnikgIklICU17lOgWllgMJrt1j5bT+/gwKBj6NFvw47HDofFYr9Sc1JTnfXr3a7BgX5rT4+93z5kf/h42OEYejj86IlKXgi01Bb9M+96P9Lvg7kFDzaeV2uyp1/OfOi+H09NQwVwvjDz9OkiU/c9va6jS2ewmIwWo6HPZNTd7mhS5TYpDkjzThq7Dfd09zq7zA9MZvN9i63XOtjvkJ09AeQe3rVNItmfEBsXHblLIt7z2dbE7Z/HRktEXH6xdE+d/JsYScyBtH0H0/ZnJOxJ3ZGUvjM1Iz4tPjphX+J24EeFdHx8yuf2/PH87zev37rmFxeXlrFTPZuYbr30XVtttn3wdyx9MfViYnTc/9svLHncnrlFb1XFD4DlprRSWdxap1JXldYqii9XljarK7WaS6ryMwN3T05aixTnC65qLjXWX6itltXXKjFoNbU1F2W3mnIB7JVyDhdP9RdimH6HKdv3mOgZl3nHZPNPzq1ZA2s8ZSt8M3TGOyEDlp6WuUflK+Pl/4N7TL40UuYaKVselXlXlQ/ZM+53/wV5hfQQlEDc8QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Stream is now Feed on iOS&quot;
        title=&quot;Stream is now Feed on iOS&quot;
        src=&quot;/blog/static/5a8eb100c806c274758c641e6c2f8f15/a5369/stream-to-feed.png&quot;
        srcset=&quot;/blog/static/5a8eb100c806c274758c641e6c2f8f15/9ec3c/stream-to-feed.png 200w,
/blog/static/5a8eb100c806c274758c641e6c2f8f15/c7805/stream-to-feed.png 400w,
/blog/static/5a8eb100c806c274758c641e6c2f8f15/a5369/stream-to-feed.png 449w&quot;
        sizes=&quot;(max-width: 449px) 100vw, 449px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Based on user feedback, we renamed Stream to Feed to make it more widely understood. Nothing in your stream has been removed or lost — you still have access to all the music discovery you know and love.&lt;/p&gt;
&lt;h2&gt;Smaller Page Titles (Android, iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 451px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e68ec0197c95c0bbf423348f0e740a92/d3429/smaller-page-titles.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 113.52549889135256%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAEY0lEQVQ4y6VUa08cZRQe2MvMzu7M3oaZZWZ3Z3YuO3uZZa/Asttuqa6tkFZqYYWFgqG73KQKprTQbLG2sBVKAQPGWFuJ0tY2wUbEaBP9bOIPMPFzE/1gYuJf8CybNEaTfvFk8mbynvOe95znPM+LIP/HGhoaSJKw2+1Op5M6tKYm+HU47HadTgcBJHHopZxNNRcYVfM6HEajEbFayebmZpZlDQYDhmEoakRRlGFoioYgiiAJh5Pi3G4Uw8ABByCMYRjWzcI1CE03/asWs9lstVoJi4VnaJFjcZ3OotOjsG80Uk0UpLbZbARBQAwCJfJe3uvxNiKIHkEMCILpdbjRQOr1DEW5aNpmxW12U9NhT7jZgpvNz+9AoGbG5XR7OcgCyTCTCXLX3bDTzFE8ezSj9DY7LX7BM//OZHsimm5NcAxFMwzCci6e7ghycafVlAoHxkeKok+Ih1SStAAUtMumSX352CVZcMmib3Rk6NixXHFwQJZE3icgbo8r13KlM1pWfExbMj49We7q7hoaKkqypPgVt9ttwlGDUY9iJoAKyqn3DNAC7FC2iyBI3IxbLIALdI0AVA67w2IhZFnmOGiH4li3y+XyeL2QC0Zbb6oGmCAIOI7D3KBDGCKMtD58WOGAIPChUCCZjIfDoUBAhXSAs9lsIUkS7ocIJh5raW9vjcdjEU0TRdFQM6NOpwcK+Hz8w/uf/fD04MHuvf0nuzeXKhemp4pDo0PDZS3SgoSC6qMvP9/e2tz6cPXOx2szb48PDhbPnO0v9J/zer2Q+ueffvzjt18P9h+9/961d2curq2vX6+ulMcvJJIpJBqN/PXns9+f/fL9t3sr1eXFq4sfrK7OzVdKpalgMAhMXF5afPrdV+ubm9uf3F2qruw9eXhn515xpBQKa4gsS/d3P/1m//Gt2xvXq7eWlqtfH+xtbH90qqcAHQJPp6cnSuXS5tbG3d2dGzerF+cvz12Zn720kEgmEUWRFxbmZmdndnZ3vnj8YG3j9rUbV68sVs5PvOX3+wHF7lOv5U+eHnhzbHhw4uWjp0vlmbMjY5cri+l0GubsKfQXO/OvliZn+l4f6T7xxrnR6WJ5ar5SCagqiCvReuSV7t7unmIynMjGkkOFwkv5rjO9g4lUCtBuznV2dfUUe/pGEmosn8mNjZ4/0d1TGBhWAwGYoD8QSaTaY/FWTYuKouTxCpIcbO3oDEeiCMO4ovG2TCbX1paJtcSCqir6ZFkJZ7J5KNvusAuiGgjHtXg2qLV6PaIiB7OZI7F4WlFUhGU5UfBnO3KpWFs0FIsomsT6ZLeUFkUfxzooSpGCuezx/PGTHW3ZjlSHJodVPpAOhDXBW3soNC0C2jLoDSiKwVuAoRj8WwkroAWEEwSfX/ED60yYSdeog7URaaQY2klTNaJDEIiTpmkgIHycmzOZMKAhjtfEAMy12oDIJExOkiSe52GfICzmurDhdTH/x0A3dQHo9fp/7oMQ6utzhbzoeXyB92+Ks+WPAi1IcgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Smaller page titles on Android and iOS&quot;
        title=&quot;Smaller page titles on Android and iOS&quot;
        src=&quot;/blog/static/e68ec0197c95c0bbf423348f0e740a92/d3429/smaller-page-titles.png&quot;
        srcset=&quot;/blog/static/e68ec0197c95c0bbf423348f0e740a92/9ec3c/smaller-page-titles.png 200w,
/blog/static/e68ec0197c95c0bbf423348f0e740a92/c7805/smaller-page-titles.png 400w,
/blog/static/e68ec0197c95c0bbf423348f0e740a92/d3429/smaller-page-titles.png 451w&quot;
        sizes=&quot;(max-width: 451px) 100vw, 451px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Thanks to labels in the navigation bar, we made page titles smaller to make the best use of space and ensure users they’re not missing out on any music. This increases content density in the first scroll of pages like Home.&lt;/p&gt;
&lt;h2&gt;New Playlist Header (Android, iOS)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 449px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/dae770234d488c84803f2364390c1865/a5369/new-playlist-header.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.03118040089086%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAEyklEQVQ4y11Sa0xTZxg+MYvJ9s9pdG6JTueybCZzyZYgWD1cSlvKACm9MDaktPQitwICLR2CJWgAh5O7cxEsFmgpN8XsokNAho5xERmiQhcYCrQHekrbU9qetnRfT5nL9uT50e97+pz3/d7nhXiMZn6cmh+nETA1QtYWwRFcAslPbsx17sl/mRTTxD2pBPcQN/YGi67g0HPpcB75uCwcllGDZfExBZzIc9zY5oTIOurxLBqcGnyUDxgSmBIcwKfBaTRYwqJWQokxKX01B5+2f8xh7P308PYjh7YnUV/XNb1/p+7IV9HCKEpebV316OhYcjL3zZ07du/ZRSIF9fffUzZfi4sWQ7ncCNNjeaWqZZwXpYnZ/ZvyA9cg6flFOjogS/+SHE7mY9iq1+v1eDxOpxPHXR4C4EYuU0DZCVTjcEBP7un5bfsor+0gUw65kL75SaXjUbGIEUihCid/qJxoyzcvPMRsDoNBv7KyYrPZgLlQfh46cyrC0r/fUXZwZtfbQdveCINJD2aWkKet7tEQUeyxyKjUoWr2JUHg1N0b9+4NoqjRZDLhOO4zF5RChQLKrTpSv5o5mfUZMyiQGsqa+Uni9TzBDbfFsYFUqmj+9y7d/aaFqeHZuT/dBIB/bu65LF8BSUW0pe8+HCtiPbgZ/U0R/d09b93JOIAOVWHIkzQOTKOkGM3optdrxz3Ey8FP78jICJvDTE/NhXLEkWjqO0b5gVvy/eVZHxXnhGeKmc0XC/WNwZJTIRQy34Asmi0WBDGsElhbW0ONKIZZigovQHlCGlq/Z1zyXlfq3l8vHe6uJT2s2Tmv3mfS7kxjwxERot72joHbP/61uLiwsKDT6RYXF50OJzGwUig7hYdOSS3PSrAphWm8EJs6Z39Wgc9W4LrzooQESlhSQ1ndtbIGzIZZrdaNjQ2Hw+GPqkBaAuVkli4b0Bcv0fkXqwsv1jYcbu8WPOKUr0Nh1p2hvrGJxwYENG4AowJpb25uEjmXQOniwonxEeJIACget9d3xsV8WSjMnh69P/XHuBH1weVy+XT/kkgJc740t7y84uzZs4ODg+AWhEF8xi3ymeMnrl9AehqWDcgqgoCyxB/+MUvSimWyPLH4NJvN7uzs/L/5BLu1/rLul07UZDKbzf7O/Gbfm7MzFC63098yGIbdbvebPZs4YWbe/Xls6aUZQfTLyytAfVW5QKqAstIV6xaj1WZFTUaD3jcXEKbFbMFsZhFPRoY5beqOoYFhu8MOKoOBg8X2N++rnJV2bubR9PTYrH5pFced9g27X9v0ugTJUho5ET76SSQ5FOzJ+vq6jYB/t33rmSkuqlJcKUv6ok/dtm7F9HrQ3vLaqtHtAW3Lw04wq+qVDVc15nUT2C2rFQMwoSYiqlJImCxTXVYKqKLbql7/s/0xuj0OIU9GgTny899WNbZjFgsIGYQEVJfLN5T83GKIE10sScmRZ5TUX/6+p6dX297doe3u7uqtqa7nfF4UTZZmpKXX1tSo1VqNpl2r7dRqO7q7bjY2NsbS0yE+Q5XEuJJ4sopDv8QIr4ijEAwvj4+o5jFUiVFXY0IVFNKZsAAJYOgWM6nH8hMi6yEeQ8mPU/HjWgTMViG77RUFrFYgbalMlYDV8l8Ctflv9r96cxMP/XcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;New playlist header on Android and iOS&quot;
        title=&quot;New playlist header on Android and iOS&quot;
        src=&quot;/blog/static/dae770234d488c84803f2364390c1865/a5369/new-playlist-header.png&quot;
        srcset=&quot;/blog/static/dae770234d488c84803f2364390c1865/9ec3c/new-playlist-header.png 200w,
/blog/static/dae770234d488c84803f2364390c1865/c7805/new-playlist-header.png 400w,
/blog/static/dae770234d488c84803f2364390c1865/a5369/new-playlist-header.png 449w&quot;
        sizes=&quot;(max-width: 449px) 100vw, 449px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Our updated and reduced-sized playlist header puts less space between you and your music, allowing you to access your playlist tracks — plus shuffle and play buttons — faster and easier.&lt;/p&gt;
&lt;h2&gt;More to Come&lt;/h2&gt;
&lt;p&gt;We’re excited to unveil future updates to SoundCloud, and we couldn’t do it without your support and feedback — stay tuned for what’s next!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android Image Loading at SoundCloud]]></title><description><![CDATA[The SoundCloud Android app recently got a significant makeover: a “new year, new me” type of thing. Our remarkable design team and…]]></description><link>https://developers.soundcloud.com/blog/android-images-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/android-images-at-soundcloud</guid><pubDate>Thu, 13 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The SoundCloud Android app recently got a significant makeover: a “new year, new me” type of thing. Our remarkable design team and outstanding engineers worked together to revamp the way we display all kinds of images in the app, and I’m about to take you behind the scenes on this “glow-up” journey.&lt;/p&gt;
&lt;h2&gt;A Peek at the UI&lt;/h2&gt;
&lt;p&gt;First, here’s a look at the user interface (UI).&lt;/p&gt;
&lt;p&gt;We went from the old design:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/e9c61/old-design.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAADH0lEQVQoz1WSe0xTZxiHz0xMlizGuCzL1Bh0LjJvDAQT42YifyzxjrFeYUYwIuhwCEZBmRpiGy9VRArFC1CUpAjGRAVvURKvUW7j6+pBtMdi6eFy2h6o7WihnO/77bSJun3Jkzd53+d7f/+8nOz70HLL0m6pbWsm99qbSbckEXN7CzE1vyCOoUHS9qCJmK/UkPsPm8jNhkZSZ64lD0w1hG9rJS45QDp4D3H0hYhg91tsdv9fnNDXy45fMmLVBSOWFZ9G4eUr0N+5iyKzEVbRjudbduG36Dhoyy8iv/AYEhb/AvOEmbBqT8MN4H7DI+Qd0OFArg5nTpSD65GkYf2RArar+KyyzVRFk0qKabrBQK9WltC/X7VRmyaD5k35kSZlZNKs3P00JWsPbfwulr758xTt7hbojbzfafHW9fRc2iZqTNOEOIckBbU6LU4cPcxySg3YUV+P6HVJWJa2HZ19Imxrd6AoehHyDWXQ6ouQmZ2DJ5PmwnayDI01Juz9IQrX1y1nu+NjoFsSr3CCSwomVVRgo6mabTNVY3fdNSxKS8U306bg1tPHcCRno3biLGzemYG9x7TITlwBgZsOXm/Eozoz4r+agF+nR7GlUycjY/YMhbOJzqAmfx/WlJay5IoqJFeasPp8OealJKOh9SXebc6C/OVspM9diJXp6Ti17xBavo3D2+MGVFZWYPIX45AYFcUKY+fg2tIEhXvd9Tr4c4oGi/U6tqLMgJUqiSVnEZe7B01WC56l/oGCr2cg4ftZWLslBZn5B7FhjQa3q2pgKDNivLpw9fwYlrNwAXbG/6RwPp8vSLq6YO22M97xHg7Zg/ceN3oGZQRGRiDYBDy1dKCVdIB/xeOdIMDucED2+SBJEqxWK4TOTvaW59HF8wo3GgoFfF4vZLebDno8TL2E/yGKInP19zPR6WRir8j8fj/7RyU883q9zKn2naojuVysf2BgjKOUhsbGxqBQGoGxsPv5fQiHyXKEoaEhDA8PQ10U8QKBANxud6QfZnR0FJz6R1TxqLg+osqfqhrmUsVIVcNd//UURfk0C4VC4Tsf+BeovZHybSqfpQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Old Design&quot;
        title=&quot;Old Design&quot;
        src=&quot;/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/8ff1e/old-design.png&quot;
        srcset=&quot;/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/9ec3c/old-design.png 200w,
/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/c7805/old-design.png 400w,
/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/8ff1e/old-design.png 800w,
/blog/static/c18857cf1ce1b71115fc5dbf032f1ef7/e9c61/old-design.png 1080w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To the new design:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/de1546939637c82b311dd4c89d0df25d/e9c61/new-design-.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.18518518518518%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAADT0lEQVQoz22TfUzUdRzHf9VmU7a0mdaKQmJGMWtmW/6hZg8ra7fEQTpAi5oQ4lno6cEdxBWCKM+PtYTugTvt8g7WYTOGhg9bBkdJPAvCCRp34PwNhBDGUb9X36OtttZ7e++zfff+vj+f9+e7r9R9Y4jqxjPoXbVo7VYuX2nD1Oom1eUkQ3Bs5i5dZxrJjk8ku7AIu6OOrGN5pO3X4NxzgIajRczPzCg918a51HxTls53/IrmsIEt5SVsLivmnezDGCw2Toom+YVpfHL6W4ayS8mSgtiRvI+jxRW8/1EKIU+upmlpOBcEh4wOxTcN9s8rZenn3h6O6LR8XFlBgtlErNmIqiAPQ3UVRZ9qSDlRw0BuOc5FT/BmZBS7EpI4kJHJrr37+GntFi498DQ3ys1Kf1MD1sQ4WWrp6iI7TUtZRjr6wgJ2O2tRfWZg1SubUR/JI/O0i6tZxZxdHIZak0rJcSOZx/JJSEqmJ3QDTQ9G4K2sUb5QJ6GJCJOlpr4+3qqqItZSw06TiWT7KaILCln26MMsDw3hoOMbunNK6bs/nG2bXkMnzNSZBvau24h3+ToaH1qDt8Ki2DQphC5eIgw723ldn0qM0ci7RjPxJgtxVisvaw/xlEpFiv0k3bll3A56lq+Dn2dr9HZi1GqqYxOZXvki55c+w3BFjZKzJ4mQgOH3ZxvYtDOKVytL2Xb8S6LE7rYKbszPJThSxaE6J52GfNqCwil/JILw9euJ1+lQi5fOifkA46oXGCkzK29HRrJIukeW3K2trI7bTph2P2vSdTyXoV+o4XotoR/uRmOz0uuoJz0omOglK3lv2eMY1r60wIMb3qB0RQTTJRZFL/b+2L33yRICVwYGsJ07x6mLF6lvceNqbqZe0PnjZdz9/czNzyN/9wNTXzmYtNRxx+Rk5kQ9szYX00Yn/t5Bxf/HPBdqa2XJ5/PR09mJR1y8ef06KAr/hWdwkF96u7nq/Y02zzW8v08yJ87v/itRPB4P3lu3ZGl2dhbf6CijY2PclmWGhoe5MznJn8LYLyYLYFAYtra00NXeIdhOd0cnbpGgXfyq+bm5wBDKgEjp83r/jvx/EJoFBjA1NcXIyAijovH4+PhC9fv9/+gCZWJiIqCR/wIJNqwbRcs+pgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;New Design&quot;
        title=&quot;New Design&quot;
        src=&quot;/blog/static/de1546939637c82b311dd4c89d0df25d/8ff1e/new-design-.png&quot;
        srcset=&quot;/blog/static/de1546939637c82b311dd4c89d0df25d/9ec3c/new-design-.png 200w,
/blog/static/de1546939637c82b311dd4c89d0df25d/c7805/new-design-.png 400w,
/blog/static/de1546939637c82b311dd4c89d0df25d/8ff1e/new-design-.png 800w,
/blog/static/de1546939637c82b311dd4c89d0df25d/e9c61/new-design-.png 1080w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Below are some images you’ll come across while using the redesigned SoundCloud app.&lt;/p&gt;
&lt;h3&gt;Simple Track&lt;/h3&gt;
&lt;p&gt;This is a basic single image for tracks.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 475px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ebd7bf40581eec28611b0c44fb23f157/aff45/track.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 99.1578947368421%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAAFKklEQVQ4yyWVaVBVZRjHX3PBhTBzw1xGRURRUkAuXC73srshiJgaI6mNmg0EhitupKyXXS5c7n65GyBXcU1rrJyaktKa6Ys1OZPT2NTUTH5oqg+NX349hz785/+eM+f83v/7nud9jjoUdLK28TTplhbS7d3o3FaSvTZS+h0kB9wY+tq5fcvLL3884+HTX8keCqALeTEM+kgVTwl60AWcrHZZ2H9rBJVl66Tjm1GGf3jEydFPUa11/6u9kfGWVlTtCXS7dnD/8VOePPuHijt3UOb3UG3aM/WM62xGdYjqaljh6UUVeGy0NDVR73Zyoq2VOR1m5tq6WdTXzVLRSq+DqL2vk7q9hI++fczTv55T88k9dlwZ5sD7N/E/eMhFbxPvth+m7sPbArT3cjYji578zViKtpNg6yHeYWWts48klyzdZSf9UohFR6tYkpdDg93Dl9//xG9/P+fnP//l7r07fB2q5XG4mWtDDlTpjauU3RhB39GK3t4ne+fG5PVg8LrJ8LrG3ORxkjs0QHJ3J9HFW1i2MZ8jDWbuP/md8GUfD/11PP14gKGuGtShWzepHxhAZ7nIpq4ucp0OMgVo8rjIFc/2eUUeuecmJ+gnd3iIFKuFmSWF6EpLufzZF7QdK+NuaxXe1jMCDIWwFWzDX7qPa8W72Oywkxy+JMscwiCp8vw+8v39ZAk0u1/AMpFJxllXw7x8+G1qrHb8sp/VpTnUn65EVYfD9JW9SfCN/ZjLq6g+d54LJ05Tf/wUJ8/Wki8J8wM+Ngb8rBfPEViGKKn3InFSWnE1x/DcHaVzMMw5DVg5MsLt/CKu7tpD91sVhHeU8ciQzyPjBj4o2snmfi8bgwEKNAl042AInbmJyNglzDKkEVlaQmJFFZ7R7/j8wSjq4I3rZHd3U2SzUWh3UCBeYu2j2GqlWBuHBigZGKRItqZQgFuuXGZlZTkTpk9j6uwZTImexYLsTDqv3+Wrxz+i9gtwtdNOqldbSj9G2TOTvGgQzxLXYNsEqgF3Dsp4OEyyuZnJ8+cwdX40ka/MYZLAFyeu4dDxY6h910ZIkK+WKtAMj5vc/n42+AQmE+hcjrES2hwIjEmDbw2G2CDQhVu3MGlGJFMWRDNl7kxUZASpUqdqz0iYeClmndRahtstIK+UiHfMNRUEggIaGnMtZYGAs/1+0pxOIlevYPq82cTqU1ER48nWgLuvXyHhzCl0jQ0YgkEMHg/pkjRDPE+SFkmiYllycUjSiWv39C4n6aEgi86dZl58HOWdPby4tZC0vbtR268MoYx6ZmZlkNBrYUVvD+mSNM3tIqffN6Ys+dK5AtJkktQpAlwlp2qJnP+oikOsq6xiWV0Dep8btf78WRavjichaQ0pBZtYpdez9OABkqSYtS1I93gxCiRdEutFKTJRghR/rABndrUz6cIZZu8oYVZqCgU1x1EZh8uZHz2P2JgY4uLjWbZpA3PfKSdGmkKidmpcLhIl0TrxJHFtvMphY4FVltnTxcS2ZiKkJtW0CNJOVKMyd25nbnQ0y5cuZdrCBdLXzhBls/CSpUNesrDcbiPGZiXO0cdKqYQYSTa/r4cogU2SZyZcbGdq6WuoIxUkDwdQyQYDSikiIiIYP3kyL8yehVr76hhYmeul2TYysb2FiZ2tTBCNk36p2ppQLQ2o5guoxtqxRpu4t4ySRrmuPlWDSfZuffl+8soPkHdwH9lHKzHKr8Dol0YgvwGTtHrTgI/MwQCZ4sagXIuMAa1RODH6HGxrN3MqPMB/Q4qhdJIDAC4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Track artwork&quot;
        title=&quot;Track artwork&quot;
        src=&quot;/blog/static/ebd7bf40581eec28611b0c44fb23f157/aff45/track.png&quot;
        srcset=&quot;/blog/static/ebd7bf40581eec28611b0c44fb23f157/9ec3c/track.png 200w,
/blog/static/ebd7bf40581eec28611b0c44fb23f157/c7805/track.png 400w,
/blog/static/ebd7bf40581eec28611b0c44fb23f157/aff45/track.png 475w&quot;
        sizes=&quot;(max-width: 475px) 100vw, 475px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Playlist&lt;/h3&gt;
&lt;p&gt;This image is for a playlist. It has a background stack with colors picked from the playlist artwork.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 477px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6d1b13fe2ce4f70b4ddc1de2b1b1db60/bad17/playlist.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.64360587002095%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFV0lEQVQ4y5WUe0xTdxTH+8+ybGZOWeYeZm+JIiLRSYAQ35s8RIECpWgpoIiIIBZFEQV5lVJaChRaoC30RRE17gESl0WNAld8ISDijFFAB0XMHIrRTL3w3bmXmPmXyW7yyfn97u93v79zzu+cK/jn5QvGcKqFkZ9oYrS/nWTM7ecY64V2xtbZwY+7ezuYx+OPmBvDTsZC749eucQ0Xu7ksXe2M5aO88zj588YAAw7OckIeu4NIsakg7b9LDJaf4GvphjeGgWWa9XwUssRnZGKobG/0TP6CCFGPZbRmk+5Et6El7YUrso8ZP50DK8fwbnbt9icSg3bevJXtspiYn8sK2HXVpax/kSwsZp12xLLhmxLZPuHxtjzA/fZnU0ONrnRzladOcMW1RaxMm0Ouzovn82x29mXr16xgovOYezeKoUuSghtQhxCdZUIqdUjjBDWViO6wQpvWSpWiKOgbziKvkEn/noBtF9ox8UmBa4d12Dz4SysPJSNnrt3Ibg6OICDp1ohMZsQbKhBZJ0J0SYjxBx1nDUgrtGB4FIVFkWLsDxKhAr7MTT/3orTdQXoO6FHUKIE6wrk6B3gBJ1OqNRKFOTloPDgAcTV1iLCaoXYYoaEo74Om0g4xmpBXNMRBJep4Rq+ESVmO8rl+1AtE8M/MR7r8gvQy3nYMzaGwow9OLo9AdbUZCRXVWE7ie6orkacyYQYEo0x1/PC0UYDJA47Qq0mhOfn4vjpdmSkJ2GlVISAQvl0yN0PH0KVlYmzkk2oTd+Nxp1JuBIRik6RENlKBeWwAVstFsQTUrsNPokJmL8+AO4JsYgt0uDkpeuIyd6PtTmHKeQBCG6MjiKuUgtZqRqp2kqkVJRjv1qFDGKnqQ6JjkYk2Gw8UkrFZ0s88O6MdzFz7idwD/DHzx1dSNXpsCorC72DgxDcHBlBqKEWIXQZkfVmRJEnIvo4nEKNtdmxzdbAk2R3YFvTMbiHbsR7LjPx4dxPebvEzxeeYjECCgqmQ+4fGUYIlYqwRg+x0QhJXT2kJCw2GPh5vNVG4dp40QTHEQgrKuDy3deYMecjvD/HBe+Qt3N/WItghQLdd+5QyMN/IkynhUivQxSFKDaaEEVsJuF4sxXb7VzIDTwSWpc4HPDdnYaPv/gcgVIpZpP98k3Bmw9G4bNXhnmUj7BqPSLIM5GBq786SElQaqZyIQ9jLVRKdOvCmhpspBr1lEoQeSAbq3JzsXBXCoLy8tDDX4pzBCsSpAgJC4RQo0QAJTisphqRxmnR14jI63A6bAMduq6yAl65h+AWGQHP0BAsTuUE86dzePH2LeTKM6FXZMErMx2rK8vhT2ygFAipHsMIzgpJLJS8C9JVYY22DL4qBWbPn4cP5n2F+TsSsZ4Ku5sTZLquwvV7T3zruQCusmT4qouxQqPC6vJS+FdpEUgXxokE0QHcfE1FGfzKVPApVcKVet/z0D54UIcFHc5FNxfy5a4uCGbNgovfMrjuS8aCrHR45B/EUkUevMkLP/pwebmax09TAm+1AstUciwuzoO7qhBb0pKwNC0FgVzInODU5CSqqAO+2SKFW0oSFlD7uaWlwl2WhkV7ZPDYSwdQay7O2EvjPfx8EeFOawtpD4f7rl1YT5dyjQv59Y+x6XwbwuRFEClLEKkoJhS8FRUrCZoXF/N2ev4GSiWiSkoQW16GvqGh/wS5Z3R8HE7iwZMneDDxBM7H47hHvT5EP5C3MUhw+1iWhWBiYgKtra24Q0U5Sm14n075o78ffdev4x6N/+8jePr0Kdra2tDc3IyOjg5evKWlBQ3UEdz7Z8+f8xsnKddvZWoKU8S/maala2lbk/UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Playlist artwork&quot;
        title=&quot;Playlist artwork&quot;
        src=&quot;/blog/static/6d1b13fe2ce4f70b4ddc1de2b1b1db60/bad17/playlist.png&quot;
        srcset=&quot;/blog/static/6d1b13fe2ce4f70b4ddc1de2b1b1db60/9ec3c/playlist.png 200w,
/blog/static/6d1b13fe2ce4f70b4ddc1de2b1b1db60/c7805/playlist.png 400w,
/blog/static/6d1b13fe2ce4f70b4ddc1de2b1b1db60/bad17/playlist.png 477w&quot;
        sizes=&quot;(max-width: 477px) 100vw, 477px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Special Playlist&lt;/h3&gt;
&lt;p&gt;This image is for special playlists. It includes additional indicators, a blurred image stack, and a circular artist image.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 475px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b99d84ea7234658fde470ebcfe59948c/aff45/artist-station.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 127.36842105263158%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGX0lEQVQ4y4WVeVBUVxbGOzOVSZXEmjWxxo2AY9hEEG0WMcE4iohYDhiUBgRkFRCIghBAbBZZRaGiYRINBEHRDGSgHWMgqGhAlIZWjAsUPHYTESMSF2psfP7mdVNY/JGquVVfnXPuOfc757536xwZEP3y5cv0iYmJvS9evFBOYWJKF8VJTPO9ipmYUEpnlRKH3tbpsp7+wYfHq89Scqqa0n/VTKJSxbGq0xyTZOnJKkpPfU2ZZJdX/YcyaU+Hch10MVL8k2fjTC1Zw+WrnUE7kwiL2/NfCdrtu1O0OhkQuUur2OqvDQ0J1kZHRWm374jRhsTEayMkf2TCXm1k/KSMkGRoVKz2TkenVuLTyprVGiFWmU2cMkvcnZrNrj0ZxKflcODocU5800D1d5eouXCFyvrLHD72FYkZucTtzeDj9BwSUrNI2peH59ZAVjm70Nvbi55wt+RISMsWY/ekk5Z/iJrvNbT0/kxplYqTeXHc/PYEzdfv0Nz1E982t5NZ8CnxEmmSRJosJfAPjcB88RJa1GpkV1s1QqIumzJTTMsr5LymgyudA6h7RyjISqU4zJX6go+orSqlpetHNN13abzRRWb+JySlZaLM2k9ASDhL5HZoNNeQtbRdE1Iyc0lU7hNr6htp7xvm6i2BjuEnnCgq5FDgOtqKUrj0jYqbd0dp6+jhzsB9zjY0k5y6j/Sc/QSGRbBkmS1tGg2yVs11ISUjm7zCw2J7Vz8/CIPc7LmLukOgcN8esj3t6P76n3TfukFnTz/Cjz/TOfATt4R+Dh4qIi0zh5DwSGzktlKFOsK2NiFxTzIl5RVi771RuodG+O7COYqOFBEXH8UXykh6GlQ01qm4pm6isbGOmx0djIyNU1ZxCmVaBtsjd7BUf2WJsKnxnBAe4kl+1k6x4cxn1FR9Ts7BHCpPFvNZSRGqohxKshOprz1Nxw9tZObuIiHBi4aLKgo+ySM8IpDA4CDkdvaThBfOqQTFJgditjuLh/O3kBDrTvHRIi5fquVy0znqzpzmy+IjXDx3hruDfZz891E2+djxcZIHXgonXN0cWL9hNUuXLZv8KRfqVYKnuxwfxXviwVxfio9kob5wllpVBUN93RxMT6QwW8m1Kxep/qoUTXMdaSkK9qX54OGxnLUuNjgsN+ddE2Ppp7TpKjwtBChWssX9ffFo/i6OJHlTkxVBSfI2zn95gLL9yRzZn0JBZhzq08fpbqomJtoFf/+VrF+3FPeN9jguN2PuvLdQq1uQfd9wVogO2kDgh6vEUM8PSIrwYLOU9Vh6JMkKRz6P2kRXTRndtWU0VBzmRkM1BXkxuKy1wcnJAjdXOaucLJkvEba0XJUedlO9kByjYMe2jeLaFZbY2y7CYbUjnxZ/gf2aFXz4vgmq1FACvNdjaGFIVmo8B9KT8NnszAY3O7w8V+K8yoZ3DN+erLC1+byQusuXYF830dbeisVWpqx1Xomruxsmi//GYmtjHOQLMFlkhNnihVhZmUhkG4mNDiYy1J2dEf/AfYMjC4z+OknY3FArJIV7EqRwFeVyCyyXmPPe352wsbVh7vxZ2Fkt4AO5KTaWCzA0noPRwnksd7Rhm687H4Xp4IKXxwrMTQ1pbZUIB3q7hNzkaII8ncU1K+VYWC7EzMoMC2sLrK1NkduYYL/UFHNzI+YazWae8WzMLIxZt8aBbYrVBEpPx2P9MmykxJq2VmRSDxP6e7rx3eQqWpvMZ/asPzDjzTeYMXMGM39vwJ//aMCsv8zkrT8ZYGDwOr95/TV++7vXeHPmG7wz/20sTeew6N1ZzJllgLrlCjKpdQu6TiuxixFhQURuD8Fvqw/ePgq8fX3w89+Kv58vgdv8CPD3RaHYgucWTzZL8Pb2IjgogJAgP2Kiwhi5fx+ZKIqCNBvQarWiTkoJXrVzaUbw7Nkz/d7z58/19v9bsoGBAaG8vJz8/HyxoqKCyspK6urqKC4upqSkhOrqaqqqqvR6WVkZjx490h+cSjw6Osovv4zpdak4ZOPj48KTJ090DnFsbIwHDx7oodN1wbrKHj9+zMjICA8fPtTbU9Xr1tOnT5E4Xu3JpCsJQ0ND3Lt3T+zp6dFJPZFUOX19fQwODjI8PKw/OP1TTJfTdZl0BaGzsxNBEMT29nZ0um7YqKX5cP36dTqk3nf79m19ol8j08np+v8Aj29t6r+PAWEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Special Playlist Artwork&quot;
        title=&quot;Special Playlist Artwork&quot;
        src=&quot;/blog/static/b99d84ea7234658fde470ebcfe59948c/aff45/artist-station.png&quot;
        srcset=&quot;/blog/static/b99d84ea7234658fde470ebcfe59948c/9ec3c/artist-station.png 200w,
/blog/static/b99d84ea7234658fde470ebcfe59948c/c7805/artist-station.png 400w,
/blog/static/b99d84ea7234658fde470ebcfe59948c/aff45/artist-station.png 475w&quot;
        sizes=&quot;(max-width: 475px) 100vw, 475px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;These images look great, but as you might guess, they’re all backed by code, and the implementation wasn’t exactly straightforward.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“With great design comes great code!”&lt;/em&gt; —Someone’s uncle&lt;/p&gt;
&lt;h2&gt;From Design to Code Implementation&lt;/h2&gt;
&lt;p&gt;Want to know how we did it? This section will cover how we went from the design ideas to implementing them in code.&lt;/p&gt;
&lt;p&gt;First off, all images now have rounded corners. This is thanks to &lt;code class=&quot;language-text&quot;&gt;ShapeableImageView&lt;/code&gt;, which supports rounded images.&lt;/p&gt;
&lt;p&gt;Playlists show the main artwork and two images behind the main artwork, and each image has rounded corners. We accomplished this by creating an XML file containing a &lt;code class=&quot;language-text&quot;&gt;ConstraintLayout&lt;/code&gt; with three children of &lt;code class=&quot;language-text&quot;&gt;ShapeableImageView&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;ConstraintLayout&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;stack2&amp;quot; /&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;stack1&amp;quot; /&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;playlistArtwork&amp;quot; /&amp;gt;
&amp;lt;ConstraintLayout/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We have some special types of playlists that can have additional overlays/blur or more images on top of the main artwork. For special playlists (see the &lt;a href=&quot;#special-playlist&quot;&gt;Special Playlist&lt;/a&gt; section), we added another &lt;code class=&quot;language-text&quot;&gt;ShapeableImageView&lt;/code&gt; as an overlay to the &lt;code class=&quot;language-text&quot;&gt;ConstraintLayout&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;visibility=&amp;quot;gone&amp;quot;&lt;/code&gt;. If a playlist is “special,” the &lt;code class=&quot;language-text&quot;&gt;ShapeableImageView&lt;/code&gt;s &lt;code class=&quot;language-text&quot;&gt;visibility&lt;/code&gt; is changed to &lt;code class=&quot;language-text&quot;&gt;visible&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;ConstraintLayout&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;stack2&amp;quot; /&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;stack1&amp;quot; /&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;playlistArtwork&amp;quot; /&amp;gt;
    &amp;lt;ShapeableImageView id=&amp;quot;playlistTypeIndicator&amp;quot; visibility=&amp;quot;gone&amp;quot; /&amp;gt;
&amp;lt;ConstraintLayout/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For playlists, the primary colors of the main artwork determine the image stack colors. We used the [Android palette library][] to make this happen. It has a simple API that picks the primary colors from images, which we can use as background colors for the stack.&lt;/p&gt;
&lt;p&gt;Additionally, images fade in when loaded. We use the &lt;a href=&quot;https://github.com/square/picasso/blob/master/picasso/src/main/java/com/squareup/picasso3/PicassoDrawable.kt&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PicassoDrawable&lt;/code&gt;&lt;/a&gt;, which crossfades the newly loaded bitmap with whatever image was previously loaded. To highlight the fade-in animation, the duration of the animation was set to 200 milliseconds.&lt;/p&gt;
&lt;p&gt;Placeholders are displayed while the actual images are downloading, and &lt;code class=&quot;language-text&quot;&gt;com.squareup.picasso.Target&lt;/code&gt; provides a callback function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;fun onPrepareLoad(placeHolderDrawable: Drawable?)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;onPrepareLoad&lt;/code&gt; enables us to add a gray color to the stacks and a default image while waiting for the main artwork to load.&lt;/p&gt;
&lt;h3&gt;Loading and Displaying Images with Picasso&lt;/h3&gt;
&lt;p&gt;We use &lt;a href=&quot;https://square.github.io/picasso/&quot;&gt;Picasso&lt;/a&gt; to load images. Picasso also offers a simple API, &lt;code class=&quot;language-text&quot;&gt;picasso.load(url).into(target)&lt;/code&gt;, to download and cache images.&lt;/p&gt;
&lt;p&gt;The image cache is used throughout the whole app so that an image in the cache will never download twice. Picasso can download or load an image from the local disk (cache).&lt;/p&gt;
&lt;p&gt;Picasso is extendable for custom image loading operations. For example, if image loading fails, we want to display an error image. There are a few different ways to do that with Picasso. At SoundCloud, we have a custom &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt; that implements &lt;code class=&quot;language-text&quot;&gt;Picasso.Target&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Every time we load an image into the &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt;, we animate (fade in) the new image bitmap:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class ArtworkImageView : Target {
    override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
        animateBitmap(bitmap)
    }

    override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) {
        animateFailureBitmap()
    }

    override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
        showPlaceholderBitmap()
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After implementing the redesign, the images looked fantastic, the design team approved, and the product managers were happy. But it came with a catch: Images were loading slower than before. The slow performance was visible while scrolling, and images flickered when moving between screens. The redesign had impacted image performance.&lt;/p&gt;
&lt;p&gt;The following section will cover how we fixed the image loading performance.&lt;/p&gt;
&lt;h2&gt;Slow Image Performance Investigation and Fixes&lt;/h2&gt;
&lt;p&gt;We started debugging and discovered not a single culprit, but rather a bunch of issues that were contributing to the performance problem. We also investigated the app’s GPU overdrawing, which I’ll elaborate on later. The next sections outline the specific issues we encountered and how we fixed them.&lt;/p&gt;
&lt;h3&gt;Issue #1 — Palette LruCache&lt;/h3&gt;
&lt;p&gt;To generate the image stack colors, we use the Android palette library. To save time on regenerating the palette, we used &lt;code class=&quot;language-text&quot;&gt;LruCache&amp;lt;Bitmap, Palette&amp;gt;&lt;/code&gt; to store the bitmap and palette key-value pair for faster on-demand loading.&lt;/p&gt;
&lt;p&gt;So every time we loaded an image, the code below executed:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;if (isInCache(bitmap)) {
    return getTheCachedPaletteFor(bitmap)
} else {
    val palette = generatePaletteFrom(bitmap)
    storePalette(bitmap, palette)

    return palette
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using a &lt;code class=&quot;language-text&quot;&gt;Bitmap&lt;/code&gt; as a key, we retrieved the palette from &lt;code class=&quot;language-text&quot;&gt;LruCache&lt;/code&gt;. However, using bitmaps as keys to the cache caused a performance issue. Given that bitmaps are large objects, it’s best to never use them as keys to a &lt;code class=&quot;language-text&quot;&gt;Cache&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Map&lt;/code&gt;, or any other data structure. This is because comparing bitmaps is time-consuming.&lt;/p&gt;
&lt;h3&gt;Solution #1&lt;/h3&gt;
&lt;p&gt;The fix ended up being simple. We switched from &lt;code class=&quot;language-text&quot;&gt;LruCache&amp;lt;Bitmap, Palette&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;LruCache&amp;lt;String, Palette&amp;gt;&lt;/code&gt;. The &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt; is the URL of the loaded bitmap, and comparing strings is quick and consumes less memory.&lt;/p&gt;
&lt;p&gt;The code is below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;if (isInCache(url)) {
    return getTheCachedPalette(url)
} else {
    val palette = generatePaletteFrom(bitmap)
    storePalette(url, palette)

    return palette
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Issue #2 — Palette Color Generation&lt;/h3&gt;
&lt;p&gt;To generate the palette colors from bitmaps, we used the code below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Palette.from(bitmap).generate()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code is simple: It provides the bitmap and generates the primary colors for that bitmap. But palette generation takes time. It works by checking each pixel in a bitmap to find the most used colors. For example, if an image contains a lot of red, then the palette will contain the red color.&lt;/p&gt;
&lt;p&gt;The bitmaps we use to generate the palette have a size of 500×500, which is large and slows the palette generation.&lt;/p&gt;
&lt;h3&gt;Solution #2&lt;/h3&gt;
&lt;p&gt;A faster way to generate the palette is to first scale down the bitmap and then generate the palette.&lt;/p&gt;
&lt;p&gt;The palette has a function, &lt;code class=&quot;language-text&quot;&gt;resizeBitmapArea(size)&lt;/code&gt;, that scales the bitmap down, so we went from 500×500 to 15×15. We also use &lt;code class=&quot;language-text&quot;&gt;maximumColorCount(colors)&lt;/code&gt; to find fewer colors, as in our implementation, we only needed the dominant color. By default, the palette finds 16 different colors.&lt;/p&gt;
&lt;p&gt;The code below generates palettes faster, but the tradeoff is primary color accuracy, which is ultimately fine; shades of red are still red:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Palette.from(bitmap)
    .resizeBitmapArea(225) // Scales down the bitmap area to a 15×15.
    .maximumColorCount(6)
    .generate()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Issue #3 — Animating from the Placeholder to the Real Image&lt;/h3&gt;
&lt;p&gt;When loading images the first time, we displayed a placeholder and then displayed the actual downloaded image with a fade-in animation. Once the image was cached and the user scrolled through the &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;, we still saw the fading in and out from the placeholder to the actual image, even though the image was cached.&lt;/p&gt;
&lt;h3&gt;Solution #3&lt;/h3&gt;
&lt;p&gt;To address this, we dropped animations for cached images. Luckily, Picasso offers the &lt;code class=&quot;language-text&quot;&gt;from&lt;/code&gt; enum, &lt;code class=&quot;language-text&quot;&gt;Picasso.LoadedFrom&lt;/code&gt;, together with the loaded bitmap. &lt;code class=&quot;language-text&quot;&gt;Picasso.LoadedFrom&lt;/code&gt; tells us where the bitmap was loaded from:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;enum LoadedFrom {
    MEMORY
    DISK
    NETWORK
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the image was loaded from &lt;code class=&quot;language-text&quot;&gt;NETWORK&lt;/code&gt;, we animate the new image; otherwise, we display the image without an animation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ArtworkImageView : TrackArtwork {

	override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
            if(from == Picasso.LoadedFrom.NETWORK) {
                animateBitmap(bitmap) // Animate from placeholder to real image.
            } else {
                showBitmapWithoutAnimation(bitmap) // Directly display image from cache.
            }
        }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Another minor tweak was that we made the fade-in animation faster — from 200 ms to 60 ms. 200 ms felt like we were showing off our fancy animations rather than benefitting the user by displaying the image quickly.&lt;/p&gt;
&lt;h3&gt;Issue #4 — Image Preloading in RecyclerView&lt;/h3&gt;
&lt;p&gt;Many screens in the SoundCloud app consist of lists of tracks or playlists, which means the user will scroll through the list to play a track. While the user scrolls, the new images are loaded and cached. Downloading new images takes time, so we thought it’d make sense to download and cache a few of the upcoming images in a list before the images of tracks/playlists are even displayed on the screen.&lt;/p&gt;
&lt;h3&gt;Solution #4&lt;/h3&gt;
&lt;p&gt;We preloaded the three subsequent images in &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;s. If you use &lt;a href=&quot;https://bumptech.github.io/glide/int/recyclerview.html&quot;&gt;Glide&lt;/a&gt;, you’ll know it supports preloading images out of the box.&lt;/p&gt;
&lt;p&gt;Since we use Picasso for image loading, we implemented image preloading based on Glide’s example.&lt;/p&gt;
&lt;p&gt;When preloading items, we needed to consider the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How many not-yet visible items do we want to preload? In other words, how many items did we want to preload? These items are always not visible until the user scrolls down to them.&lt;/li&gt;
&lt;li&gt;How many items are left to preload? We needed to check if we were about to reach the end of the &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;; for example, if we were always preloading the next five items and reaching the last bottom four items, we needed to stop preloading toward the end of the &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make sure we don’t preload items twice.&lt;/li&gt;
&lt;li&gt;Only load items that aren’t yet visible on the screen. If we hardcode the preload size to 5 items but show 10 items, we end up preloading items that were already loaded.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below is the &lt;code class=&quot;language-text&quot;&gt;ItemPreLoader&lt;/code&gt; class, which handles all the points mentioned above:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;const val DEFAULT_PRELOAD_ITEMS_SIZE = 3

/***
 * The `ItemPreLoader` calculates which items should be preloaded.
 *
 * Items can only be preloaded when the conditions below are met:
 * 1. `preloadEnabled` is `true`, meaning that `PreloadScrollListener` has enabled preloading.
 * 2. `lastVisiblePosition` is larger than `0`. The first few items are already preloaded by the adapter, and if we try to load more items immediately, it could overwhelm the app&amp;#39;s memory.
 * 3. `lastVisiblePosition` is smaller than `items.size`. We shouldn’t continue preloading elements outside the bounds of the list (`IndexOutOfBoundsException`).
 * 4. `startIndex` is larger than `lastPreloadedPosition`. Already preloaded items must not be loaded again.
 * 5. The list isn’t exhausted; there are more items. Same as 2.
 * 6. The last visible item’s `ViewType` must be the same as the `ViewType` of the item we’re currently preloading. Otherwise, we&amp;#39;ll cause a `ClassCastException` if the `ViewTypes` are different. `getItemViewType(lastVisiblePosition) == getItemViewType(realIndex)` ensures we don&amp;#39;t cause a `ClassCastException`.
 *
 * If the above conditions are met, `ItemPreLoader` slices the items list from the `adapterPosition`(`startIndex`) to the `lastIndex`(`startIndex + preloadItemsSize`) and calls `viewHolder.preloadNextItem` on those items.
 */
class ItemPreLoader(
    val preloadItemsSize: Int = DEFAULT_PRELOAD_ITEMS_SIZE
) {
    var preloadEnabled = false

    internal var adapterPosition = 0
    private var lastPreloadedPosition = -1

    fun &amp;lt;T&amp;gt; preLoadItems(
        viewHolder: ScViewHolder&amp;lt;T&amp;gt;,
        items: List&amp;lt;T&amp;gt;,
        position: Int,
        getItemViewType: (Int) -&amp;gt; Int
    ) {
        val lastVisiblePosition = max(adapterPosition, position)

        /*
            - `preloadEnabled` must be `true` to continue preloading items.
            - `lastVisiblePosition` can be set to `-1` if there are no items in the adapter. In this case, we don&amp;#39;t want to preload items.
            - `lastVisiblePosition` can be set to `0`. We don&amp;#39;t want to start preloading immediately when creating an adapter,
                and the adapter already loads the next item(s) to be shown.
         */
        if (!preloadEnabled || lastVisiblePosition &amp;lt;= 0) {
            return
        }

        val startIndex = if (preloadItemsSize &amp;lt; lastVisiblePosition) {
            lastVisiblePosition + 1
        } else {
            preloadItemsSize
        }

        val lastPreloadIndex = (startIndex + preloadItemsSize - 1).coerceAtMost(items.lastIndex)

        if (startIndex in lastPreloadedPosition until lastPreloadIndex) {
            lastPreloadedPosition = lastPreloadIndex
            var realIndex = startIndex

            items.slice(startIndex..lastPreloadIndex).forEach {
                if (getItemViewType(lastVisiblePosition) == getItemViewType(realIndex)) {
                    viewHolder.preloadNextItem(it)
                }

                realIndex++
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We also needed to set a custom &lt;code class=&quot;language-text&quot;&gt;OnScrollListener&lt;/code&gt; to the &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;. The &lt;code class=&quot;language-text&quot;&gt;OnScrollListener&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enables item preloading when the user scrolls down.&lt;/li&gt;
&lt;li&gt;Disables item preloading when the list is idle or the user scrolls up.&lt;/li&gt;
&lt;li&gt;Finds the last visible item position in &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class PreloadScrollListener(val itemPreLoader: ItemPreLoader) : RecyclerView.OnScrollListener() {

    /**
     * The scroll state can only have the following three values:
     * 1. `SCROLL_STATE_IDLE` is when the user isn&amp;#39;t scrolling, and it enables image preloading.
     * 2. `SCROLL_STATE_DRAGGING` is when the user is scrolling with their finger on the screen, and it enables image preloading.
     * 3. `SCROLL_STATE_SETTLING` is when the user fling scrolls, and it disables image preloading.
     */
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)

        when (newState) {
            SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING -&amp;gt; itemPreLoader.preloadEnabled = true
            SCROLL_STATE_SETTLING -&amp;gt; itemPreLoader.preloadEnabled = false
        }
    }

    /***
     * There&amp;#39;s a special case where the user scrolls upward. In this case, image preloading is disabled.
     *
     * The `PreloadScrollListener` also sets the `ItemPreLoader.adapterPosition`.
     * The `LastVisibleAdapterPositionFinder` class grabs the `LayoutManager` from the `RecyclerView`, and from the `LayoutManager`, we find the last visible item position.
     */
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        if (dy &amp;lt; 0) {
            itemPreLoader.preloadEnabled = false
        }

        val position = findLastVisiblePosition(recyclerView)
        itemPreLoader.adapterPosition = position
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, here’s the code to find the last visible item in the &lt;code class=&quot;language-text&quot;&gt;RecyclerView&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;fun findLastVisiblePosition(recyclerView: RecyclerView): Int {
    return when (recyclerView.layoutManager) {
        is LinearLayoutManager -&amp;gt; getLastVisiblePositionFrom(recyclerView.layoutManager as LinearLayoutManager)
        else -&amp;gt; -1
    }
}

fun getLastVisiblePositionFrom(manager: LinearLayoutManager): Int {
    return manager.findLastVisibleItemPosition()
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;GPU Overdrawing&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering#debug_overdraw&quot;&gt;GPU overdrawing&lt;/a&gt; is when an app draws the same pixel more than once within the same frame. As a result, GPU overdraw visualization shows where an app might be doing more rendering work than necessary.&lt;/p&gt;
&lt;p&gt;We caused a high overdraw severity because we were rendering/drawing three &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt;s on top of each other in the stacked &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt;s when displaying playlists. This is something we shouldn’t do.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/933953b257a06d8b925be681264b7f58/bb59d/over-draw.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.409638554216876%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACv0lEQVQozzWRW0jTcRTHV0RBRheIoLeCqB4iih56qad6inqJwiLthrdMErJSy2Z0I9AIu5g3NK20XOqcbe7qnG2mU4PS0jXTvCx1uvucuc19+s/qB9/fw/me8z3fc47I5w8zbHcz6nBjc/sYc3oYmXYxNOXA5fHTO2SnXN7JI80n0hvayZB2UKjqpqReT9/INEPWUc6nXSY96wbjtl+Iptwhfvab8bZpGZRJmGxpxm824jHpaNereNLUybHEdCpyH3Ei4yGnMvIpe1BI5ukEiqsbeNuoY+1SEftXLsfy5TMiuzfIdJ8ZV/UbXp07iyYrC1oNoNPQppBS8+E7Vy6JkaXdRXW/CKkAU95jCtKyeV5Zy0dTB4c3r+dYzGqG+3oFh54gMwM9/Kp6iflhHl35eXjlcjDoMShl1Gs/k38xl/cXxHzMvEZX9mWMd+5RnnaTiqJS2owdxO3ezsWtexgbsEQdhpgUHAaaZExKarEUl/C9rJSFFg1GlVzYn56kuOO8OHIYXU4GhttiylMTyDuwl7LCAsoa9OzasI7kTdsY6e//K2gXBNEb+K3WMF37juGKF0SEkU1qJVVKLSmJhyiO3YFUnErjrUs8i99J7r7NVJU+pbhOy5aYJRxdtYYfvcLIdk+IoZ5WAmoZPr0Cf4sCnwC/rgm1XEKd9gs5V8WknorlTIJwzeR0spPiuX86hZoaCSXvVGxcJuLgiqVYo0cJh8Fo7qFRIadJqaSxuXkRUsV7JHItM45Z2rq/Uq7spFLZTlWzidfqLt6q21GY+piy+7l+8zaxJ+MZHRtD5PN5sVgGccx4mZsLEwxCtEkoBETA5/UwaP3BfCBIeH4BFv7Go4gIef/f+PiUUBtCFAgEsNlsOJ0zuN0uQSz0LyWy+M/OzjIxOSHwDpwup9AouMhFIguL/Nzcb6xWKwMD34hq/QGMhHMQ6I5uYAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Overdraw&quot;
        title=&quot;Overdraw&quot;
        src=&quot;/blog/static/933953b257a06d8b925be681264b7f58/8ff1e/over-draw.png&quot;
        srcset=&quot;/blog/static/933953b257a06d8b925be681264b7f58/9ec3c/over-draw.png 200w,
/blog/static/933953b257a06d8b925be681264b7f58/c7805/over-draw.png 400w,
/blog/static/933953b257a06d8b925be681264b7f58/8ff1e/over-draw.png 800w,
/blog/static/933953b257a06d8b925be681264b7f58/6ff5e/over-draw.png 1200w,
/blog/static/933953b257a06d8b925be681264b7f58/bb59d/over-draw.png 1328w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;The Issue&lt;/h3&gt;
&lt;p&gt;We draw three full images on top of each other, even though only 10 percent of the stacked images are visible. With stacks, 90 percent of an image isn’t visible, but it’s still drawn, which affects the frame rate.&lt;/p&gt;
&lt;p&gt;To fix overdrawing, we would’ve needed to add a lot of custom complex drawing logic, which would take time to build, debug, and maintain. So the tradeoff here was unlikely to be worth the upfront effort and maintenance difficulties.&lt;/p&gt;
&lt;p&gt;We did build the custom drawing logic, but it was too complex to maintain, and there was no performance benefit.&lt;/p&gt;
&lt;p&gt;The main ideas were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flatten the stacked &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt;s into one &lt;code class=&quot;language-text&quot;&gt;ImageView&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Draw only the visible areas of bitmaps.&lt;/li&gt;
&lt;li&gt;Faster image drawing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code below draws three bitmaps on a custom view. Only the bitmaps’ visible areas are drawn, so as to avoid GPU overdrawing. Since only a subset (10 percent) of the bitmaps is drawn, drawing is now faster:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;StackedImageView: View() {

    val visibleBitmapOffset = 40f

    override fun draw(canvas: Canvas) {
	// Show the bitmap area calculations.
	val bounds = RectF(0, 0, width, height)

	val paint = Paint()
       	paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)

        canvas.drawBitmap(topStackBitmap, null, topArea(bounds), paint)
        canvas.drawBitmap(middleStackBitmap, null, middleArea(bounds), paint)
        canvas.drawBitmap(bottomStackBitmap, null, bottomArea(bounds), paint)
    }
}

private fun topArea(bounds: RectF) = bounds.copy().apply {
        right -= visibleBitmapOffset * 2
        bottom -= visibleBitmapOffset * 2
    }

    private fun middleArea(bounds: RectF) = bounds.copy().apply {
        top += visibleBitmapOffset
        right -= visibleBitmapOffset
        left += visibleBitmapOffset
        bottom -= visibleBitmapOffset
    }

    private fun bottomArea(bounds: RectF) = bounds.copy().apply {
        top += visibleBitmapOffset * 2
        left += visibleBitmapOffset * 2
    }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code above calculates the bounds (area) of each bitmap. The bounds are the area into which the bitmap will be drawn. The &lt;code class=&quot;language-text&quot;&gt;visibleBitmapOffset&lt;/code&gt; defines how many pixels the bounds will be moved/offset.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the top stack bitmap area, the bitmap is displaced by &lt;code class=&quot;language-text&quot;&gt;visibleBitmapOffset*2&lt;/code&gt; pixels toward the top-left corner.&lt;/li&gt;
&lt;li&gt;In the middle stack bitmap area, all sides are moved by &lt;code class=&quot;language-text&quot;&gt;visibleBitmapOffset/2&lt;/code&gt; pixels, placing the bitmap in the middle of the view.&lt;/li&gt;
&lt;li&gt;In the bottom stack bitmap area, the bitmap is displaced by &lt;code class=&quot;language-text&quot;&gt;visibleBitmapOffset*2&lt;/code&gt; pixels toward the bottom-right corner.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To draw only the visible bitmap areas and avoid overdrawing, we use &lt;code class=&quot;language-text&quot;&gt;paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PorterDuff.Mode.DST_ATOP&lt;/code&gt; draws only the outside area of the following image. So if there is already something drawn in the view, then &lt;code class=&quot;language-text&quot;&gt;DST_ATOP&lt;/code&gt; only draws the outer part of what we’re drawing now. Since we already drew the top bitmap, only the middle bitmap’s outer part is drawn. It’s the same with the bottom bitmap; only the outer section is drawn.&lt;/p&gt;
&lt;p&gt;The section above outlines the basics of how to draw three images without overdrawing. Complications arise when the images have round corners; the stacks can be solid colors; and there can be more images on top of the stack, fading in each stack of bitmaps and drawing rounded borders. Implementing all of that led to a great deal of drawing code, which was hard to maintain. Thus, it wasn’t included in the project.&lt;/p&gt;
&lt;p&gt;If you want to learn more about computer graphics drawing, &lt;a href=&quot;https://www.amazon.com/Fundamentals-Computer-Graphics-Steve-Marschner/dp/1482229390&quot;&gt;&lt;em&gt;Fundamentals of Computer Graphics&lt;/em&gt;&lt;/a&gt; is a great book.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Redesigning an entire app takes a lot of time — from product managers coming up with the product specifications, whiteboarding sessions, and creating wireframes, to UI and UX designers drawing the final design. Finally, the engineers have to implement the new design and rewrite UI components, and usually, all this work is done under pressure with a set deadline.&lt;/p&gt;
&lt;p&gt;We all like to go fast and get a redesign done, but as a result, we end up rushing and forgetting the important stuff like properly testing the new UI to see if it works, and checking to ensure the performance and frame rate are acceptable. Testing and measuring performance is the responsibility of the engineers, so it’s of the utmost importance to always test and measure the performance of your new UI.&lt;/p&gt;
&lt;p&gt;With proper performance measuring and testing, we were able to identify and fix image UI issues we encountered. And although it took longer, SoundCloud ended up with a more polished product.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Echo: Next-Level Humane Registry with Backstage]]></title><description><![CDATA[We’ve seen considerable adoption of our internal humane registry, Services Directory, since we sanitized ownership definition and introduced…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-echo-next-level-backstage</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-echo-next-level-backstage</guid><pubDate>Mon, 19 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 640px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABtElEQVQoz52Su0oDQRSGzyaKNkZQ0CBprCWKD2DjrTEmM7sbE2MMGBG8JCiIYOFKQEXxERSJ4CNoobWNvS+wEdE+Eewy/jOZTdZr4YGfMxf45j9zDhFCJIm8LDW8eqL2K0t5shknL5KmRbnsYnO/V60YKtcq5ECfIHWZbTKwDspMvjATjGxu+o/IeXsKqFxzO5pnElq3FTBAX0JwMli+2AaHIZ/D7mxyLnghhHrQqVYGASlCM1AnfYPYNAb4OdxG1D5PAZOZkURstguwkMV4RAihy3UjgJQgjvUOnE57JXdBE9AedCMWSQD8Cui49xCPx6NsNj4k17vPj4Yu7xgOk8gm8rVTdUc8Vz0ArUBFlH8E0ENdAm1alvcFPt2HPwwDGOaM9bYa4kp3t1AZOoSG6KfAA+0vcyHlYjtrtZuM93t3FufhXGG9TQMn4OoKpV6j21Ht2mg0ResTGE3dKp2qdS6TaY1K+Ywc7x9r7gCAo74xao1NUxr+nmpMQ4olKD4To4X0PE1NTqqz/fs7OXvN0WrMoUu/xmahqPLG2jpZvsHOpNNUOCjRpXaIZhhaeg7/gP43PgCs8d4PCc34WQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud adopted Backstage&quot;
        title=&quot;SoundCloud adopted Backstage&quot;
        src=&quot;/blog/static/9d4acf0fb6371670e9a76109a8cadfd4/e49a9/SC-BS.png&quot;
        srcset=&quot;/blog/static/9d4acf0fb6371670e9a76109a8cadfd4/9ec3c/SC-BS.png 200w,
/blog/static/9d4acf0fb6371670e9a76109a8cadfd4/c7805/SC-BS.png 400w,
/blog/static/9d4acf0fb6371670e9a76109a8cadfd4/e49a9/SC-BS.png 640w&quot;
        sizes=&quot;(max-width: 640px) 100vw, 640px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We’ve seen considerable adoption of our &lt;a href=&quot;obvious-ownership-humane-registry&quot;&gt;internal humane registry, Services Directory&lt;/a&gt;, since we sanitized ownership definition and introduced an easier-to-use user interface (UI) to our development infrastructure. Namely, managing backend feature toggles (or as we call them, &lt;code class=&quot;language-text&quot;&gt;rollouts&lt;/code&gt;) and visualizing interservice dependencies has been helpful in bringing engineering managers, product managers, and engineers closer — not only in their day-to-day work, but also when communicating with other teams in the organization.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 78.89273356401384%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAAB60lEQVQ4y41TiY6bMBDl//9pq3YrrZS2qnqxOZaEBDAbTnOTgOHVNkdYglY70sjjwX68mXlWwK1tW0ytaRpUVSX9cq1wfKXQucvV7tY9CXEOUn6WyXN1XUscRQB4nofNZgPXdSWgOGCaJmxiwY8yfP72jIfHJ3x5+oFPX1dyfXhc4fuzCRqG0HUdURTJuxKQMYbr9Sr/MjAUschXNYNDc9h+CtNNpFteCoOvQVygYR1DcVYynJc73Q9RVtaI8wofMWUAGXy6b/r9i0nxR3NH9k3TfWsXXLkxwx2gsKEVAkjE00qGYqZVKbdEe/dxv98jTTO82gSn01HGYniiZ0NTpiRGwIEhJmBlWeJyucg4ilO4AR3zIZ9sWfHBsfYdhn0JZz/CVrdgkDM6BTRQCcMvk0kQYTSrsDrU0BzWs5wxHAF57NMExAmQZEU3BJ7XA0DzuD57RkXVYuMAJMIy4HwQb18N16HjwLII6r53RZHD4ML3+EOYD1KZDmRJQkKwlFLet2AUvuit7/tIkuSOxChsM9dh5Ic3TV5ivPQQRGpHVZwLcitZz16kzy/OhbtUhbBtqMIpbChWfsIuUXHMNOjpDlqy5kz1d/s6ze9iVVb2L/opCXHAI7bJ35Ghlq5l+R8FPHASpDCwjn/Le/8Bz5vca70yuJkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dependency diagram&quot;
        title=&quot;Dependency diagram&quot;
        src=&quot;/blog/static/bdd1a3a77e3906487b36ba618984c6cb/8ff1e/sd-dependency-diagram.png&quot;
        srcset=&quot;/blog/static/bdd1a3a77e3906487b36ba618984c6cb/9ec3c/sd-dependency-diagram.png 200w,
/blog/static/bdd1a3a77e3906487b36ba618984c6cb/c7805/sd-dependency-diagram.png 400w,
/blog/static/bdd1a3a77e3906487b36ba618984c6cb/8ff1e/sd-dependency-diagram.png 800w,
/blog/static/bdd1a3a77e3906487b36ba618984c6cb/a9dd4/sd-dependency-diagram.png 867w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;With higher engagement, we also got feedback and tons of ideas for extending Services Directory and making it even more useful.&lt;/p&gt;
&lt;p&gt;When talking to engineers at other tech companies, and also internally to our recent hires, we recurringly heard “organizing the software inventory” to be a repeated problem companies solved in different shapes and forms, just like we did. At approximately the same time we were investing in Services Directory, the industry saw the emergence of open source alternatives for building developer portals. Our team analyzed options, weighed the cost of maintaining our own Services Directory, and finally decided to migrate (and contribute) to externally developed software.&lt;/p&gt;
&lt;p&gt;In this post, we’ll describe how we took Services Directory to the next level and migrated our internal system to the open source &lt;a href=&quot;https://backstage.io/docs/overview/what-is-backstage&quot;&gt;Backstage developer portal technology&lt;/a&gt;, and how we’re now providing a lot more capabilities and empowering our engineering team, in turn speeding up software delivery and engineering effectiveness.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/f7efd23e7cf13d8a58c25c74e082dd5d/backstage-logo.svg&quot; alt=&quot;Backstage logo&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Adoption Path&lt;/h2&gt;
&lt;p&gt;Like most pieces of work that influence other teams’ workflows and critical-path tooling, we wrote &lt;a href=&quot;https://philcalcado.com/2018/11/19/a_structured_rfc_process.html&quot;&gt;an RFC document&lt;/a&gt; to collect thoughts and establish a migration path out of Services Directory and into Backstage.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.33110367892976%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACMklEQVQ4y41UR5LbMBDU/+9+h3/gu/YgV22pFFeSmVaBOYJJak8Pl9R6D7ZR1RwQodETgNna6+B4V9i2JbDhui7O72fYlgXPe0eSJCirGl3f4/F44H6/K9jnWFlWyMoI33/m+PajxCwvDJI0Q5YXKGSyrAyMqZFkJdquQyfIq26YLwqZr8SWqGQd+5UxyEuDKKvhJwazJM1lsIapG7GN9nly03a6oWkaVfS31vd3wbB+RnW5nN60LepmIGRL4hiHwwGXywW+7yPLMqRpqpZKCf7T0n2CHs6itIR7y3BLalxjg6IaCM/nM+bzOV5eXrBcLvH6+or1eq12u93ieDxitVqpbUXMRBjGCSxHgp8VqOpW0ChhFEVYLBZKwg3/agwL904uMwHqtgzyNJL4fiD/Nf6n9ULIPMziJMXlekMumaNkxpGNsbIsW8cZV81qWf4JCQ+rYiwlJQzCSAlZFtxcN+1EuD9YONpXhHEGP0oRhuEEP5B9QYFbkKIX7+73x0AYSQxPvyytRZKZjxhSgWUNxe44jtrx37YdvQDjHMPCMCkha4dkBF2rPwjzPMfpdJLb4kmCYk1SIOUzKgyCYBgTq7U6ElIVi5j4rJD1RQUkdF1Hr6H9HsLyInjnG1xRRnWcZwKfhPIxX8BWiXK6NRA+7dgnWPQs7l5u1pQUfqiM2f1MyIWbzQb7/R5vb29qWdC8PezvdrsJRu6zCHy6PJKq1TqEBpovDd3hA0H7tT9ifIW4/zdQ13w9wjF5ZQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;One Pager&quot;
        title=&quot;One Pager&quot;
        src=&quot;/blog/static/4fb347b0ce5847b63a168754c84d7aa3/8ff1e/one-pager-screenshot.png&quot;
        srcset=&quot;/blog/static/4fb347b0ce5847b63a168754c84d7aa3/9ec3c/one-pager-screenshot.png 200w,
/blog/static/4fb347b0ce5847b63a168754c84d7aa3/c7805/one-pager-screenshot.png 400w,
/blog/static/4fb347b0ce5847b63a168754c84d7aa3/8ff1e/one-pager-screenshot.png 800w,
/blog/static/4fb347b0ce5847b63a168754c84d7aa3/e4566/one-pager-screenshot.png 1196w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The key idea and deliverable was to provide a narrow but high-value use case, and only later expand to new features and start evangelizing the platform. As mentioned in our &lt;a href=&quot;obvious-ownership-humane-registry&quot;&gt;last post on the topic&lt;/a&gt;, we wanted Backstage to replace Services Directory in its core value proposition:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The registry must enable finding out which systems a team owns and who the owners of given systems are.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Naming Is Hard&lt;/h4&gt;
&lt;p&gt;As we set out with a plan to build a prototype for what Backstage could look like at SoundCloud, we also had to make a naming decision. Although &lt;em&gt;Backstage&lt;/em&gt; is a compelling name, SoundCloud — being an audio platform — already had at least two other things with that name (including this very blog). We didn’t want to make things even more confusing, so after long deliberation, we settled on &lt;strong&gt;SoundCloud Echo&lt;/strong&gt; for the codename of our Backstage instance. Not only does “echo” have a relation to sound in general, but it’s also one of the very first commands engineers write on their path to software development:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Hello World!&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Bringing In Some Glue&lt;/h3&gt;
&lt;p&gt;We went through a few steps to get to what we considered our adoption milestone.&lt;/p&gt;
&lt;p&gt;Getting started with Backstage meant making our platform team acquainted with how the platform looks and works, along with becoming familiar with its &lt;a href=&quot;https://backstage.io/community&quot;&gt;community&lt;/a&gt;. With access to the maintainers and other &lt;a href=&quot;https://github.com/backstage/backstage&quot;&gt;project&lt;/a&gt; contributors, we also identified opportunities to learn the long-term project roadmap and unblock ourselves if we ran into questions. We were quickly able to identify which bits would smoothly connect to our existing infrastructure and which bits would require some “glue.”&lt;/p&gt;
&lt;h4&gt;Database&lt;/h4&gt;
&lt;p&gt;The first challenge was a fundamental one: the database.&lt;/p&gt;
&lt;p&gt;As described in a previous post, the &lt;a href=&quot;did-i-break-you&quot;&gt;golden paths&lt;/a&gt; at SoundCloud evolved around MySQL as our choice for relational database management systems (RDBMS). We have experts within the company that have plenty of knowledge on how to provision, query, and maintain MySQL clusters. On top of that, we’ve built plenty of supporting infrastructure for this technology.&lt;/p&gt;
&lt;p&gt;What we found, though, is that Backstage can work out of the box with PostgreSQL. Even though &lt;a href=&quot;https://github.com/backstage/backstage/issues/6281&quot;&gt;there’s interest from the open source community&lt;/a&gt; in providing Backstage with support for running on MySQL, it remains unavailable. We ran an internal proof of concept to estimate the work it would take for us to implement that support, but we deemed going forward too risky, especially given that we would’ve had to play catch with the upstream project.&lt;/p&gt;
&lt;p&gt;The alternative was to rely on Cloud resources. Instead of provisioning a PostgreSQL cluster ourselves, we set up a Postgres database node in &lt;a href=&quot;https://cloud.google.com/sql&quot;&gt;Google’s Cloud SQL&lt;/a&gt;, through a proxy deployed as a sidecar container in its Kubernetes pod.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/83cffcf4c964913d17d18936be47eb96/cloudsql-proxy-diagram.svg&quot; alt=&quot;Cloud SQL proxy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://cloud.google.com/sql/docs/mysql/sql-proxy&quot;&gt;Cloud SQL Auth proxy&lt;/a&gt; works by having a local client running in the local environment. Our instance communicates with the proxy through the standard database protocol. The Cloud SQL Auth proxy uses a secure tunnel to communicate with its companion process running on the server.&lt;/p&gt;
&lt;p&gt;This way, our app can use a PostgreSQL database, and we get the benefits of managed resources in the Cloud — for example, automatic backups, data replication, telemetry, etc. Most importantly, engineers operating the project have a less steep learning curve to the PostgreSQL setup. When, in the future, Backstage supports MySQL as a database engine, we can then decide to move over.&lt;/p&gt;
&lt;h4&gt;Catalog Descriptors&lt;/h4&gt;
&lt;p&gt;So we have a storage for the catalog, but how do we get the data in?&lt;/p&gt;
&lt;p&gt;As we saw in &lt;a href=&quot;obvious-ownership-humane-registry&quot;&gt;our previous post&lt;/a&gt;, Services Directory already relied on a background worker job indexing “manifest files,” which were essentially JSON files with a predefined schema. We were positioned especially well for the migration to Backstage because we had done the work to standardize and sanitize all these files across SoundCloud repositories, which meant we could make some assumptions about their shape when ingesting them into our new PostgreSQL database.&lt;/p&gt;
&lt;p&gt;Backstage’s catalog feature is &lt;a href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format&quot;&gt;built around the concept of metadata YAML files&lt;/a&gt;, so our job here was to write a parser that could translate our JSON manifests into Backstage’s entity metadata objects by hooking into the &lt;a href=&quot;https://backstage.io/docs/features/software-catalog/life-of-an-entity&quot;&gt;entity processing loop&lt;/a&gt;.&lt;/p&gt;
&lt;center&gt;&lt;img alt=&quot;Lifecycle of an entity&quot; src=&quot;/blog/08ac20455f70536e81bb82df982e4527/life-of-an-entity_overview.svg&quot;&gt;&lt;/center&gt;
&lt;p&gt;It was quite pleasing to see how quickly we got to a working prototype of Backstage that indexes most of SoundCloud’s software components.&lt;/p&gt;
&lt;p&gt;This was also an enabler for our platform team: With real-life data coming in, we could speed up our exploration and experimentation and identify special-case scenarios even before we rolled Backstage out to the rest of our engineering organization. Just like with human relationships, with developer tooling, sometimes the first impression is the only impression, and we aimed to push that bar very high to drive adoption among teams.&lt;/p&gt;
&lt;p&gt;Months later, when we had already established Backstage as a replacement for Services Directory, we ended up substituting our manifest files with &lt;a href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format&quot;&gt;Backstage catalog descriptor files&lt;/a&gt;. In addition to making the custom-made parser obsolete, using the format from the open source project meant we got access to plenty of new features while still reducing our internal maintenance burden: No custom internal format means no learning curve and no work to keep all files following the schemas.&lt;/p&gt;
&lt;p&gt;That change was transparent to our engineers — our platform team ran a bulk change automating the translation from JSON to YAML and pushing these files to all repositories in the organization. That’s yet another learning for successful adoption of a tool on the engineering toolbelt: &lt;em&gt;Users love new features, especially when they have to do no work to get them&lt;/em&gt;. By hooking in the entity processing pipeline, we were capable of stitching in several quality-of-life improvements for catalog users. Soon enough, we had most of our APIs cataloged, we had public repositories, and we could also support indexing of &lt;a href=&quot;https://roadie.io/blog/backstage-monorepo-guide/&quot;&gt;monorepos&lt;/a&gt;.&lt;/p&gt;
&lt;center&gt;&lt;img alt=&quot;Software catalog entity model&quot; src=&quot;/blog/5e2d591f7ad1e18f878bdbed853afaad/software-model-entities.svg&quot;&gt;&lt;/center&gt;
&lt;h4&gt;Backend Integrations&lt;/h4&gt;
&lt;p&gt;With the catalog up and running, we moved our attention to external integrations. Services Directory catalog’s main supporting feature was to provide a friendlier UI for engineers to interact with our backend feature toggles.&lt;/p&gt;
&lt;p&gt;We took that as an opportunity to get acquainted with the different system interactions within: how we could instruct Echo’s backend to communicate with an already-existing API, and how easy it would be to design and code a frontend plugin for that integration.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 511px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.64579256360078%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAADGUlEQVQ4y4VTS0wTURR9lOmAfEwlRqNIjApCjGjwswFFWWiMujXRjS5NNH6iJEaibjS6MFF3ujXaGL8rNPIRBFLAD512Pi1WPm1p5w0FsfQz7XSmc32vsQQ16p3c3Pvm3XvmvHfPoOedTxE1AEAC5gfkhAw4hlNKDKuyGgZedj+k+/1in6X12kX0X9t18Ih1w9bd64UbYcRjftgAHYx0JpvVDEODNLhkzv7zgwU0njxzKdfnUwzqBdS/YH3BUe325mXr6hs3125qXuKZ9TjiahRGet8bgmNIT5oJGAl+fkIBqmury9gitpjmb/q4vzNkGKaahCaWKVohRcT+pJkENZXU0loqHTdj4Apzj2jdA/t9C2NlSp62D+T6/N+gXAzpa3u92ppeKV31aSJTRRhWouIlxcvIfmXF8ooSPuzmPdMSDI0OwrBvGMbnx0DEwqv8kVmWLXn22sHQtRTSzvlnDDOWMqfnU+bs9Hw2So4/+QtbQeZPu6a4O1zQeYsLOG+6Q657BPBofmiM1VrUtGffUrru+ui/Rd6BN6KDO6zTFAjDeA7IYrEU/GtwE/PjCznps9DIh8xWCvLiQ1y78zKYpblP0edyRXU7Wv6phAGpPxcLC5mCK9fvsjT/5Iu2RQmxmGqoOJrRosls9quizyBvxJMrftTzAkmKuJ8c8bg0LR4TFeJYOCFhsTF/hzabrbxl/+Fyuv7ojdyeJYDc2Bx4gnHwTKVgVNYAyVpwQdhExJwJJmTIoxOnmnTLrmeLyLK0jlowCts8odRl3p84656Mnxf8iQujcuYU2tlyyLp+S1PN1bo2JCjCYDIdh9kwznzHES1hxKls7ASkjPgqu91eScE6HdLf73zLroNLaxr2bqxvOFAszUiOtKGCz+ky/KLXUIkmRwKf23u6e2739ffZOzo6rtCeiiKExiOGxRNKM0IgkXMxkGQIw0JEtLWa1DSUlZbbREVw4AQGHMWGPCfriorpv/x4MYH8kd+6VURk8ocjK2tdSQddWlZqIzp0DH8dgndct9Ht7NKdgREgg8oBkju15MFozOe/2w8uIyGjBO/yXAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Example backend integration&quot;
        title=&quot;Example backend integration&quot;
        src=&quot;/blog/static/33036b5e296e36ebfd4255184c1b4afd/d502a/rollouts-echo-integration.png&quot;
        srcset=&quot;/blog/static/33036b5e296e36ebfd4255184c1b4afd/9ec3c/rollouts-echo-integration.png 200w,
/blog/static/33036b5e296e36ebfd4255184c1b4afd/c7805/rollouts-echo-integration.png 400w,
/blog/static/33036b5e296e36ebfd4255184c1b4afd/d502a/rollouts-echo-integration.png 511w&quot;
        sizes=&quot;(max-width: 511px) 100vw, 511px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The fact that Backstage uses a &lt;a href=&quot;https://backstage.io/storybook/&quot;&gt;well-thought-out and documented design system&lt;/a&gt; sped up our efforts considerably. On top of that, having a React codebase meant we could lean on both the existing plethora of open source UI libraries and components, and the extensive knowledge of our internal team of web engineers.&lt;/p&gt;
&lt;p&gt;For the first time, “word was out,” and we started seeing excitement within the organization for what potential Echo/Backstage would unlock.&lt;/p&gt;
&lt;h2&gt;Maintenance, Ownership, Updates&lt;/h2&gt;
&lt;p&gt;Naturally, at SoundCloud, each team is able to operate on its own and make the best decisions for themselves. Our platform teams provide guardrails and support common use cases that we consider our &lt;a href=&quot;did-i-break-you&quot;&gt;golden paths&lt;/a&gt;, but we don’t want to mandate technology choices for everyone else. Backstage fits perfectly into that philosophy, and its plugin mechanism serves as a way for our engineers to build value &lt;em&gt;together&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;On top of that, we recognize how &lt;a href=&quot;https://teamtopologies.com/&quot;&gt;some teams are way better suited&lt;/a&gt; than our platform team to drive the vision for their own software ownership. In that sense, the plugin for interacting with our build system is provided by the team that owns the build systems, the plugin that shows active or past A/B tests is owned by the “experimentations” team, etc.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAADmklEQVQ4y42U646bRhTHXakf4rqxwfgG5uY7NmCDgcHcvPLuRpvspUmUT0nVB+mXvlwVqQ+R7dKsEinPsB2dnsGu7arRtkh/nRkGfnPmXKZUwqfb7ZZUTXui6Xr5n9K2VlXL35ZK5dJOYqtd1vv9sqZphVRVfYL/l/YPX69/x3HcbzWO+4T2Dm1+LHyXNzudXJSkXJTlnBfqea1WY2t3uPaZ4/n3yOD2QPSwoqjqF73XA/QIcNetdmP2fmLOwA0JzL0l9AaD/bqOkhXlk6prwh6oK2olSZOPSZpCFMd/picndKv1Tic0zlJ6enNFY5wn6zXF71DRQ5qlEIbhnWWa9QNQVSv4wb3v+7D0PEqiFXg4DggBL/DBJwEEQQDZ+SmQ1QpIHMFyuQTHcWiIc1Q+Gg7/DWQ/IYhG6wwChFqOA0EYol3A1LbAWrowmc3AZxt6HoPSVRQxD/PxaPQIMMuYp9BVFJBFCZR+D4ZzC+q1GgiCAA7z2A/AWdiUEP8RD/0D0MfjTt0FjDEZxmIOs6UDBno5Rvno1cKewum7X+jlTz+D59r5wkuPgJpWiRlwFYIfEgSmYCOkPxpChomaWCYYngtr3Mi0bYienYEXEyCbVzS7eAMk8HLDXByAMi9Ukiy7H6EXfduk2ekG3v74DsYIaksSDKYGTOY2tEQRZDx+gElxMWnWfEbD/ZEHB6AiSZU4Se5Z0A3iF8DnL17A0DDAx7gysIHHZ3E1TBPICcaY1aTr0lUcfyUpml7EkGDcsFToCo+5cB3ojUYgdbswmBkIdECRZZBVFaKzDfjo5WLp/v8sszq0sStC9ICVD/4M9nwOcywhFuul6xZ1WABX/wU8WbPkgCh3odVsQqPVAgfhDMLqL0Tv9nW4K+xHgWGSYEETwHbDzggLu9lsIMH3LkLJ30AWw+hrQOXQegzIWo+Nf3j5Eq6ur+Hy6hJevX5dJGrpe0UYWOu5DMg8ZDEcHgH7eg8vh/QPXGCgBxKGFHuWYobps4sLen1zQ8/Oz4s5eoPdQWiAG/ue94D1ixdK9LsxmRyAXLVasWz7i4FlMsLMHotlPmNJwiMevx+PxzAcDsG0LJjOZp/brfbh+mK3cKvTed+RpI9tUfzQEcVbtLfMCs3GLS8It41ms5h3pO3aTh9Q93VB+PX7avVwwaJ3pdrTp1UBb92mIOzUKCxe/9yg3+ewBrkGzhv17RobNxsNjuc4vs7z1Z6mf8NYfwFyJdISTYZo+wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Echo Plugin model&quot;
        title=&quot;Echo Plugin model&quot;
        src=&quot;/blog/static/bcd0b5b1da3f55b1a818bcf0fb459e37/8ff1e/bs-plugin-model.png&quot;
        srcset=&quot;/blog/static/bcd0b5b1da3f55b1a818bcf0fb459e37/9ec3c/bs-plugin-model.png 200w,
/blog/static/bcd0b5b1da3f55b1a818bcf0fb459e37/c7805/bs-plugin-model.png 400w,
/blog/static/bcd0b5b1da3f55b1a818bcf0fb459e37/8ff1e/bs-plugin-model.png 800w,
/blog/static/bcd0b5b1da3f55b1a818bcf0fb459e37/6b691/bs-plugin-model.png 975w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Our platform team is constrained in people, time, and resources, so the co-ownership model works very well for us. With more team autonomy, we’ve seen fast adoption, growth, and also interest in contributing to Echo across the engineering organization. Meanwhile, our platform team continues to be responsible for the infrastructure of, running upgrades on, and keeping a vision of the longer-term plans for Backstage and Echo at SoundCloud — along with its limitations and opportunities.&lt;/p&gt;
&lt;h2&gt;Powering Up Engineers&lt;/h2&gt;
&lt;p&gt;Echo gets more powerful every time we deploy an upgrade to the underlying Backstage infrastructure, but over time, we’ve collected and implemented several plugins of our own that had a positive impact on our engineering team’s day to day — whether that’s by bridging communication or helping service operations. Here are a few examples of “power-ups” we’ve built to aid our engineering organization.&lt;/p&gt;
&lt;h4&gt;Entity Augmentations&lt;/h4&gt;
&lt;p&gt;Different to most entities in the catalog, &lt;code class=&quot;language-text&quot;&gt;Group&lt;/code&gt;s and &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt;s are sourced from external “entity providers.” In our current implementation, that means GitHub.&lt;/p&gt;
&lt;p&gt;The entity provider modeling can differ from the Backstage modeling, so we allow entity augmentation by merging the source-of-truth data (GitHub) with manually curated fields from the YAML entity envelope. In most cases, these will be &lt;a href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format#annotations-optional&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;annotations&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; soundcloud.com/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; GroupAugmentation
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; beep&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;team
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;soundcloud.com/team-readme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; confluence&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;139924197&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;soundcloud.com/team-alerts-email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;for&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alerts@sc.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;During the next catalog-refresh cycle, Echo will fetch available augmentations and merge them with existing entries in its catalog. The final shapes of the merged entities become available through Echo’s UI and its API.&lt;/p&gt;
&lt;p&gt;This added feature allows further integration of Echo’s entities to reference into external systems.&lt;/p&gt;
&lt;p&gt;In the example above, we point a page in our &lt;a href=&quot;https://www.atlassian.com/software/confluence&quot;&gt;Confluence&lt;/a&gt; instance to render a “team README” with basic information about the team, contacts, ways of working, etc. The interesting aspect is that the content doesn’t need to be moved or replicated within Echo; the data source continues to be Confluence, where our non-technical audience is comfortable, but Echo, home of engineering, can read, display, and include information in its search index.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 93.59165424739196%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAADl0lEQVQ4y4VT62tUVxC/n4X6F5jNmsca8QGW9lNpoVAEIQXBF4jxAZq20mC1hDapKRLxhVEUNcb4IbGKYEpsraVQWmppwT40iUm2WXc3UTfZ3Tx3927u+/nrzIm7mBjxwNyZM3fO78xvZo5UtnY13j1Yi/VfH8L7DZ/infqPsKZuF8pqtyG4bytK927B0ppqLNlRLfbsX7l/B978bA/ePrQX6w7sQeiT7XiD/ktbPoC0bHkQdedPoflWJ47cvIqD7WdQ134OH7eexu4LJ1Bz/jhqW1vQ+E0bmm604yvSX3ZexhedrWRfweHrV9B4rQ31HZdQc/EkpIqKCvwXiUPRbWiGA910YVoudMOG7fjCdl3A8wGHtGX7wm87gM02Cfs9j4S0VLVqFQYGh5DJ5pBMpQnIpGCXDjoEZsOwXcizs0iNj0POz4KXT+AufTz4pD1kczLyFOORT1qxogqRaJRusaGQ0zINutWCaZqwLJNu9oSWczkYuo5sXkaGRFVUZDN5iregqBr5VRjERqoMVaL3nyHIKRuO50CzmIIvMnGZK607d3/Axs2bcKLlNLa3HEX12QbcaO5By3uTuH1WgWbrmJYpCdujGlZWItwfg6VQ+lQEBuPUfZIC4O8//4JjB+rx/c1baLx+FYe72vFtWw+ad/ahuyNNxKn2ugWHmEll5eWIj4yIg0zvxeW7c/tY14/oemsj7u5vwofHG7HhWAP2tZ3Cys9rcKS7gwIBTddgGAakUCiEaGxYHHQch+pnCy1sagpDToSjiHT/hJG/H+LOX3/gt74H+LX3AW7/eQ+P4jHKzKY6W0KohiH0D4aRTqchy3KRalEI+HVL52Zls1AUhSiXleNxNEZAnshubiz8eVrMCfPyuWF0CZWGhW2PznGpmBEnIJUEAojFh4sAC8G85yD37kcxk1VeGVdYUklJoFhDvqkQXAhMZVR89+8YBsNhMegvAi6MZT0PkFOeF8iNsgwMxBO4P/AEicRTaFPDwr8YaBGwQHmx5eg5AZIcn0I+l4ExFSc0b9FYARgIlCI8FBEtz+fzolOz9ARZiz09K1U3xZPUNB2KZpKemzl+nixsc6e5MVJpaRC9j/oxMTFBlBIYHR3F2NhYUXifePaM/s35kySTk5OYnp5+SRhUCgaXo69/QASJA8mkELZTqZTQPKMFPTMzU5w5ZqKqatEWL4Xf8lDksRhghX4ynYXCNxds9XlMge5C+R+jujDTv5fCSQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Echo Team Readme plugin&quot;
        title=&quot;Echo Team Readme plugin&quot;
        src=&quot;/blog/static/d81448fb39eb118f149b0dbf006d7307/8ff1e/team-readme.png&quot;
        srcset=&quot;/blog/static/d81448fb39eb118f149b0dbf006d7307/9ec3c/team-readme.png 200w,
/blog/static/d81448fb39eb118f149b0dbf006d7307/c7805/team-readme.png 400w,
/blog/static/d81448fb39eb118f149b0dbf006d7307/8ff1e/team-readme.png 800w,
/blog/static/d81448fb39eb118f149b0dbf006d7307/6ff5e/team-readme.png 1200w,
/blog/static/d81448fb39eb118f149b0dbf006d7307/7dc55/team-readme.png 1342w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Another existing integration is with &lt;a href=&quot;https://prometheus.io/docs/alerting/latest/alertmanager/&quot;&gt;Prometheus’ Alertmanager&lt;/a&gt;. Since Echo exposes its catalog through a queryable API, annotations in the entity augmentation can inform our Alertmanager deployment about &lt;a href=&quot;https://prometheus.io/docs/alerting/latest/configuration/#receiver&quot;&gt;notification receivers&lt;/a&gt; — these can be email (as exemplified above), Slack, PagerDuty, etc.&lt;/p&gt;
&lt;p&gt;The benefit of using entity augmentations is that of data centralization: When, unavoidably, a company reorganization happens and a team gets created or renamed, the team manager updates a single file and our automation kicks in for the dependent systems. In the Alertmanager use case, this means no manual interaction to configure receivers. Similar workflows can be established for automating the creation of pull requests in selected repositories, or even infrastructure provisioning.&lt;/p&gt;
&lt;h4&gt;Daily Ritual Support&lt;/h4&gt;
&lt;p&gt;As the engineering home page, Echo also supports recurring tasks individual contributors run through. Within the teams, that can be checking open pull requests across all of their ownership domain, or even selecting a team member at random to take notes in a retrospective meeting. These capabilities can be especially useful when we consider the interactions that happen across teams as well.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.122549019607845%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACMElEQVQoz11SS2hTQRR9rgvuUklJk6DiIroRVKhu1J22tWhXLTRR/LWibdVaKrRCtULdiXu3bnTjRugiJmZTdCsSgyhNbL6m7/+b9zveNy+W4oXDfXPevWfO3BkhfSyDc3M3cWV9GaPPFnFxdQGnFm4hc28KmbtTODQzif25MfTlLuHgzATxWRx/cB1Di9M4TTjx8Aav6cuOYt/kBQjxwUGcmc5i4uUaxtdXMP5iBWeXZ3GSRI/OX8OR2RyGlu5gbG0Jl58/xsjTaNPh1UecCxEaOf/kPg7PXYVwoL8fHzY2oFomOpKIaqOB7VYbqqFDVGTImgbdNKDybMIIYUVZ3wPLsmASLyQSCZRKJYShU5Os6NANG7ZlQ1UU4lQ4joPA9/9DwOHvWfueByGdTiOfz6NSqaBcLkNTVTBmQ5IkVKs1tFtdmOQgjCAIdvM/8HWPc10XQiqVQrFYRIOOWq83eUG4q+f5YI4JUdviLWGTTw3MUOAHfiRInNRuwiUDYXDBeDzOBcOQlA6JeSTm8m13tF94t3mbhA3+X2408fnta3g+o7pIcPPNK3S3tyJBGo0wMBDHp2I0w/df5vFH+cHFXNeBQy7rok6OiCBXta6Nrz9FtGSHu97RXPzuGGiKNnfshTOMxWIofCxwwW+17zCZxQfMGOOXwZjLcwiVLks2HGgG425sqrEdGgNz+HH5kZPJJAqFSNA0Xbp+umHbjp4Bfw5mL1vEW2AEm76tXZg9ROu/WCeiZtgm5kAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Randomly picked team member&quot;
        title=&quot;Randomly picked team member&quot;
        src=&quot;/blog/static/36ea7d34c73325ec58e56002147614c2/8ff1e/random-team-member.png&quot;
        srcset=&quot;/blog/static/36ea7d34c73325ec58e56002147614c2/9ec3c/random-team-member.png 200w,
/blog/static/36ea7d34c73325ec58e56002147614c2/c7805/random-team-member.png 400w,
/blog/static/36ea7d34c73325ec58e56002147614c2/8ff1e/random-team-member.png 800w,
/blog/static/36ea7d34c73325ec58e56002147614c2/6ff5e/random-team-member.png 1200w,
/blog/static/36ea7d34c73325ec58e56002147614c2/2f950/random-team-member.png 1600w,
/blog/static/36ea7d34c73325ec58e56002147614c2/260ce/random-team-member.png 1632w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As an example project workflow, an engineer can visit action item lists from previous incident review meetings and check whether they’re assigned to anyone, write and publish an RFC outlining a possible architectural change as a solution, and finally announce the system migration to the rest of the engineering organization so dependent teams can prepare accordingly.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 375px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 177.86666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAYAAACJ8xqgAAAACXBIWXMAAAsSAAALEgHS3X78AAAFiklEQVRIx31V2VYiSRBlljMP0zPdoiwCyiKiLQIqrmwFFMW+FpsiIO627Zz2/5/uRAQUjqP2wz2RGVl540ZkZJYp1m3iUK/hQK9inxBtVbDXLCNUKwqC1Ty2KhoCpSxBFesvZuDLp+HRFKzmknCpCTiycTgyMZhivSaK4wHSgy5OiHy/VUWkXoJy1iXiCsJEuk2k+xR0s5wjkhTcuRRW1CScRLJMJLb0CSzKMSypI5g2KGrr9hLjfx7Re7hF9/5GiLThKWpXYzRvJmLZv17MCplXS4uy/xIuKUcwJw5gMicP8bWqYY9StZDTnjnBQnwfPk4nm8CvkQ3BbzubWCV1VlLyhdaZhAmds1R5bqM1E098hbR8bKfxIsn+/TAEFylg31LqEJ+OdvDHQUjmTqoXf8eK/jzZBQuypY9hJ8JlgunvWBRmimiN7WPheBdfDnfw10EYnw8jNI5g4WgXK8oJvJkEIS7wpOPicyQOsUxwJo/gpLmTD+WX6Bbsx3vIZrNIpVJIEgybTCVlnFIUscrMMjKZDFRVRYb2aVkV4UwSjjQRmoI+rBBhqVDA09OT4NvDAx6/PeLp+/e57/nHD9zf3eH5+VnmlUoF6XQa9UYDlWIJESWBVVIvNfxK7Fouh2arhRah3W5D13XoZA0fz+v1uqx1ul3k83nJStM05GlvVE0jQF1gCqROsB0/Efk5WuConBpbTssAb2ZrrPOc97DNkd1PJRBI0CmbFxbgdrsl0s3NDSaTCcbjMUajES4vL3FxcSH+O0q3S8pub29RLpeRTCbngfNUruDWFpjLtLS0BI/XKymcn58LmIzBxByAfUzOPiY/OzsTPwfp9/uicHt7G8w1JfR4hJDVXF1dyUZWYpBy3RpU/FqtJrVka9S1VCpJdq8IvaSQnb1eT6LzZo7MltMsUEqxWEzaJZFISLpsGZw2iwkGg68JucBGsQ01zWZT6sWWiY0A0gV82p2OKOSAbwj5hJmM+8tImdMfUP3G4wspx2AwwCX5GFJrKoeut98nNFpAVbMYDs9xMaaDITuajbWcikQ8RukmppZTT8SRojmn/KaGqijMoFCqoNW/QLM/Qet0gvb5DfSzSxpfot4ZoNE5hz64Jt+1WP7u5wqJuNrqC1mTiIWoO5SNjd4INf0UDZqzvzt+RK199v4pv6SsCgFvYFL97Ap12sRo9unB7Y2FtFzXRXGhXPuIMDcjzE0VUEqlegc1SrHS7KNCqqvtAQUbodzoUO2m9VOold7UcG1tbX7KxWKRbsVEboZxS4zryIc1pg64v7/H9fW13JThcCgKw+HwC6Hf758T8iL3GrcOf8ztwpZbiMfc+EzIATkIXwZW+CEh15A/4s18WwxCfrr4qlWrVQnIa3z9uOl/SsgwiJiA0z09PX31pCnzFzw9v3ofE/KbRx8pSooIlBmRIj1qQOVMJPj0nfyQkNPNF0rS3PliWVAwbKk69dO6YOZXc5rUPRKJvCacvtiadH+Deq1O7dIZ3VMLjaTB9cEN+YZoD+8Evcl3sRqR8+8jGo3CYrG8l3Ja0s5QmsVKXW5Di5qbg3RGD6g0ulSS1PSb2bescG9v7y0hPwys1ICWL6BUbQoxo1zTJe3pem5+u7iGoVBomrLVaoXT6YSH/isrq244XW76x3jI54LVaoHdbofD4RJrs9nmPh4bWF5eno9nhA7CCja/biK6uwlvIETEq1j48hkWmx1Ojx+OFS+WKZjZbH6DxcVFUceBTMzqcrmw6vYivBNB7DgE/2YYLlLNalxuH7zrW/D4OVAQvo0QbWRF1lcqGULIPygGPxC+tQDcvnWs+dfx4idC39qLJRhr78HERIypww0vQezMz//sQGCdHtAtGXtnwT/CXKHnfwuGarbcBRsbG698H+FfhcPEyeYuTHYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Announcements inbox&quot;
        title=&quot;Announcements inbox&quot;
        src=&quot;/blog/static/c1bfacdb1b292cf92363b80af0e41cec/d266f/echo-announcements-inbox.png&quot;
        srcset=&quot;/blog/static/c1bfacdb1b292cf92363b80af0e41cec/9ec3c/echo-announcements-inbox.png 200w,
/blog/static/c1bfacdb1b292cf92363b80af0e41cec/d266f/echo-announcements-inbox.png 375w&quot;
        sizes=&quot;(max-width: 375px) 100vw, 375px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;All of these are supported by Echo in one way or another, and the long-term vision is to continue to identify intra- and inter-team interactions to standardize solutions to speed up the engineering practice end to end.&lt;/p&gt;
&lt;h4&gt;Domain-Specific Technical Health&lt;/h4&gt;
&lt;p&gt;Especially important as we consolidate our golden paths and internal &lt;a href=&quot;https://www.thoughtworks.com/radar&quot;&gt;tech radar&lt;/a&gt;, Echo also helps increase visibility of our team’s ongoing operations. The data set is an aggregation of sources like Prometheus, Zoekt, GitHub, CodeScene, and others. Through the modeling of the catalog, we then further slice our software components across our business domains and establish a maturity grade as to what we consider best practices or technical debt. We’ve also been exploring building a “migration tracker” in a similar fashion as to &lt;a href=&quot;https://engineering.atspotify.com/2020/06/tech-migrations-the-spotify-way/&quot;&gt;what Spotify describes in this post&lt;/a&gt;.&lt;/p&gt;
&lt;center&gt;&lt;a href=&quot;https://engineering.atspotify.com/2020/06/tech-migrations-the-spotify-way/&quot;&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40.36458333333333%; position: relative; bottom: 0; left: 0; background-image: url(&amp;apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsTAAALEwEAmpwYAAABYUlEQVQY03WR2W7CMBBF8/+/0oe+dYGytGoFokhFZSk0RRAwymKSOHZiO97SCaiPHUvWnBldzfXYk1KGYQh34xrnHGMsiiLIIaAAeMbYGecuSAhBCIm6brvWekrVtKBhGgX5EUq61jjHG+xjfr6girJ4Fa+JLAA5rzJC6P7blS2CWNVcxmnS2Q0CiqA0QK+z0xwQ9MqobjD8QJ+Pux6tmdUmK2jhL3jvptHKA8NbvCNxvghW43BaKX7302GoGG8ny3QN+ge/V+zyp81wS/dOueMh8EOc9m+bBHmNaebnr+5x2D0MckFg8jSZddGwf3zhSsAaRuEEus+nN2PhGMqFWL6r0X1r22hjrQ1JJLQANlqrWqH0JLWE/WmlwRo6n8C/sw7eKKSkOIaGdc6rqioneUlLRhkrmTEg1+16rwGzjAE0l+Ta1RY8tOyVZQl/k6ZpkiRZljV/IYQgpMgrw5Vr/olfklbA9upu+CoAAAAASUVORK5CYII=&amp;apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Migration tracker&quot; title=&quot;Migration tracker&quot; src=&quot;/blog/static/0b25f19984978203374f33099de1db19/8ff1e/migration-tracker.png&quot; srcset=&quot;/blog/static/0b25f19984978203374f33099de1db19/9ec3c/migration-tracker.png 200w,
/blog/static/0b25f19984978203374f33099de1db19/c7805/migration-tracker.png 400w,
/blog/static/0b25f19984978203374f33099de1db19/8ff1e/migration-tracker.png 800w,
/blog/static/0b25f19984978203374f33099de1db19/6ff5e/migration-tracker.png 1200w,
/blog/static/0b25f19984978203374f33099de1db19/ccc27/migration-tracker.png 1536w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; loading=&quot;lazy&quot;&gt;
    &lt;/span&gt;&lt;/a&gt;&lt;/center&gt;
&lt;p&gt;Put in a service perspective, these insights can be useful for individual contributors. Now, take that to the organizational level: Through historical data and Echo’s visualization-aggregating services, teams, and domains, its dataset is also informational to engineering managers preparing for their next planning meeting, and to leaders looking to build an &lt;a href=&quot;https://en.wikipedia.org/wiki/Return_on_investment&quot;&gt;ROI&lt;/a&gt; argument to select a domain to work on accumulated technical debt.&lt;/p&gt;
&lt;h3&gt;Down the Line&lt;/h3&gt;
&lt;p&gt;We’ve seen more engagement within teams and toward our platform team as more engineers got acquainted with Echo and brought it into their daily workflows. Often, conversations bubble around feature requests and plugin ideas.&lt;/p&gt;
&lt;p&gt;These proposals feed back into our own internal roadmap and help us define &lt;a href=&quot;https://en.wikipedia.org/wiki/Performance_indicator&quot;&gt;KPIs&lt;/a&gt; to measure the success of the platform. From a product perspective, we’ve been tracking page views and &lt;a href=&quot;https://en.wikipedia.org/wiki/Active_users&quot;&gt;DAU/MAU&lt;/a&gt; to establish where to invest or what to defund.&lt;/p&gt;
&lt;p&gt;For the future, we envision being able to bring usage information back to the teams themselves: Imagine, for example, being able to inform teams of opportunities to provide more extensive documentation, or even when knowledge siloing exists.&lt;/p&gt;
&lt;p&gt;Other opportunities lie on getting Echo closer to where our users are: For example, a lot of daily work happens on Slack. Individuals asking questions on how services work, checking in on dependent teams, incident management and response — all of that, and more, happens in Slack. Instead of engineers coming to Echo, why not make Echo conversational?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100.50590219224283%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAABk0lEQVQ4y5WU22qEMBCGff/b3pbS29IXKRS2FEppy1oPuLuuJhmTmIMasx032wOFRfd3IiPmy0wmh2hXlus41lo75w6L5b3v+z6yUkFNBG+klONRw1F+TtgzEs9J8vj28PK+Wj1lWVYUG6WUtXZJ/Agb61ghtro1mDmOtzz5SAoBQI3VxhjMFuEwhF+g6Orm+vb+LlnHaZ4jr49DhJKgZiLHyWeSpUWeZ2mKWRBCKCEAMEue5my6oajFpubFHiom0GqQpFF1o0zXT2V1wf5XJMII1nZlVTNo0ChlAA2hjNDJxaYIGOCagmEcHf+HnyLjwvAGBIfOmsMlCnD3GhdvyeYjr5rWXgbjuuyYrhuzpYpJcxmMWyrNcjTgsnf+sKDOv/DgXFZCuoO84nklKlDjMj6kPZZMIVayFt84bb8cdm4gpC53W8k5o6Sq9taYcPBmYNySbdvKSa08OVK1SuOjVHde03nGHgiHD1zwn3/o4w0xnNcEh37hGI0ed+HSUzldBkhaY6WQGAcThqO+N/NZG/op8hdvdo9Rio2qcgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Slack integration&quot;
        title=&quot;Slack integration&quot;
        src=&quot;/blog/static/93c922119330309fbb90465423bb4a92/8ff1e/slack-integration.png&quot;
        srcset=&quot;/blog/static/93c922119330309fbb90465423bb4a92/9ec3c/slack-integration.png 200w,
/blog/static/93c922119330309fbb90465423bb4a92/c7805/slack-integration.png 400w,
/blog/static/93c922119330309fbb90465423bb4a92/8ff1e/slack-integration.png 800w,
/blog/static/93c922119330309fbb90465423bb4a92/5c484/slack-integration.png 1186w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Similar potential exists in making information contextual by other means: GitHub comments, Jira tickets, Confluence pages — you name it. The fact information is available is the important part, and where it lives shouldn’t matter. Part of our platform’s team mission is to make that information accessible, discoverable, and easy to digest, whether that’s for a human reader or to build automation upon.&lt;/p&gt;
&lt;h2&gt;Learning as We Go&lt;/h2&gt;
&lt;p&gt;It’s been two years, and we’re still gaining confidence and working on bringing traction to Backstage at SoundCloud. We’ve gone through some operational hoops to move from Services Directory into SoundCloud Echo, and we’ve objectively provided a better development experience to our engineers, while also opening doors to plenty of potential the platform provides.&lt;/p&gt;
&lt;h4&gt;Advocacy and Education&lt;/h4&gt;
&lt;p&gt;As envisioned, relying on an open source solution brought us a lot of value “for free,” but we still had to invest a considerable amount of work to make it match our team’s needs and wishes. Developer productivity is a neverending road, and although we see lots of aptitude for the future, SoundCloud (and every Backstage adopter) makes an operational funding decision when picking Backstage to power its developer portal. Nowadays, Echo is a central piece of our infrastructure, and its maintenance means responsibility for the platform team.&lt;/p&gt;
&lt;p&gt;Onboarding the engineering group to Backstage is more than telling engineers about it. The investment from the business is quite demanding, and to make it “the homepage of engineering” also means cultivating it as part of the culture for engineering stakeholders (EMs, PMs, Directors). Training and advocating is a work-heavy task and demands organizational efforts, especially when it’s cross-team and cross-discipline and involves non-technical folks. A big part of the work relies on “vision selling,” and that might include reshaping some of the existing culture, introducing practices like &lt;a href=&quot;https://docs-as-co.de/&quot;&gt;docs-as-code&lt;/a&gt;, or asking teams to change their ways of working, for instance, to keep their ownership listing clear and sanitized, or to consult some Backstage-provided dashboard during standup meetings.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.40469208211144%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC0ElEQVQoz21SS0wTURSdaQsFUQqCYIi4wMSIUaSCQTculBhAQYy4qYlGCZoYCIkpakKiwTSKKBF1ZUhtJHxsmVJa+qOt1Olnpp3+pjNtodPSWogB1AUG93VmQNKgN3k575137n333ncBIMMUCgWLKIqymEgkwHQ6Df69x3FcTRDEeKZPu2SgvLtLVNnfezuPJYLBIIuRSATwer2A1WoFdhrNFzHocDgu22y2xlAoxJdKpXyGe9bXX56TnVO4LaYzADgcDoAgCLMHaSHg8/l4YrGYS5JkNYZh52EYruvo6MilM+yhV6dhempvIOBvD4XCtUwMDEEL6WqaPRi2ZztoptXW1HCFQiGfnI92U7H4E4aDlMpSBqmV5VzIaD4Viy/eTyYTvTTFscJzJ1Kp1KRGq60AVlbDbI8C3sBRWiAbGnyV73K5n7vdWAv72Ny73Vv9G/b5/M1jZW28qFnz0GOHTs/qXJXYHN7nNoXKgFwAlH8Y3iwd0kPFrpCn6uvadxFz9rkcTV634/hhGfWo5VP44pJTd4wMzV/ROkx1fsPk2UW1Ujo12Cmw63RCt8WH43D0ZHhC3hMYk18CYtJGvimoewF7DX2RkZEb6TTKNToXrmrQZAPvfUr0YIZoJAIB5dPHEgGte+l1zY4l1Wr/ukFTbtbaD9oNLgTRe4S/fGrZkm1CBNy92VqgMs7UL8HwOWpKMTTxunu/AU2IjQjVwJZsa82KYeo26cC9UtGtpgO/jab8lG7mwsZHSbZdT5Z+0aIqtwmr2ojKr8edsmr2Z4n5aFvUYu6kVEo39uZtkR6hIItrQcIEXA1OHvlJqCDHtKRi5zg5dZ5DTpMnjn0mzmyTw1132E9Jjo+WfJs1XEsj5iyY/FFvsaMNmc6LcQjMHLUtBFci64JlfI27Tkq5cfvopobCcR7wH8vm7yqggZ4tMJ9GAQgCAgbplbevuIRx/ueRPxj1dYSTIwKKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Where to find documentation — flowchart&quot;
        title=&quot;Where to find documentation — flowchart&quot;
        src=&quot;/blog/static/e118aef2b2aca854c3f89440d6fba68a/8ff1e/documentation-flowchart.png&quot;
        srcset=&quot;/blog/static/e118aef2b2aca854c3f89440d6fba68a/9ec3c/documentation-flowchart.png 200w,
/blog/static/e118aef2b2aca854c3f89440d6fba68a/c7805/documentation-flowchart.png 400w,
/blog/static/e118aef2b2aca854c3f89440d6fba68a/8ff1e/documentation-flowchart.png 800w,
/blog/static/e118aef2b2aca854c3f89440d6fba68a/6ff5e/documentation-flowchart.png 1200w,
/blog/static/e118aef2b2aca854c3f89440d6fba68a/4b690/documentation-flowchart.png 1364w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As exemplified earlier, in many cases we choose &lt;em&gt;not to change&lt;/em&gt; workflows, but rather to build system integrations and provide new/alternative UIs, so individuals continue writing and consuming documents where they’re comfortable. This can be counterproductive: Tenured folks stick to “the old ways,” while new team members adopt Backstage’s way, and due to information fragmentation, the platform team starts seeing a wave of “what option should I use?” questions. It’s the responsibility of the platform team to work with the rest of engineering to decide the best moment to decommission features — even if sometimes that means deciding to roll back on a Backstage feature.&lt;/p&gt;
&lt;p&gt;Similarly, pushing the Backstage catalog model onto teams, albeit simple, still demands educational efforts. Pair that with our previously existing catalog model, and some bridging work had to happen. Components, systems, domains; split and combined monorepos; first-party and third-party dependency listing — all of these are catalog-modeling questions whose answer is “it depends,” and for the most part, whose intricacies users shouldn’t care about.&lt;/p&gt;
&lt;p&gt;After two years, we’re still in the early days of the full Backstage model adoption, and even then, we might decide not to pursue it completely or to introduce our own constructs. Likewise, we haven’t yet adopted the full spectrum of Backstage core features: &lt;a href=&quot;https://backstage.io/docs/features/software-templates/&quot;&gt;Software Templates&lt;/a&gt;, although super powerful, are still something we haven’t invested in. Given the maintenance cost, we’re not yet ready to support them, nor are we prepared for the cognitive load it might introduce to the rest of our engineering team.&lt;/p&gt;
&lt;h4&gt;Contributors&lt;/h4&gt;
&lt;p&gt;As we evaluated, experimented, learned, and adopted Backstage internally, we also found opportunities to play a part in the wider community. That means engaging with the maintainers and adopters at the &lt;a href=&quot;https://discord.gg/MUpMjP2&quot;&gt;Discord chatroom&lt;/a&gt;, presenting at the monthly &lt;a href=&quot;https://github.com/backstage/community&quot;&gt;Backstage meetups&lt;/a&gt;, and contributing code.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.4726368159204%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADD0lEQVQ4y21TS4gcVRR9MbgQwVm7EUXERYbYXV09NfX/v/r2p/o3Q0tnzKTNhBkNiApGBD8ouNGsdevGJpkQJ2Tl3kUcQScJCG4Ed67cKDhdVSe3CkcD5sHhvFcU551773lM1/Urnud9K0nSzWazefAwBEE4eMS3bzRNu9Vut/dp/xyj1Wg0Hqt4dXWVMcdxbvf7fSiKAhKtWVU0KLJaQ1W1Gory39mx3Zp12X++EsIB2Ob67dNveH+eYmEYXptOpxgOh39blpVzzvNuluQ8dnNJEfMw4XmnFxF7uWHLeZR6S1FqFOLaS3+9cObp15958cmEPctW2MkyDGM/iiKEQbBUVQUtQUCqvoxMn6OjnsdmfAkD5yJSZY5wbRtcvIDUeLXsWjtlIM/B1y9iIH3x3psWrNf834TK4f5sNoPPg2WrLUGWLXwQ38TV/vf4JPwOX+4c4er0R7zPD/GO+QM+Ht0tP7/wMz4c3S/f8u+Ve8a9Yq78hB39V7yi3r1eOzRNE5KsLHU3hmaH8KwYWTxBygewtQCuGf0L343KiHfRSyalYyZl4HbLJBguHSMBd3sLtiatX1dVFcTHBgnqTliatlmOpgN0sgRhyMFDnyrwwAMfhqFhvDHB25e3yl6kl8mkg63L5/KoH6DX7SyYadm3ooic6WZpeOmx7kRL3TDLyXwDyTAt4zBGEASwbRuuQ9Olae9c2sNHV3ZxfuCgv72J3Xf38mw2QOiFC2ZZ9mfUx/uG5fzuxgNYfgrDtDDZ3ijTQQruc1BOa0GKGKr2VNjoudBkCaqlwc94rmoqKJcLtrLy1Olq2qKkdKjcG7qbfEUO/zi3u4XOuEsOo9qh67q1IEULFHCsnm0QC2g2mmicbeSiKKLVai3YoxYN6mg0GyPqxss4ios0TQsSLchlQe4KclJQvAqh2SyaFQvCMYlVF33N6GUwxzaZ2JZO/aP3BIX7l/FoTNkMa0cn5Z64pOf6P1T/0XBvMNVwapV1WT4x+Hgcx9eyLDui3t0hHD4MEj2kCw+p7/XZ9/2K71RzIHz6AP/uoVql6pzUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud contributed open-source plugins&quot;
        title=&quot;SoundCloud contributed open-source plugins&quot;
        src=&quot;/blog/static/ee3e4006d5c5f1d0a1a875aea3c2ade0/8ff1e/contributed-plugins.png&quot;
        srcset=&quot;/blog/static/ee3e4006d5c5f1d0a1a875aea3c2ade0/9ec3c/contributed-plugins.png 200w,
/blog/static/ee3e4006d5c5f1d0a1a875aea3c2ade0/c7805/contributed-plugins.png 400w,
/blog/static/ee3e4006d5c5f1d0a1a875aea3c2ade0/8ff1e/contributed-plugins.png 800w,
/blog/static/ee3e4006d5c5f1d0a1a875aea3c2ade0/091d4/contributed-plugins.png 1005w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The plugins SoundCloud open sourced revolve around areas like continuous delivery, observability, and technical health assessment, and there are more to come. Summed up, we see more than 1,000 downloads per week.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[WINE Workshop]]></title><description><![CDATA[SoundCloud has a variety of resource groups, and one such group is called Women in Engineering, or WINE. The mission statement of the group…]]></description><link>https://developers.soundcloud.com/blog/wine-workshop</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/wine-workshop</guid><pubDate>Tue, 02 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud has a variety of resource groups, and one such group is called Women in Engineering, or WINE. The mission statement of the group — which is open to people of all genders — is to work “on initiatives to attract and ensure an environment for women to thrive within Engineering at SoundCloud and in the industry.”&lt;/p&gt;
&lt;p&gt;This is accomplished in a variety of ways — from hosting monthly events like meetings and lunches, to scheduling regular internal knowledge-sharing sessions. And in 2021, one of the group’s initiatives was to support woman and non-binary engineers at the company in writing and publishing posts on our engineering blog.&lt;/p&gt;
&lt;h2&gt;Making a Plan&lt;/h2&gt;
&lt;p&gt;To get started on this initiative, I met with Aleksandra Gavrilovska, one of our backend engineers and former iOS Engineering Manager, to talk about what we wanted to accomplish and how we’d do it. But before we could proceed, it was important to first figure out the barriers engineers were facing. So we created a short survey and asked members of WINE to take it.&lt;/p&gt;
&lt;p&gt;We ended up with answers from 20 individuals, or 37 percent of the members of WINE. Of those 20 people, 7 of them said they would likely publish a post in the following year.&lt;/p&gt;
&lt;p&gt;When we looked at the reasons people weren’t writing posts, the top answers were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I don’t know what to write about.&lt;/li&gt;
&lt;li&gt;I don’t have enough time.&lt;/li&gt;
&lt;li&gt;I don’t think my topic is interesting enough.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also asked what could motivate people to write more, and the most common answer was having someone help them choose a topic.&lt;/p&gt;
&lt;p&gt;Next, Aleksandra synced with Tiffany Conroy, SoundCloud’s former VP of Engineering, who agreed to host a workshop for brainstorming ideas.&lt;/p&gt;
&lt;h2&gt;Hosting a Workshop&lt;/h2&gt;
&lt;p&gt;The workshop took place in March of 2021, and there were 23 participants. Although the goal was to increase the number of blog posts, the workshop was also geared toward WINE members who were interested in doing demos — which is a bi-weekly event on Friday afternoons when engineers at SoundCloud share what they are or were working on — or presenting their work in other public ways, like at conferences or meetups.&lt;/p&gt;
&lt;p&gt;In total, the workshop was approximately 90 minutes, and it included interactive breakout sessions where participants talked with one another, formulated their ideas, and expanded on them. At the end of the workshop, everyone had at least one topic they could confidently write a blog post about, along with an outline of what it should entail.&lt;/p&gt;
&lt;h2&gt;Looking at the Results&lt;/h2&gt;
&lt;p&gt;We looked at blog posts in the time frame of 2016 to 2020, and saw that there were 64 total posts published. Of those 64, only 6 of them were written by women — less than 10 percent. However, after this workshop, we published 6 posts by women.&lt;/p&gt;
&lt;p&gt;Here’s a breakdown of the statistics for 2021:&lt;/p&gt;
&lt;p&gt;Total Posts — 14
Total Authors — 15
Women Authors — 5
Posts by Women Authors — 6&lt;/p&gt;
&lt;p&gt;That means that 43 percent of the posts published in 2021 were written or co-written by a member of WINE. Ideally, we want that number to be at least 50 percent, but it’s still a huge improvement over the 10 percent from the five-year period before.&lt;/p&gt;
&lt;h2&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;It’s safe to say we were successful in what we set out to do, but our next challenge to tackle is how to keep this momentum. As new people are always joining SoundCloud’s engineering organization, repeating this workshop would be a good start, as it was clearly a successful first step at getting more diverse voices to contribute to our blog.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[PR Templates for Effective Pull Requests]]></title><description><![CDATA[The first step to any code review is understanding what the code does and why. Although the author is by far the most qualified person to…]]></description><link>https://developers.soundcloud.com/blog/pr-templates-for-effective-pull-requests</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/pr-templates-for-effective-pull-requests</guid><pubDate>Tue, 12 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The first step to any code review is understanding what the code does and why. Although the author is by far the most qualified person to answer these questions, all too often, the reviewer is left to answer them on their own. With no pull request (PR) description, reviewers need to assess changes with only the title and, at best, some kind of tracker ticket for context — and let’s be real, the ticket is probably empty too. The review process essentially becomes a big game of telephone. The truth is, empty pull request descriptions slow down teams.&lt;/p&gt;
&lt;p&gt;When I first joined SoundCloud, that’s what PRs in many of our greener projects looked like. Every time I reviewed a PR, I had to meet with the author in real time to get answers about what their code did or why they made certain decisions. Often, after talking to them, I’d realize that all the assumptions I’d made on my own were totally incorrect. In turn, I found I was starting to avoid PR reviews because there was so much overhead in getting to the review.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/96d72cecf52c936dcdcdba62c19cadb0/1002f/before-pr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 12.01848998459168%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAUklEQVQI14XKOw6AIBQFUfa/Pysag4XE//cFURiJGluKk7nFVXbzFPWM7uVXrhdGwOwxISNSperBYSWgxHmafqIbZ9pkEccZwYf4IeP9HFd49g1pmpqTOxff4QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before adding PR templates&quot;
        title=&quot;Before adding PR templates&quot;
        src=&quot;/blog/static/96d72cecf52c936dcdcdba62c19cadb0/8ff1e/before-pr.png&quot;
        srcset=&quot;/blog/static/96d72cecf52c936dcdcdba62c19cadb0/9ec3c/before-pr.png 200w,
/blog/static/96d72cecf52c936dcdcdba62c19cadb0/c7805/before-pr.png 400w,
/blog/static/96d72cecf52c936dcdcdba62c19cadb0/8ff1e/before-pr.png 800w,
/blog/static/96d72cecf52c936dcdcdba62c19cadb0/6ff5e/before-pr.png 1200w,
/blog/static/96d72cecf52c936dcdcdba62c19cadb0/1002f/before-pr.png 1298w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Before adding PR templates&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;To combat this, I recommended a PR template as a way to make it easier to orient myself when reviewing. Not wanting to introduce too many changes too quickly, we initially added the template to just one repository. There was also no rule against empty PRs; we merely added the template and left it up to the individual to decide whether or not to fill it out. Lo and behold, with just this simple suggestion, suddenly all of our PRs were full of context, explanation, and discussion!&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/2f950/after-pr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAACLElEQVQoz41SXWvTUBjOf5CBTAeuadMkjbVJCbbb3e689EYEUQQvFMWbwgZuoL/BC6/8C17oxZhuYzgUFG/tZ9rtZtKkpk1i1ibn5NM3p+0+Cg4fHp68J3mf97zvyaF+2Z7SHx2aSNGHHQN1Bu6h5R258ZFzEQ8srDoh1RtY1VZH0w0vjF0vdP0QBZEXxbCc6DggxGGMg4TIjyCNGo3cbrdr23YcxWEYBkHgewlOFWN/GsDneIooihOzrve1Xq+raqqmWX8ANiiUAz0eDm372CaAVwihc2bDsGr1RrtzoLQ7zZaCEI4vBHjGDMEMD0wAVV3Xhd1M04SI5M1ipgql9416owmb/6zWBoYJY/t+cDYP4nCCiPBkFVMuwmSqZDA4FMjG2AuCk9ToX/1P2j7fTGSa1vhgoABotVrf2tnf3vv6YXPn3futj7v7m58+b+99GTmIIrVn55nUJubK6sbVdIHJl1OcTPNyipdhmc2XVE3/LzPNifLSCsNLabaQ4cTFzHWhUFJ7EzMMMD6J4FTJbRmbF2ghVyjRrEizBUaQU6wkiEuJ2fOTS9NoNB88enb34dM79x8TPgG9dfve6zdvV1+8nJunGb6YmDkxJ5ZpThKk5cSMPR9+8rfvP5icnBZuLrLFa9mEMOGl+ezzyvra+qvLCwybL9Hg56QMJ0GJfHFZ1X5TAWnbcZyW0p5SSbSl1GqNgWFU1jbmrtAZXkxl86nsDUEspXlREMuw819PIND90Zg10AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After adding PR templates&quot;
        title=&quot;After adding PR templates&quot;
        src=&quot;/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/8ff1e/after-pr.png&quot;
        srcset=&quot;/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/9ec3c/after-pr.png 200w,
/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/c7805/after-pr.png 400w,
/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/8ff1e/after-pr.png 800w,
/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/6ff5e/after-pr.png 1200w,
/blog/static/737bfc26dbe3f587c6a82c43cb8a792e/2f950/after-pr.png 1600w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;After adding PR templates&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;By reducing the PR writing process to a series of fill-in-the-blank steps, the PR transformed from a throwaway document to a valuable asset for knowledge sharing across teams that will outlive its authors. PR review instantly became easier, and the feedback on PRs became more relevant. Our experiment in templates was so successful that other teams began asking for our template to use in their repositories. I even had friends outside of work curious what kind of template we were using, so today I’m sharing the template that got us there.&lt;/p&gt;
&lt;h2&gt;Anatomy of a Good PR Template&lt;/h2&gt;
&lt;h3&gt;## Scope&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;!-- Brief description of WHAT you’re doing and WHY. --&amp;gt;

[closes TICKET-###](https://link-to-your-ticket)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The scope is the big picture of &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt;. If your PR were an essay, the scope would be the introduction. Scope eases reviewers into your PR, so they spend less time orienting themselves to the ticket and more time reviewing effectively.&lt;/p&gt;
&lt;h3&gt;## Implementation&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;With @pair

&amp;lt;!--

Some description of HOW you achieved it. Perhaps give a high level description of the program flow. Did you need to refactor something? What tradeoffs did you take? Are there things in here which you’d particularly like people to pay close attention to?

--&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This section gives a human-readable breakdown of the changes in the PR. It might not be immediately obvious to reviewers what every change in the git diff does, so a summary of the technical implementation gives reviewers a context to place those individual changes into.&lt;/p&gt;
&lt;p&gt;I like to leave a spot to call out pairing in this section. It positions pairing as a core value for the team, and it comes in handy when the PR surfaces in a git blame; the more names on the PR, the higher the chance that one of them has an answer to questions.&lt;/p&gt;
&lt;h3&gt;## Screenshots&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;|         | before | after |
| ------- | ------ | ----- |
| desktop |        |       |
| mobile  |        |       |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A picture is worth a thousand words, so definitely include a screenshot section if your team is responsible for any kind of UI component. It’s by far the most direct way to show the changes introduced in your PR. It also provides the fringe benefit of forcing developers to QA their own work. Including a section for web and mobile screenshots ensures that responsive designs are maintained.&lt;/p&gt;
&lt;p&gt;Screenshots might make less sense for backend APIs, but including class or flow &lt;a href=&quot;https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/&quot;&gt;diagrams&lt;/a&gt; is a great alternative to visually demonstrate your changes.&lt;/p&gt;
&lt;h3&gt;## How to Test&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;!--

A straightforward scenario of how to test your changes could help colleagues that are not familiar with the part of the code that you are changing but want to see it in action. This section can include a description or step-by-step instructions of how to get to the state of v2 that your change affects.

A &amp;quot;How To Test&amp;quot; section can look something like this:

- Sign in with a user with tracks
- Activate `show_awesome_cat_gifs` feature (add `?feature.show_awesome_cat_gifs=1` to your URL)
- You should see a GIF with cats dancing

--&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There’s no better way to judge the success of a PR than actually running the code and seeing it work. A straightforward scenario of how to test your changes will help your reviewers see your change in action.&lt;/p&gt;
&lt;h3&gt;## Emoji Guide&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/fc567f1fd73c4581bafd2d17e4d13441/a300b/emoji-guide.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.40136054421769%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABqElEQVQoz41Sa2vcMBD0//9HKTRQUmgDgRZS+q3JXe7hhyRLPj8iW/JNZnXnNBdSqGFYeVcaZmc3c8qhqhTq2kJrg812i32eo7Y25Xf7PYqiZK5ApVQ6l1WFvDj973anutIa8zwj+6K+k8xBm5okDiEExBhTUSDnaZpe8yHEcwwJS03uHo9HZFt3i75/Rtt26PoefhzTpXHBOOF/v0QY3R0MFYo6abtkm4ZRoGiBQIhF1aJcHv4LWW4HKHqh6YF4ZhNxnTxMeWOSZ4ZRvBWvFjUfEnY3N9BUUymdoBZQWVFWyVvJu+aQHojCj1p9JQyuQeSlkd55tiaOTSxMNPrkZ0hniTEN6Yz5b7xsmW1Kq/IFd8BwdY3px0/EYUg58VUG9vab57ek8R1hyV3KSxTaoW8KYHOLIf+NrmvgA2iHQ8mWjW3S2TYtO+EqkXDkCo1UfkH4sFqTsOBQGpg2x539hLL4Cq9+4Xmgt7rGn8c1VusNHlZPeGRU9FUbbgIHOPjxwsfMsOi9T0nnD/hcf6Pae/Z6j6nfoO0GOPpsCYky/fo8RHm77OlC+AJoDU8pJnrM/wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Emoji guide&quot;
        title=&quot;Emoji guide&quot;
        src=&quot;/blog/static/fc567f1fd73c4581bafd2d17e4d13441/8ff1e/emoji-guide.png&quot;
        srcset=&quot;/blog/static/fc567f1fd73c4581bafd2d17e4d13441/9ec3c/emoji-guide.png 200w,
/blog/static/fc567f1fd73c4581bafd2d17e4d13441/c7805/emoji-guide.png 400w,
/blog/static/fc567f1fd73c4581bafd2d17e4d13441/8ff1e/emoji-guide.png 800w,
/blog/static/fc567f1fd73c4581bafd2d17e4d13441/a300b/emoji-guide.png 1176w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I like to include an emoji guide for reviewers in my PR templates, because it enforces code review standards on every PR. Color coding review comments with emojis helps keep feedback organized, disambiguates non-blocking and blocking feedback, and encourages positive feedback as a crucial part of the review process. 😃&lt;/p&gt;
&lt;h2&gt;The Template&lt;/h2&gt;
&lt;p&gt;GitHub makes installing templates extremely convenient. Just copy the template into &lt;code class=&quot;language-text&quot;&gt;pull_request_template.md&lt;/code&gt; (or &lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository&quot;&gt;any of the files listed here&lt;/a&gt;) and commit it to your main branch. From then on, you’ll see your PR template populate your new PRs automatically.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;!--

Please use the content below as a template for your pull request.
Feel free to remove sections which do not make sense.

--&amp;gt;

## Scope

&amp;lt;!-- Brief description of WHAT you’re doing and WHY. --&amp;gt;

[closes TICKET-###](https://link-to-your-ticket)

## Implementation

With @pair

&amp;lt;!--

Some description of HOW you achieved it. Perhaps give a high level description of the program flow. Did you need to refactor something? What tradeoffs did you take? Are there things in here which you’d particularly like people to pay close attention to?

--&amp;gt;

## Screenshots

|         | before | after |
| ------- | ------ | ----- |
| desktop |        |       |
| mobile  |        |       |

## How to Test

&amp;lt;!--

A straightforward scenario of how to test your changes could help colleagues that are not familiar with the part of the code that you are changing but want to see it in action. This section can include a description or step-by-step instructions of how to get to the state of v2 that your change affects.

A &amp;quot;How To Test&amp;quot; section can look something like this:

- Sign in with a user with tracks
- Activate `show_awesome_cat_gifs` feature (add `?feature.show_awesome_cat_gifs=1` to your URL)
- You should see a GIF with cats dancing

--&amp;gt;

## Emoji Guide

**For reviewers: Emojis can be added to comments to call out blocking versus non-blocking feedback.**

E.g: Praise, minor suggestions, or clarifying questions that don’t block merging the PR.

&amp;gt; 🟢 Nice refactor!

&amp;gt; 🟡 Why was the default value removed?

E.g: Blocking feedback must be addressed before merging.

&amp;gt; 🔴 This change will break something important

| | | |
| --- | --- | --- |
| Blocking | 🔴 ❌ 🚨 | RED |
| Non-blocking | 🟡 💡 🤔 💭 | Yellow, thinking, etc |
| Praise | 🟢 💚 😍 👍 🙌 | Green, hearts, positive emojis, etc |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Adding a PR template is a quick and easy way to improve PR hygiene on your teams. As a reviewer, I enjoy a lower barrier of entry in my reviews. As a code author, the reviews I receive are also more useful and productive as a result of including this template. I strongly recommend using a PR template, and I’d definitely consider adding a PR template for any new codebases in the future.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Learning Scala at SoundCloud]]></title><description><![CDATA[I’m a backend developer who worked extensively writing code in Golang before joining SoundCloud. SoundCloud mostly uses Scala for its…]]></description><link>https://developers.soundcloud.com/blog/learning-scala-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/learning-scala-at-soundcloud</guid><pubDate>Tue, 19 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m a backend developer who worked extensively writing code in Golang before joining SoundCloud. SoundCloud mostly uses Scala for its &lt;a href=&quot;https://developers.soundcloud.com/blog/category/microservices&quot;&gt;microservices&lt;/a&gt;, so when I joined, it was important to learn it. As I was new to the language, I faced some challenges going from knowing nothing to becoming skilled at writing code in Scala.&lt;/p&gt;
&lt;p&gt;This blog post will cover how I kickstarted the learning process, what resources I made use of, how the Scala community at SoundCloud helped me learn and grow, how I was able to keep going when things got challenging, and how I set achievable objectives to monitor my progress.&lt;/p&gt;
&lt;h2&gt;Learning a New Language&lt;/h2&gt;
&lt;p&gt;Learning a new language of any kind requires rewiring one’s way of thinking and seeing things. Personally, I had anxiety about not being able to do simple things, like fixing a compilation error that felt straightforward, converting one type to another, or debugging the existing code that had imports that provide implicit functions. However, I found that both being consistent in studying and using a language daily and getting help from people are key to learning a new language.&lt;/p&gt;
&lt;p&gt;To give you more insight into this, I’ll share how I started learning with the help of Scala developers at SoundCloud. More specifically, I’ll talk about how I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enrolled in and completed a Coursera program&lt;/li&gt;
&lt;li&gt;Ordered a book, &lt;em&gt;&lt;a href=&quot;https://horstmann.com/scala/&quot;&gt;Scala for the Impatient&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Mixed up my learning methods&lt;/li&gt;
&lt;li&gt;Eagerly dove into the work and sought help along the way&lt;/li&gt;
&lt;li&gt;Took advantage of learning and development&lt;/li&gt;
&lt;li&gt;Set personal objectives and key results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without further ado, keep reading!&lt;/p&gt;
&lt;h2&gt;Coursera&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.coursera.org/&quot;&gt;Coursera&lt;/a&gt; is an online learning platform for taking courses on a variety of topics via its partnership with universities and companies around the world. It also provides certification for these courses. I began by enrolling in &lt;a href=&quot;https://www.coursera.org/learn/scala-functional-programming&quot;&gt;Functional Programming Principles in Scala&lt;/a&gt;, which is taught by Martin Odersky, one of the designers of Scala. I followed the course to first set up my environment, and then I started with my first “Hello, World!” program. I then began learning the concepts, and I worked on a few exercises.&lt;/p&gt;
&lt;p&gt;It wasn’t as easy as I thought it would be, and a lot of that had to do with coming from Golang. More specifically, here are a few things I found different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go and Scala have different approaches to concurrency and parallelism. Go has &lt;a href=&quot;https://dave.cheney.net/tag/goroutines&quot;&gt;goroutines&lt;/a&gt; and &lt;a href=&quot;https://dave.cheney.net/tag/channels&quot;&gt;channels&lt;/a&gt;, while Scala has &lt;a href=&quot;https://docs.scala-lang.org/overviews/core/futures.html&quot;&gt;Futures and Promises&lt;/a&gt;. As a result, concurrent code written in Go and &lt;a href=&quot;http://twitter.github.io/scala_school/concurrency.html&quot;&gt;Scala&lt;/a&gt; looks quite different. (Here are examples of my code for running a task concurrently in &lt;a href=&quot;https://go.dev/play/p/isrtNAq4yGx&quot;&gt;Go&lt;/a&gt; and &lt;a href=&quot;https://onecompiler.com/scala/3xtr8zj9h&quot;&gt;Scala&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;Scala takes more time to compile compared to Go. The compile time for the code linked above was 3 seconds in Scala and .16 seconds in Go. The slowness of the compiler is thoroughly &lt;a href=&quot;https://stackoverflow.com/questions/3490383/java-compile-speed-vs-scala-compile-speed/3612212#3612212&quot;&gt;described here by&lt;/a&gt; Odersky.&lt;/li&gt;
&lt;li&gt;Go is a procedural and imperative compiled language, and its syntax is similar to that of the C family of languages. Meanwhile, Scala is a hybrid language that combines object-oriented and functional approaches.&lt;/li&gt;
&lt;li&gt;Unlike Go, Scala is very concise and requires less boilerplate code.&lt;/li&gt;
&lt;li&gt;Each language has a different approach to tooling. Common tasks — like compiling code, running tests, and building an application — are built into Go. Scala, on the other hand, has a separate tool to do these things, &lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To understand why applications should use Scala, it was especially helpful to watch videos and read blogs. They demonstrated both why Scala supports both functional and object-oriented programming, and its uses in different applications. There’s a suggested video on Coursera called &lt;a href=&quot;https://www.youtube.com/watch?v=3jg1AheF4n0&quot;&gt;Working Hard to Keep It Simple&lt;/a&gt; (also from Martin Odersky), and it was a great introduction to understanding Scala in a nutshell. I also recommend the blog post, &lt;a href=&quot;https://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html&quot;&gt;Strategic Scala Style: Principle of Least Power&lt;/a&gt;, which aims to provide style guidelines at the “strategic” level. Scala provides various ways to solve a problem, and that post outlines how to choose one from the range of possible solutions.&lt;/p&gt;
&lt;h2&gt;Scala for the Impatient&lt;/h2&gt;
&lt;p&gt;I started reading &lt;em&gt;Scala for the Impatient&lt;/em&gt;, which was ideal for a Scala beginner like myself. It doesn’t just introduce concepts; it also compares them to other languages like Java and C++, which was one of the things I really liked, because it helped me better understand and made me curious about how things are done in Scala. It also helped me visualize the comparison and provided a sense of familiarity when learning a new language. It has simple examples for learning Scala and an extensive list of exercises at the end of each chapter. The book began with the basics — like types and arrays — and it progressed to concepts like inheritance, objects, operator overloading, tuples, and lists.&lt;/p&gt;
&lt;p&gt;One fun fact I learned is that usually, other languages (such as C, Java, or Golang) use &lt;code class=&quot;language-text&quot;&gt;[]&lt;/code&gt; to access an element. Scala, however, uses &lt;code class=&quot;language-text&quot;&gt;()&lt;/code&gt; to access an element. For example, to create an array and to access an element, respectively, in Golang, you’d do the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;var z [Size]String
var i=z[0]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Meanwhile, to create an array and to access an element, respectively, in Scala, you’d do this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;val z = new Array[String] (Size)
val i= z(0)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, both the above statements create an array and access an element; however, Scala uses parentheses to access an element, while other languages — such as Go, C, C++, and Java — use square brackets to access an element. Apart from this being unusual in other languages, using parentheses means that collections are considered functions, and this can be useful to include functionalities like &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; and&lt;code class=&quot;language-text&quot;&gt;filter&lt;/code&gt;, which expect their arguments to behave syntactically as functions, and not just semantically. To understand more of the history of using square brackets and the importance of switching to parentheses, please refer to the &lt;a href=&quot;https://drmarkclewis.medium.com/syntax-for-indexing-why-is-an-antiquated-standard-798aa81f01bb&quot;&gt;Syntax for Indexing blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Switching Patterns&lt;/h2&gt;
&lt;p&gt;All that said, most of the exercises weren’t easy, in the sense that I needed to focus on the problem to solve, and not just the language to express the solution. This is something I felt could’ve been different in the course. Giving problems like Huffman coding, &lt;code class=&quot;language-text&quot;&gt;Anagrams&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LinkedList&lt;/code&gt;, and sorting to Scala beginners is like asking a person who just started learning how to skateboard to do it for 30 kilometers: At this point, the person isn’t really great at skateboarding, much less for that distance.&lt;/p&gt;
&lt;p&gt;Since it takes time to learn and solve the problems, I learned not to rush or feel like I had to finish the course quickly. And to keep things interesting and to reinforce my learning, I regularly changed how I learned by switching between watching videos and reading.&lt;/p&gt;
&lt;p&gt;I had started reading &lt;em&gt;Scala for the Impatient&lt;/em&gt; while still taking the Coursera course, as I found the exercises from Coursera were difficult, and I hoped the book would provide more context. Once I finished reading the basic concepts of Scala and doing exercises from the book, I wanted to try a different learning pattern, so I resumed the Coursera course. This time, I felt like it was easier to listen to and digest the video lectures, as I had just learned these same concepts from the book and the videos served to reinforce those learnings. This is because the simple exercises that I tried from the book provided me with more practical and hands-on exposure to functions and libraries in Scala, which in turn helped me complete Coursera exercises that I wasn’t able to finish before.&lt;/p&gt;
&lt;h2&gt;Diving In and Seeking Help&lt;/h2&gt;
&lt;p&gt;Even though exercises and courses helped me code in Scala, the one thing as a beginner that really helped accelerate my learning was to actually work on the projects. I started working on a pull request approximately two weeks after I started learning Scala to update a Scala version in a SoundCloud repository. It used a library called Scalaz, but it started failing after the update. I could understand the compilation error, but I had no idea how to fix it. As a note, this repository was written years ago, and most of the methods or concepts had been deprecated or replaced in the new version of Scala. These things can be learned only by working on them.&lt;/p&gt;
&lt;p&gt;When I felt like it was really difficult for me to understand and to fix these errors, I asked questions and sought help from the #scala channel on the SoundCloud Slack, and I also did pair programming with colleagues. In doing so, I found that one of the more effective ways to learn a language, beyond just diving in and doing it, is to actually be part of the community and have discussions. It made me realize that it’s OK to make mistakes and ask questions, as this &lt;a href=&quot;https://developers.soundcloud.com/blog/how-we-share-knowledge-as-a-web-collective&quot;&gt;knowledge sharing&lt;/a&gt; helps us learn from each other.&lt;/p&gt;
&lt;h2&gt;Taking Advantage of L&amp;#x26;D&lt;/h2&gt;
&lt;p&gt;SoundCloud has &lt;a href=&quot;https://developers.soundcloud.com/blog/a-happy-new-employee&quot;&gt;great employee policies&lt;/a&gt; for learning and development (L&amp;#x26;D). It provides self-allocated time (SAT) for employees, a budget for learning that allows us to buy books and take courses, and extensive documentation for resources that help us learn and grow as developers. Additionally, SoundCloud has great internal documentation for Scala that covers the best practices, test styles, patterns, and libraries used here to provide guidelines for developers to write idiomatic “SoundCloudy” Scala. This provides sufficient resources for beginners.&lt;/p&gt;
&lt;h2&gt;Setting Objectives&lt;/h2&gt;
&lt;p&gt;Learning is a process, and especially considering that Scala is both an object-oriented and functional language — meaning it has many ways of expressing things — it might take a long time for people to master this language. As such, it’s helpful to set objectives to track learning and stay on course, which is where &lt;a href=&quot;https://www.whatmatters.com/faqs/okr-meaning-definition-example&quot;&gt;Objectives and Key Results&lt;/a&gt; (OKRs) come into play. This is a goal-setting methodology used to set goals with measurable results.&lt;/p&gt;
&lt;p&gt;In my opinion, learning is a neverending process, but being able to track things on an individual and company level is crucial to know that the learning is indeed happening. OKRs helped with this. Together with my manager, I came up with my own OKRs using a performance management toolkit at SoundCloud called Small Improvements. These OKRs were to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finish the Scala course from Coursera&lt;/li&gt;
&lt;li&gt;Create a small personal project using Scala&lt;/li&gt;
&lt;li&gt;Get my hands dirty by implementing as many pull requests as possible&lt;/li&gt;
&lt;li&gt;Perform code reviews on my colleagues’ pull requests&lt;/li&gt;
&lt;li&gt;Record this process in a blog post to help others who are new to Scala&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Key Results&lt;/h2&gt;
&lt;p&gt;After setting and working toward the objectives outlined above, here are my key results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I am certified in the Coursera &lt;em&gt;Functional Programming in Scala&lt;/em&gt; course.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I wrote a web service to create/retrieve a playlist that uses the following libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://http4s.org/&quot;&gt;Http4s&lt;/a&gt; — for the HTTP layer, this library uses &lt;a href=&quot;https://github.com/typelevel/cats-effect&quot;&gt;cats-effect&lt;/a&gt; to manage I/O&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flywaydb.org/&quot;&gt;Flyway&lt;/a&gt; for database migrations&lt;/li&gt;
&lt;li&gt;PostgreSQL for the database server&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://getquill.io/#&quot;&gt;quill&lt;/a&gt; for expressing SQL as a Scala class&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/circe/circe&quot;&gt;circe&lt;/a&gt; for JSON encoding and decoding&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Doing this helped me start a project from scratch, incorporate concepts I had learned, and explore various libraries in Scala. You can see the end result, &lt;a href=&quot;https://github.com/bhuvana-chinnadurai/playlists&quot;&gt;playlists&lt;/a&gt;, on GitHub.&lt;/p&gt;
&lt;p&gt;Additionally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I implemented features by raising 64 pull requests and code reviewed 44 pull requests in Scala at SoundCloud in the past few months.&lt;/li&gt;
&lt;li&gt;I finished writing this blog post.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;In addition to all I did and learned, I had a few additional takeaways from this process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One of Scala’s goals is to make programmers focus on high-level application logic, so it provides extensive built-in methods. This means I can do many manipulations of any type with less effort and time than in Go. One of the best examples of this is the &lt;code class=&quot;language-text&quot;&gt;.map&lt;/code&gt; method. With the help of this method, I don’t have to worry about writing code to iterate an array or list. Rather, I can spend my efforts on writing the logic of what manipulations need to be done on these elements.&lt;/li&gt;
&lt;li&gt;As a developer, I think one of the essential tasks of my job is to write effective test code that’s both readable and easy to write in order to cover simple and complex test cases. One of the simple and yet more efficient testing frameworks that I used in the project is &lt;a href=&quot;https://scalameta.org/munit/&quot;&gt;MUnit&lt;/a&gt;, in combination with the integration library &lt;a href=&quot;https://github.com/typelevel/munit-cats-effect&quot;&gt;munit-cats-effect&lt;/a&gt;. This made it easy to test the stream response for HTTP testing. Meanwhile, there are other simple and more important testing frameworks, like &lt;a href=&quot;https://www.baeldung.com/scala/scalatest&quot;&gt;ScalaTest&lt;/a&gt;, with &lt;a href=&quot;https://blog.rockthejvm.com/testing-styles-scalatest/&quot;&gt;different testing styles&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Because I was a Golang developer for almost four years, I nearly forgot about object-oriented programming concepts like inheritance, encapsulation, and polymorphism. The whole process of learning Scala made me recall and refresh these concepts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Learning is a continuous process, and the longer I’m here at SoundCloud, the more I’m learning. It’s a great opportunity to be able to grow my skills and learn a new language, and if you’re considering doing the same, I hope this post can help you kickstart your learning.&lt;/p&gt;
&lt;h2&gt;Additional References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.scala-lang.org/getting-started/index.html&quot;&gt;Scala documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://users.scala-lang.org/&quot;&gt;Scala community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.scala-lang.org/overviews/scala-book/hello-world-1.html&quot;&gt;Scala “Hello, world” example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=izW2xI6-69M&quot;&gt;Scala Hello World tutorial using IntelliJ IDEA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.github.io/scala_school/&quot;&gt;Scala School!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.scala-lang.org/online-courses.html&quot;&gt;Online Courses from the Scala Center&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://fruzenshtein.com/http4s-another-crud-example/&quot;&gt;Another https4s CRUD tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.innoq.com/en/blog/functional-service-in-scala/&quot;&gt;Functional Service in Scala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@albamus/testing-and-error-handling-in-http4s-2a05572e535d&quot;&gt;Testing and Error Handling in http4s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[The End of the Public API Strangler]]></title><description><![CDATA[This is the story of how we used the Strangler pattern to migrate our public API from a monolithic codebase to a fully fledged BFF over the…]]></description><link>https://developers.soundcloud.com/blog/end-of-the-strangler</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/end-of-the-strangler</guid><pubDate>Mon, 14 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the story of how we used the &lt;a href=&quot;https://martinfowler.com/bliki/StranglerFigApplication.html&quot;&gt;Strangler pattern&lt;/a&gt; to migrate our &lt;a href=&quot;https://developers.soundcloud.com/docs/api/explorer/open-api&quot;&gt;public API&lt;/a&gt; from a monolithic codebase to a fully fledged &lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-1&quot;&gt;BFF&lt;/a&gt; over the course of eight years. It also discusses some of the trials and tribulations we encountered along the way.&lt;/p&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;SoundCloud started as a single Ruby on Rails application more than 14 years ago. Back then, this single application served the website and the public API. While going through multiple growth phases — both in terms of user traffic and the size of the engineering team — we made the choice to more formally adopt a type of service architecture commonly referred to as microservices. This move promised to unblock engineering teams and widen technology choices, in turn allowing us to pick appropriate tools for handling our scaling challenges.&lt;/p&gt;
&lt;p&gt;After the Cambrian explosion of languages, frameworks, and approaches, the engineering organization started to consolidate, and Scala became the default language for delivering our microservice architecture. That’s because it’s underpinned by Twitter’s Finagle as the RPC framework for interservice communication, and we knew we wanted to use Finagle.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;In the new reality of a microservices architecture, where some new features now existed outside of the Rails application, and some services supplemented the existing features of the Rails application, we needed to decide how to maintain the public API going forward. It was crucial to ensure that important features — like serving content — continued to work for existing integrations, even though their implementations had changed and now spanned multiple services.&lt;/p&gt;
&lt;p&gt;We tried various approaches and learned that our Rails application didn’t perform well when interacting with multiple microservices to serve user traffic. As a result, in 2014, we made the decision to not integrate it with other services to serve the public API, but to instead build a Scala service using Finagle that would internally proxy requests to the existing public API. This new service would intercept and augment the public API responses by calling additional services when necessary, (somewhat loosely) following the &lt;a href=&quot;https://martinfowler.com/bliki/StranglerFigApplication.html&quot;&gt;Strangler pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Typically, the goal of the Strangler pattern is to incrementally replace the functionality of one system with the functionality of a new, more desirable system or systems, one piece at a time. It’s commonly used when migrating from a monolithic codebase to a microservice architecture. Although this end goal informed the original decision to adopt the Strangler pattern, our choice to use it was more motivated by an immediate need rather than planning for a future free of the public API monolith.&lt;/p&gt;
&lt;p&gt;As a result, the Strangler was left, along with the monolith, largely unmaintained while feature development on our internal APIs continued at pace. To facilitate the continued development of our internal APIs, it became necessary to duplicate code paths for accessing core entities, e.g. tracks, playlists, users, etc. This meant one code path for internal clients and one for the public API. In addition to the obvious downside of this duplication, inconsistencies between the two APIs also emerged. A lack of maintenance also meant knowledge loss, security issues from exposing the monolith with deprecated Rails versions via transparent proxying from the Strangler, and scope creep due to feature teams often needing to touch the Strangler and/or the monolith without much prior knowledge.&lt;/p&gt;
&lt;p&gt;As the business matured further and new investments in the public API were planned, the case to address the current situation became compelling, and the work was scoped to complete the migration of the Strangler to a fully-fledged BFF. In January 2020, after a six-year period in which modest progress was made, the work began in earnest.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/35d62/strangler-pattern.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.34517203107659%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAABNUlEQVQ4y3WTiY6DMAxE8/9/iYq4KffRUsjqRRpkaDeSZSeZjM+44zg8su/7RbOkOXu/3+cZe4ne685hbNvmu67zWZb5tm395/P5IlyWJZByJiyaO4t12jRN46Mo8kVRfIHYv16vcw/28Xj4qqrOyC8RYszz7PM8933f/yS0KYMty9Kv6xpssiLLS4REMI5jeGi93gkV7TAMgQRtI3UqLiA8ogHawrPnDsfUF1tis8F2igggwmP2ECPYNGSapnAnh8JbLOKUhupxT9eWRDXFBquo7XIaiefzGbpMB+1MWodExDm1juPY13V9NkPzeDYFwjRNA6FSVVqqsbAQJkkS3ljCS5eVMgBI6RyaKCwhtWS8IGV0IP2KUGkzAmh10Eaq2mKLlPmz3++S8v3//jeHWji814/1B8g2/BkgnTVNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Strangler pattern&quot;
        title=&quot;The Strangler pattern&quot;
        src=&quot;/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/8ff1e/strangler-pattern.png&quot;
        srcset=&quot;/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/9ec3c/strangler-pattern.png 200w,
/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/c7805/strangler-pattern.png 400w,
/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/8ff1e/strangler-pattern.png 800w,
/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/6ff5e/strangler-pattern.png 1200w,
/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/2f950/strangler-pattern.png 1600w,
/blog/static/39f04214a9c8fbada5f8f3fd1eafafd7/35d62/strangler-pattern.png 3604w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Porting from the Public API to the BFF&lt;/h2&gt;
&lt;p&gt;Importantly, two preliminary steps helped us reduce the scope of the work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some of the official apps were still making some direct calls to the public API. These were migrated to use the official BFFs, which enabled us to shrink the API surface, and hence the scope, significantly.&lt;/li&gt;
&lt;li&gt;Much of the common functionality in the BFFs was consolidated in &lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-2&quot;&gt;Value-Added Services&lt;/a&gt;, further reducing the scope of the work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without these preliminary measures, completing this project may have been unrealistic.&lt;/p&gt;
&lt;p&gt;Endpoints could be ported with varying degrees of difficulty. In some cases, there were existing reference implementations in other BFFs, e.g. for web or mobile, that could be used as a guide. In others, a complete rewrite was needed, and often it was necessary to add missing functionality to downstream microservices.&lt;/p&gt;
&lt;h3&gt;Challenges&lt;/h3&gt;
&lt;p&gt;Due to the lack of experience within the company with the public API codebase, it was actually necessary to first investigate and document the full list of endpoints that the public API exposed. This involved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adding telemetry to understand which endpoints were still in use.&lt;/li&gt;
&lt;li&gt;Explicitly declaring all known public API routes in the Strangler codebase.&lt;/li&gt;
&lt;li&gt;Adding a fallback to call the public API for any undeclared routes.&lt;/li&gt;
&lt;li&gt;Removing the fallback once we were confident we had identified all routes.&lt;/li&gt;
&lt;li&gt;Removing routes that weren’t in use and weren’t documented on the &lt;a href=&quot;https://developers.soundcloud.com/&quot;&gt;developer portal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Creating a JIRA ticket for each endpoint to be ported, i.e. reimplemented in the Strangler, by calling existing microservices instead of the public API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, some things that come for free (or “magic”) in Ruby were things we needed to implement ourselves — for example, multipart request parameter parsing. Furthermore, Rails doesn’t need to be explicit about all route and &lt;code class=&quot;language-text&quot;&gt;Content-Type&lt;/code&gt; combinations it supports. This sometimes led to unpleasant surprises during porting as it became clear that entire chunks of functionality remained to be implemented.&lt;/p&gt;
&lt;h3&gt;Response Comparisons&lt;/h3&gt;
&lt;p&gt;To build up confidence in ported endpoints, the process typically goes like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ported implementation gets deployed alongside the old code that proxies to the public API.&lt;/li&gt;
&lt;li&gt;Incoming requests execute both codepaths — the old (using the proxy) and the new code.&lt;/li&gt;
&lt;li&gt;The response of the proxy’s call to the public API gets returned to the caller.&lt;/li&gt;
&lt;li&gt;At the same time, the responses of the proxy and the new code are compared for consistency.&lt;/li&gt;
&lt;li&gt;If the responses of the old and new code don’t match, a telemetry event is triggered and the difference is logged for inspection by the developer.&lt;/li&gt;
&lt;li&gt;The developer may then need to make some changes to the ported implementation until they’re confident that the new code matches the original in terms of functionality.&lt;/li&gt;
&lt;li&gt;At this point, the proxy can be removed and the ported response gets returned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, this is only really possible for non-mutating methods, i.e. &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;HEAD&lt;/code&gt; requests. Otherwise, the code might end up creating two entries in the database for a single request. To deal with mutating methods, i.e. &lt;code class=&quot;language-text&quot;&gt;PUT&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;POST&lt;/code&gt; requests, it was sometimes necessary to perform extensive manual regression testing. Not everything went as smoothly as we would’ve liked, and the use of rollout flags also proved useful for quickly disabling the ported code where problems did occur.&lt;/p&gt;
&lt;h2&gt;Learnings&lt;/h2&gt;
&lt;p&gt;Decisions need to be made based on data, and telemetry was key in informing such decisions. For example, undocumented endpoints that receive minimal usage can sometimes be deprecated and later removed. The less code to port and maintain, the better.&lt;/p&gt;
&lt;p&gt;Adopting the Strangler pattern comes with significant risks. As mentioned, there was initially a long fallow period where not much work was done to port the public API endpoints to the Strangler. During this period, some knowledge about the project was lost and the Strangler in fact &lt;em&gt;added&lt;/em&gt; some complexity for teams developing new features. If you decide to adopt the Strangler pattern, make sure to have a plan to complete the migration before the knowledge is lost and it becomes a daunting task with increased risk.&lt;/p&gt;
&lt;p&gt;As with any kind of rewrite, even an incremental one, bugs can occur. The nature of the public API — being accessible to anyone who wishes to use it — meant that sometimes things broke, and sometimes for seemingly innocuous changes. In some instances, the tenet of &lt;a href=&quot;https://www.hyrumslaw.com/&quot;&gt;Hyrum’s Law&lt;/a&gt; came into play, where breakages occurred due to third-party integrations relying on &lt;em&gt;undocumented&lt;/em&gt; aspects of the APIs.&lt;/p&gt;
&lt;p&gt;It’s worth considering whether such disruptions to your business are worth the ultimate benefits of the work.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The work wasn’t without its challenges, and anyone embarking on such a project should be aware of the risks and benefits involved. However, now that all endpoints have finally been ported, there are some notable benefits: the Strangler is now a fully fledged BFF; the entire codebase of the public API has been deleted; and we have a codebase that most engineers can contribute to (Scala service), that doesn’t negatively impact project scope, that fits with our microservice architecture, and that helps ensure data consistency and security.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[What Is New with Periskop in 2022]]></title><description><![CDATA[In a previous blog post, we explained how we built an internal pull-based exception monitoring service called Periskop — which is heavily…]]></description><link>https://developers.soundcloud.com/blog/periskop-in-2022</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/periskop-in-2022</guid><pubDate>Tue, 01 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/81436/periskop_logo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.644171779141104%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAhUlEQVQI1y3OzwpBURAH4LvARi4l/yKuIgtZSDZKEUtJCQtZWOAF5BHIE9v6bp1T35maZn5N9PvENzYkPChzCr0GPY40mbImw5IzLSa8KEa+O11GXMnzZEsnDC8YcmFHIfTaIXjOnmwamESeWmVFTD9cXApXzhhQY5wucuBLncrvHefSnD8nTWWCnpZaigAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Periskop&quot;
        title=&quot;Periskop&quot;
        src=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/8ff1e/periskop_logo.png&quot;
        srcset=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/9ec3c/periskop_logo.png 200w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/c7805/periskop_logo.png 400w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/8ff1e/periskop_logo.png 800w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/6ff5e/periskop_logo.png 1200w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/2f950/periskop_logo.png 1600w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/81436/periskop_logo.png 1630w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In a previous &lt;a href=&quot;https://developers.soundcloud.com/blog/periskop-exception-monitoring-service&quot;&gt;blog post&lt;/a&gt;, we explained how we built an internal pull-based exception monitoring service called Periskop — which is heavily influenced by &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt; — which we open sourced.&lt;/p&gt;
&lt;p&gt;The adoption of this tool at SoundCloud has proven successful during the past year. Currently, it’s a &lt;a href=&quot;https://developers.soundcloud.com/blog/did-i-break-you&quot;&gt;Golden Path&lt;/a&gt; tool in our &lt;strong&gt;observability&lt;/strong&gt; stack, and it’s particularly key when an incident happens. Many teams started using Periskop as part of their debugging workflow — even those that still have to maintain legacy systems that don’t use SoundCloud’s default technical stack.&lt;/p&gt;
&lt;p&gt;This increased use encouraged us to improve Periskop by adding more functionality and releasing more open source projects for the Periskop ecosystem.&lt;/p&gt;
&lt;p&gt;In this post, we’ll discuss recent updates we made to Periskop and its ecosystem. Many of the new features explained here were mentioned in the previous blog post as part of future work.&lt;/p&gt;
&lt;h2&gt;Official Website and New Organization&lt;/h2&gt;
&lt;p&gt;We’re happy to announce that we have our static site pointing to &lt;a href=&quot;http://periskop.io&quot;&gt;http://periskop.io&lt;/a&gt;. We also moved all related Periskop repositories to a new GitHub organization called &lt;a href=&quot;https://github.com/periskop-dev&quot;&gt;periskop-dev&lt;/a&gt;. The goal of this is to change the governance model to one that’s more community-centric and not exclusive to SoundCloud. This lines up with our mission of making Periskop the go-to tool for exception reporting in the cloud native ecosystem.&lt;/p&gt;
&lt;h2&gt;Client Libraries&lt;/h2&gt;
&lt;p&gt;Since we opened Periskop to the community, we wanted users of different languages to also use Periskop for their programs. Initially, we developed a client library for Scala, which is the technology we use the most internally. Later, we started adding more client libraries, and there are currently four:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/periskop-dev/periskop-scala&quot;&gt;periskop-scala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/periskop-dev/periskop-go&quot;&gt;periskop-go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/periskop-dev/periskop-python&quot;&gt;periskop-python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/periskop-dev/periskop-ruby&quot;&gt;periskop-ruby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The development of periskop-go was especially interesting, as Periskop uses it to scrape itself to capture any error that occurs during its execution.&lt;/p&gt;
&lt;h2&gt;Persistence&lt;/h2&gt;
&lt;p&gt;We built Periskop with simplicity in mind: deploy and run. Software that’s similar to Periskop — like &lt;a href=&quot;https://sentry.io&quot;&gt;Sentry&lt;/a&gt; — requires provisioning a database if you want to deploy the service internally to store all the exceptions.&lt;/p&gt;
&lt;p&gt;With Periskop, you can simply deploy using in-memory storage for the exceptions. This has the downside that if you redeploy your Periskop instance, the captured errors won’t persist. However, this usually isn’t a problem, since most of the time, if the scraped service had the errors reported in memory (via a client library), the errors would be scraped again.&lt;/p&gt;
&lt;p&gt;To solve this problem, we decided to extend Periskop, providing the ability to store exceptions in a persistent storage. Currently, Periskop can be configured to use persistent database systems such SQLite, MySQL, and PostgreSQL. Documentation on how to achieve this can be found in part of this &lt;a href=&quot;https://github.com/periskop-dev/periskop#enable-persistance-storage&quot;&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Push Gateway&lt;/h2&gt;
&lt;p&gt;We also wanted to provide &lt;a href=&quot;https://github.com/periskop-dev/periskop-pushgateway&quot;&gt;push&lt;/a&gt; capabilities to Periskop. This is especially useful for shortlived processes like batch jobs or fork-based application servers. In the &lt;a href=&quot;https://prometheus.io/docs/practices/pushing/&quot;&gt;Prometheus documentation&lt;/a&gt;, you can find plenty of good examples and a detailed explanation as to why a push gateway is useful.&lt;/p&gt;
&lt;p&gt;The following graph shows the architecture of the push gateway.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 585px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1f59288cb3837b66689cfd2fbfea7fc1/899a8/pushgateway.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.04273504273506%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB10lEQVQoz42T23KcMAyG9/0frDdNepHpZJajgQ2wHAwYbAPGkquFbTZNLhrNfyHG/EL6LE7uGwEgjWmkLKUspqlYltraHhFO+xkivr+HxwPeAyi3tty2V86fm/opz39I+QIQAqwnayd0b0oFTfPSda8AKWJ1ePbAvWLtXIBYjALWVTgXA5DMCaCbdZKlMYu9LA15my1LrPU0juM0jUIIpZZ5vijlF7m4XNYs06Mo8TCj68VQ+F4bR3UU8jRptEqGgSdJmmUZY3FVcaUYp/NwjFnnB0NVDQAM0VDbvd18sMzaGG6K9s4/tV0558+at40bhhmRAUT7l3EjmADqryjX+IgD2BXRdy7SisgllNxn/t814Qcz27FFD2DUujH9srTrepMxHfE/PF/MsZwas2bOhe+0yfm7yH++XZ6K/FfHn51LAOznmdGfxr6uXd+t1mb3ma0dqFLbcO/cBh6XU6q1L8TAGEvTNIqipumkZE0dBb5grDufeXUVRBsO2ojerNOhT0hm9Y0JjKG7Jc1aK2NoMfJ19Xg7laUr8kWrHPHett42GunqHKmiDq3lj3X9Z8PSYaCNqXba7E6b3vykL7RLgDNiiEAlfEqsDW9L8r2/agEYAaYPklT3D1+hXe967degAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;push gateway&quot;
        title=&quot;push gateway&quot;
        src=&quot;/blog/static/1f59288cb3837b66689cfd2fbfea7fc1/899a8/pushgateway.png&quot;
        srcset=&quot;/blog/static/1f59288cb3837b66689cfd2fbfea7fc1/9ec3c/pushgateway.png 200w,
/blog/static/1f59288cb3837b66689cfd2fbfea7fc1/c7805/pushgateway.png 400w,
/blog/static/1f59288cb3837b66689cfd2fbfea7fc1/899a8/pushgateway.png 585w&quot;
        sizes=&quot;(max-width: 585px) 100vw, 585px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Basically, it’s a simple Go service that you can deploy as a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar&quot;&gt;sidecar container&lt;/a&gt;. Client libraries can use the &lt;code class=&quot;language-text&quot;&gt;push_to_gateway&lt;/code&gt; method pointing to the address and port where the service is deployed, pushing any exported metrics to the gateway.&lt;/p&gt;
&lt;p&gt;We use it in production for our monolith written in Rails with periskop-ruby. There’s &lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt; middleware, which handles exceptions that may happen during the execution of a request, and it pushes them to the attached push gateway of the deployed service.&lt;/p&gt;
&lt;h2&gt;Service Discovery&lt;/h2&gt;
&lt;p&gt;As we’re following the principle of &lt;em&gt;Prometheus, but for exceptions&lt;/em&gt;, we also wanted to have the same &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/http_sd/&quot;&gt;service discovery&lt;/a&gt; mechanism that Prometheus has in place. This &lt;a href=&quot;https://github.com/periskop-dev/periskop/pull/113&quot;&gt;pull request&lt;/a&gt; shows how this was achieved and how we can configure Periskop to use Prometheus service discovery.&lt;/p&gt;
&lt;h2&gt;User Experience Improvements&lt;/h2&gt;
&lt;p&gt;We introduced several improvements to the UI that make Periskop more user friendly — especially when searching or filtering for a specific error during an outage. These are the main improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search for errors — We had an initial search functionality that was only searching for the error name. We extended the search functionality to search any text within the reported errors. This is particularly useful when searching for a specific endpoint or for an error message.&lt;/li&gt;
&lt;li&gt;Filters and sorting — We added the ability to filter per severity type and sort errors per number of occurrences or the date of last occurrence. Thanks to &lt;a href=&quot;https://github.com/sikozonpc&quot;&gt;Tiago Taquelim&lt;/a&gt; for the contributions!&lt;/li&gt;
&lt;li&gt;Mark errors as resolved — We also added the option to mark errors as resolved. This comes in handy if you fix a bug, you deploy it, and you don’t want it to be in the UI anymore.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prometheus Metrics Support&lt;/h2&gt;
&lt;p&gt;All reported errors are instrumented using Prometheus metrics. In the &lt;a href=&quot;https://github.com/periskop-dev/periskop#alert-reported-exceptions&quot;&gt;documentation&lt;/a&gt;, there’s a basic example of an alerting definition for &lt;a href=&quot;https://www.prometheus.io/docs/alerting/latest/alertmanager/&quot;&gt;Alertmanager&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What’s Coming Next&lt;/h2&gt;
&lt;p&gt;Some of the ideas we have in mind for continued improvement and added features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built-in federation (hierarchical collection)&lt;/li&gt;
&lt;li&gt;Time series visualization&lt;/li&gt;
&lt;li&gt;More integrations (&lt;a href=&quot;https://backstage.io/&quot;&gt;Backstage&lt;/a&gt;, &lt;a href=&quot;https://grafana.com/oss/grafana/&quot;&gt;Grafana&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Support for more language and frameworks&lt;/li&gt;
&lt;li&gt;Labeling of errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Periskop is &lt;a href=&quot;https://github.com/periskop-dev/periskop&quot;&gt;open source&lt;/a&gt;, and we’re happy to accept external contributions! If you find this project useful, we’d love to hear from you. Please drop us a line at our &lt;a href=&quot;https://gitter.im/soundcloud/periskop&quot;&gt;gitter chat&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Obvious Ownership: A Sensible Humane Registry]]></title><description><![CDATA[Imagine yourself as an engineer who just joined SoundCloud. Besides meeting your colleagues and getting your new laptop, badge, and that…]]></description><link>https://developers.soundcloud.com/blog/obvious-ownership-humane-registry</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/obvious-ownership-humane-registry</guid><pubDate>Thu, 06 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Imagine yourself as an engineer who just joined SoundCloud. Besides meeting your colleagues and getting your new laptop, badge, and that cool branded hoodie, the first weeks at work will be about &lt;em&gt;exploration&lt;/em&gt;. Onboarding involves &lt;a href=&quot;https://developers.soundcloud.com/blog/a-happy-new-employee&quot;&gt;a lot of “looking around”&lt;/a&gt;: finding mentorship, getting to know the company’s culture, and exploring codebases — and most likely, you’ll want to contribute code too!&lt;/p&gt;
&lt;p&gt;The first question you might ask is &lt;em&gt;“What do I even work on?”&lt;/em&gt;. With time, you’ll identify a few sibling teams you interact with often — because you either consume their data or provide them with yours. So now, it’s important to know not only what you work on, but also which folks depend on you, and vice versa.&lt;/p&gt;
&lt;p&gt;As SoundCloud has grown rapidly — both in the number of people and in software — we’ve identified &lt;em&gt;“Who owns X?”&lt;/em&gt; as a recurring question coming primarily from new joiners, but also from tenured folks. You can imagine how this information can also be useful to an EM advocating for more people on their team, or to an incident-response team when diagnosing an outage scenario. We needed an obvious way to consult system ownership, so we decided to build a &lt;a href=&quot;http://martinfowler.com/bliki/HumaneRegistry.html&quot;&gt;humane registry&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Humane Registries&lt;/h2&gt;
&lt;p&gt;If you’ve been around service-oriented architecture (&lt;a href=&quot;https://en.wikipedia.org/wiki/Service-oriented_architecture&quot;&gt;SOA&lt;/a&gt;) before, you may have heard of service directories. The &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_web_service_specifications&quot;&gt;WS-* standard&lt;/a&gt; for them is called Universal Description, Discovery and Integration (&lt;a href=&quot;https://en.wikipedia.org/wiki/Web_Services_Discovery#Universal_Description_Discovery_and_Integration&quot;&gt;UDDI&lt;/a&gt;), and it’s a complicated mix of service discovery and human readable information. &lt;a href=&quot;http://www.amazon.com/Web-Services-Essentials-OReilly-XML/dp/0596002246?tag=fragmental-20&quot;&gt;A snippet from a classic book describes UDDI&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“At its core, UDDI consists of two parts (…) a technical specification for building a distributed directory of businesses and web services [that] enables anyone to search [its] data. It also enables any company to register itself and its services.&lt;/p&gt;
&lt;p&gt;The data captured within UDDI is divided into three main categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;White pages: This includes general information about a specific company—for example, business name, business description, contact information, address and phone numbers.&lt;/li&gt;
&lt;li&gt;Yellow pages: This includes general classification data for either the company or the service offered. For example, this data may include industry, product, or geographic codes based on standard taxonomies.&lt;/li&gt;
&lt;li&gt;Green pages: This category contains technical information about a web service. Generally, this includes a pointer to an external specification and an address for invoking the web service (…) UDDI can be used to describe any service, from a single web page or email address all the way up to SOAP, CORBA, and Java RMI services.”&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Humane registries play a similar role, but they focus on &lt;em&gt;human-readable information&lt;/em&gt;. These systems are pretty much &lt;a href=&quot;https://www.openhub.net/p/finagle&quot;&gt;what OpenHub does&lt;/a&gt;, i.e. automatically derive as much information as possible from a project based on its code and metadata, and add a layer of prose via wiki-like tools.&lt;/p&gt;
&lt;p&gt;The whole point is that a humane registry acknowledges that while automation and APIs are crucial for an SOA architecture, &lt;strong&gt;people talking to each other is still the most important and effective collaboration we have.&lt;/strong&gt; The registry helps start effective conversations; it doesn’t try to eliminate them.&lt;/p&gt;
&lt;h2&gt;Services Directory&lt;/h2&gt;
&lt;p&gt;When planning our implementation, we had a single requirement: The registry must enable finding out which systems a team owns and who the owners of given systems are. As a stretch goal, we were also interested in exposing dependencies across systems and teams.&lt;/p&gt;
&lt;p&gt;To deliver, we introduced the concept of “manifest files,” which would serve as entity metadata descriptors: Each code repository should have &lt;em&gt;at least&lt;/em&gt; one manifest file. Naturally, one of the fields in the manifest file would be the &lt;code class=&quot;language-text&quot;&gt;owner&lt;/code&gt; team for that specific software piece. These files would then be ingested by the registry on a recurring basis, in order to form an up-to-date software-catalog graph that could then be rendered into the registry’s UI:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;_schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;soundcloud/applicationmanifest/v1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;periskop&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Language agnostic exception aggregator for microservice environments.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;engineering-productivity-team&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tool&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our humane registry was named &lt;strong&gt;Services Directory&lt;/strong&gt;. Of course, later on we learned we were interested in cataloguing more than just &lt;em&gt;services&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/e46ff/early-days-services-directory.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.94234345939243%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAACKklEQVQoz52SXW7UMBSFu094qYANtJqiVmIB7AGJbbAIVKmljNrSmWkmyTiT2HFix4mdv8O1ZyrKCw9EOvJNYn/3HOuefP/8HvhyCnx9h2+f3uLN6Qd8vFjg6uoq6PLykt4vsFgscHZ29k+dn5/jRKkGbJdjl2bIcw5ZVRBCQCmN/3lOalmiEgV614HzgsAptFZoW4O+70nuLw0DfXMWSgpM44BKSjLAw78AHDRDXywBy5BnD9jFd9BVhLrcUKN1kBSroEO9hm1iWPEI1zFEmxsk0S0alR2Ak2EY9BrjsIfICZjcYb9bQvJfBPWQp1D71f9XkvZ2KSb5gL56AkuX4Pt7dGZ/BDY7DPktpj4DpwPP62sk21va9IAio43ejUmCfO3aFEMbYxRLOBMjjm6Qs+VrIDnc32CUj2jqZ4jikRw9BaCuNpjJ+ejYK2V/gATfrK6Rxj4yOwDHLqfIK4qRoNURQTdBulrDqIhcbTFYSmHTo3YHYPNMd5jQnf8ghz9hNHtxSA6qFUXm0HVCDtcoSeKokm/QU9N5LDENAmNP6jKM1Kx3NBXJPV3VCrYVB+BsG8w8BjoF07YoigJlWULSOHDOw0zWdQ1rLZxzmOnQTKMDSVDbYhvHNL85uq47RqaPTubojUZjDCTBPKCqvKpQ+wYe7ldHs+kI3qsSVklE0RZpmF394tBgUiJ0NgQsj0ClVFhDTWqaJhyY5pmiD5hNjXEcwRgLKXwC//wGotJ3xuapQiEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Early services directory UI (2015)&quot;
        title=&quot;Early services directory UI (2015)&quot;
        src=&quot;/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/8ff1e/early-days-services-directory.png&quot;
        srcset=&quot;/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/9ec3c/early-days-services-directory.png 200w,
/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/c7805/early-days-services-directory.png 400w,
/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/8ff1e/early-days-services-directory.png 800w,
/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/6ff5e/early-days-services-directory.png 1200w,
/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/2f950/early-days-services-directory.png 1600w,
/blog/static/51e0fc700e6ab0fd2406bd6ccd16a4c2/e46ff/early-days-services-directory.png 1613w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;More Automation: A Schema Definition&lt;/h2&gt;
&lt;p&gt;By introducing manifest files and ingestion through a services directory, we attained our goal of having a central index for our software pieces. What we observed, though, is that maintaining correctness of the data over time became a challenge: teams get renamed, &lt;a href=&quot;https://developers.soundcloud.com/blog/how-to-successfully-hand-over-systems&quot;&gt;systems are handed over&lt;/a&gt;, etc. In addition, small things like typos, or different ways of referring to teams — as exemplified by the screenshot above with user-growth and User Growth team names — prevented our aggregation logic from providing good results.&lt;/p&gt;
&lt;p&gt;Well, there are &lt;a href=&quot;https://json-schema.org/&quot;&gt;existing technologies&lt;/a&gt; built specifically for the purpose of validating documents. So we began to define a &lt;em&gt;vocabulary&lt;/em&gt; of properties our ingestion system expected, and we encoded them in a &lt;code class=&quot;language-text&quot;&gt;schema&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://json-schema.org/draft-07/schema#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SoundCloud&apos;s Application Manifests Schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    …
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    …
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The next step was to provide a validation engine based on the predefined schema, running it against the manifest files. In the spirit of continuous delivery, we could hook that up in our CD pipelines as part of the linting stage: Our manifests would be validated against each individual change to these repositories, assuring they never drift from the schema.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/12fc05c03bbf2896526f77d940a6a207/fb323/early-days-warning-services-directory.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.528089887640455%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABpklEQVQoz41Ta2+DMAzk//+3SdP2ZdpWStuVlD7CKwTIg5udEsYmbRrqyaSJz+dzSIQQkFIiz3MURYHvz0S/Ff7xJEopeO/hnIMlxPfRWDji8GvQ3m9wc26SpimapgkLa23YMOOI6phhvAqY6gJTFjDyBNNrGDpjjPmGmBcIb7cbRiKw88EY66pCeb1AXs4B5ZVA1pRlCc5hmyK6rltIk6iOF27+01kiJkW2a2CpVet8sIOTfsOiMD/mqOvm7iFvcjQj3PUDltp1bQk3DvdCZOQds4AVOHdpeRiGr2q8yQRE6HULp+pAGFTccjgp4LhQLBK7igrZE+d4UtM8MYpM3Ej0XIjWNia0EpaK2NnrnwiE2/Qd50Kg1wqdaqBI0UDTrOoaFRXjZFbBxlfktwu3YVa2wkL49vqANH2m6Z3QKkl+XqB7FdT51YU1pFD3/Z+Xmq9+Ujw9YtzvMJ0EJnGHFzns8YA+22LYZdBz5HPdNkWXbihuoJd3iq8v8NRFokkJGYRpBf7MurZFRpf+kO2wJ2w3m7AuaYiavi7eD5FsUFpDH/ZAU+MTq16jlKbRY8gAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Manifest validation warning in system details page&quot;
        title=&quot;Manifest validation warning in system details page&quot;
        src=&quot;/blog/static/12fc05c03bbf2896526f77d940a6a207/8ff1e/early-days-warning-services-directory.png&quot;
        srcset=&quot;/blog/static/12fc05c03bbf2896526f77d940a6a207/9ec3c/early-days-warning-services-directory.png 200w,
/blog/static/12fc05c03bbf2896526f77d940a6a207/c7805/early-days-warning-services-directory.png 400w,
/blog/static/12fc05c03bbf2896526f77d940a6a207/8ff1e/early-days-warning-services-directory.png 800w,
/blog/static/12fc05c03bbf2896526f77d940a6a207/6ff5e/early-days-warning-services-directory.png 1200w,
/blog/static/12fc05c03bbf2896526f77d940a6a207/2f950/early-days-warning-services-directory.png 1600w,
/blog/static/12fc05c03bbf2896526f77d940a6a207/fb323/early-days-warning-services-directory.png 1780w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As a bonus, by relying on JSON Schema as a schema-description standard, we were also able to provide ahead-of-commit support (developer experience FTW) by leaning on autocompletion of popular code editors.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ea3e6f8e4e5676603ec09b669627e645/manifest-autocomplete.gif&quot; alt=&quot;Manifest file autocompletion&quot;&gt;&lt;/p&gt;
&lt;p&gt;On top of executing single-pass manifest validation, having an automated way to check for schema compliance opens the door to a more holistic view of the company’s software catalog. Services Directory ingests manifest files, checks syntactically for unwanted properties, and has the ability to report back semantically on values.&lt;/p&gt;
&lt;p&gt;It was easy to set up a validation step to compare &lt;code class=&quot;language-text&quot;&gt;owner&lt;/code&gt; values with a knowingly good list of existing teams, sourced from our &lt;a href=&quot;https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol&quot;&gt;LDAP server&lt;/a&gt;. This means a software piece with an invalid team name gets rejected in a CI environment. It also means we could provide teams with a comprehensive view of their ownership. A background job exports Prometheus metrics on the manifest validation engine, which powers a dashboard, like shown below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/82590969a37aaa937e8eab48a999384e/beee6/manifest-dashboard-global.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.48148148148148%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJElEQVQY0z2QR27DMBBFtUwQwyosYi+SLEsIbATZZJed73+knyFlZ/Ewff6QjVQR87wj5w1SRzCuMaoAbRK4MBVjM5SJENJVq3WiXKLYUq+HIl8/46brBJhQEKNB18sKlwbKefTDWBmFhSJ6psAYCVoPoSyGQdWcJSEtj3rTthKjd0i3GeezQNsKSG9xe9zR0/ITCc7C4Zuu+ugFzieO/fcTfkvVf+s4HmHFj5nw3nJaSAPlqp6N1Q6kMnBCHPGrPjyvL5QXMaHr95QXcIIRLQk2gf7O+gnWZRgiTVdcti84v8DFBSGtcGFGmPbaZ1xCJH/d7lhWupR6dJ1N5K9oYr5S45EoKBMQ8gqfaGGYMC07LV0QKVf6tI1UvyDP1//4NVsE/wBWS5yr0pY90gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Global view for a single team&quot;
        title=&quot;Global view for a single team&quot;
        src=&quot;/blog/static/82590969a37aaa937e8eab48a999384e/8ff1e/manifest-dashboard-global.png&quot;
        srcset=&quot;/blog/static/82590969a37aaa937e8eab48a999384e/9ec3c/manifest-dashboard-global.png 200w,
/blog/static/82590969a37aaa937e8eab48a999384e/c7805/manifest-dashboard-global.png 400w,
/blog/static/82590969a37aaa937e8eab48a999384e/8ff1e/manifest-dashboard-global.png 800w,
/blog/static/82590969a37aaa937e8eab48a999384e/6ff5e/manifest-dashboard-global.png 1200w,
/blog/static/82590969a37aaa937e8eab48a999384e/2f950/manifest-dashboard-global.png 1600w,
/blog/static/82590969a37aaa937e8eab48a999384e/beee6/manifest-dashboard-global.png 1728w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a258273049ea84ee1b19fdc7a8897f36/781de/manifest-over-time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.00348432055749%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAt0lEQVQY01WQWw6EIAxFXcX4LghUQM3sf3d3aEHNfJy0Iae3QDfOBhNZGMeI+YvAJzheykIO/bhimAxm2jCtBo6Teryf2IsjkOXmETpyHnxl+CNiNlYPh5lU0GVLORsJtFXPpR39sBRWfArVI3WFTsUzIxwJ4UxPlaC/QPd60itl9vbewHbDO4gb42L0tiL1T+Dt5Doji9f2qkYnf8f5gE+pEms1nmG2AOt3DaXSP158seK5oFXCf9tmisFyAGcxAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Manifest validation in the entire company&quot;
        title=&quot;Manifest validation in the entire company&quot;
        src=&quot;/blog/static/a258273049ea84ee1b19fdc7a8897f36/8ff1e/manifest-over-time.png&quot;
        srcset=&quot;/blog/static/a258273049ea84ee1b19fdc7a8897f36/9ec3c/manifest-over-time.png 200w,
/blog/static/a258273049ea84ee1b19fdc7a8897f36/c7805/manifest-over-time.png 400w,
/blog/static/a258273049ea84ee1b19fdc7a8897f36/8ff1e/manifest-over-time.png 800w,
/blog/static/a258273049ea84ee1b19fdc7a8897f36/6ff5e/manifest-over-time.png 1200w,
/blog/static/a258273049ea84ee1b19fdc7a8897f36/2f950/manifest-over-time.png 1600w,
/blog/static/a258273049ea84ee1b19fdc7a8897f36/781de/manifest-over-time.png 2296w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;What Else?&lt;/h2&gt;
&lt;p&gt;Ensuring code ownership visibility is just one aspect of Services Directory, our humane registry. Including manifest files as entity descriptors became ingrained in SoundCloud’s engineering culture, and as schema providers, we have a way to ensure standardization across our vast code landscape.&lt;/p&gt;
&lt;p&gt;Since introducing validation logic, we’ve seen higher engagement with Services Directory and identified it as a central piece in our developer experience tooling offering. Various teams and individuals have reached out with feature requests and integration ideas, which we’ve been partnering on to provide — &lt;a href=&quot;https://github.com/backstage/backstage/pull/7795&quot;&gt;we’ll share more about that in the future&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Tiny Letter from Kafka]]></title><description><![CDATA[This article discusses the powerful design choice of Apache Kafka, “an open-source distributed event streaming platform,” and gives a sneak…]]></description><link>https://developers.soundcloud.com/blog/tiny-letter-from-kafka</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/tiny-letter-from-kafka</guid><pubDate>Wed, 13 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article discusses the powerful design choice of &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt;, “an open-source distributed event streaming platform,” and gives a sneak peek at how SoundCloud teams consume event data from it.&lt;/p&gt;
&lt;h2&gt;The Story of the Name&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Why is Apache Kafka named after the author Franz Kafka?&lt;/em&gt; Frankly, this was my first thought when I began learning about Apache Kafka. After researching, I found out there’s a little story behind the name.&lt;/p&gt;
&lt;p&gt;In a &lt;a href=&quot;https://www.linkedin.com/pulse/why-kafka-named-langfei-he/&quot;&gt;LinkedIn post&lt;/a&gt; from 22 November 2019, Langfei He shared the following story, which has been told and retold by many people over the years.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Franz Kafka met a young girl crying in a Berlin park after she lost her favorite doll. Kafka tried to find the doll for her, but he wasn’t successful. He asked her to meet the next day to continue looking, but when they still hadn’t found the doll, Kafka gave the girl a letter he claimed was from the doll.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;It read: “Please do not cry. I have gone on a trip to see the world. I’m going to write to you about my adventures.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And so, whenever Kafka would come back from his travels, he’d meet with the girl and read her the letters. After a few years of this, he came back with a new doll and gave it to the girl.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Upon receiving this doll, the girl said, “This doll does not look like my doll at all.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But Kafka gave the girl a tiny letter, saying it was from the doll. The letter said, “My trips have changed me.” She was convinced and took the doll with her.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The following year, Kafka died.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Many years later, the girl found a letter inside a hidden crevice in the doll. The tiny letter said, “Everything you love is very likely to be lost, but in the end, love will return in a different way.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I found this story amusing, not just because of how sentimental it is, but also because of how it can be used to explain the core flow of Apache Kafka.&lt;/p&gt;
&lt;p&gt;How is that exactly?&lt;/p&gt;
&lt;p&gt;If you want to understand why Apache Kafka is a brilliant choice of a distributed messaging system and why we should discuss it more, keep reading.&lt;/p&gt;
&lt;h2&gt;Publish–Subscribe Messaging Pattern&lt;/h2&gt;
&lt;p&gt;To understand Apache Kafka, you first must understand the publish–subscribe messaging pattern.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern&quot;&gt;The publish–subscribe messaging pattern&lt;/a&gt; has a publisher and a subscriber — the publisher is a sender, and the subscriber is a receiver. The publisher doesn’t send messages directly to the subscribers; instead, it classifies each message under a certain topic without knowing if there are any subscribers to its messages. The same goes for receivers: They request a certain type of message without knowing if there are publishers.&lt;/p&gt;
&lt;p&gt;The publish–subscribe system usually has a broker where all messages are published.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/acf8326884a68d863fb43267147b82a4/a1120/pubsub-system.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.78737541528239%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABf0lEQVQoz3WRTUvDQBCG87u8eBHvnjx7URHUi1oVTx4LgkgVD4KCN6un/gMpFE8Fi21poW1aWls/2uxHskk2yetuYtBSHUj2DfPsO5kZI4oi6NCn7/vgnIMxBk4ZnNDDpFjH2+EDqDmC7Yk4F+cVp/k0Uh9Dv4QQoJTGkJQyeXwJ4blgyoiV2+AfFhxXxCZBEMSM5vU9/Z2aGo6TVE0r/K6oYYsRENfGeDIGsQgCGUxx2syyLMWHyR/anM4Y/RfXnQIylRwumvcxl7KuK+E4SfvGYMhi0e8PMBqNVEsSZrebaNVWu9PBcJjoncoZ5h/XsVHOThXyRB82bSSGK/s9EMpRrb6gVq/DIgSNZhNdZUrVYmq1GkzTBGEUu0+nWCisYbOU/V4Oh21zvPfzGLQvEYUMxtxyC62emJrJX9qTPjLPOSwWt7BdPlGzlCofqeVwiEke9ucNQvkKY/VogKbpxZfCMJqZY6pDdR5Xr7BU2sNB5fwnH1K45A6udYvA6+ALV8BWwKHWb4kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;pub-sub-system&quot;
        title=&quot;pub-sub-system&quot;
        src=&quot;/blog/static/acf8326884a68d863fb43267147b82a4/8ff1e/pubsub-system.png&quot;
        srcset=&quot;/blog/static/acf8326884a68d863fb43267147b82a4/9ec3c/pubsub-system.png 200w,
/blog/static/acf8326884a68d863fb43267147b82a4/c7805/pubsub-system.png 400w,
/blog/static/acf8326884a68d863fb43267147b82a4/8ff1e/pubsub-system.png 800w,
/blog/static/acf8326884a68d863fb43267147b82a4/6ff5e/pubsub-system.png 1200w,
/blog/static/acf8326884a68d863fb43267147b82a4/2f950/pubsub-system.png 1600w,
/blog/static/acf8326884a68d863fb43267147b82a4/a1120/pubsub-system.png 4816w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-1&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;Image 1-1 illustrates how Kafka uses the publish–subscribe pattern. Senders don’t send messages directly to receivers. Instead, senders publish messages inside the broker, which is where all messages are published, just like a bulletin board. Subscribers who are interested in a certain topic will pull the messages from the brokers. Imagine a library: Authors don’t deliver their books to readers directly. Instead, readers check out the books they’re interested in.&lt;/p&gt;
&lt;h2&gt;Topic? Log? Kafka as a Database?&lt;/h2&gt;
&lt;p&gt;In Kafka, a topic is a category name that represents a stream of “related” events. For example, we can set topics like &lt;code class=&quot;language-text&quot;&gt;user_created&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;password_changed&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;new_track_release&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;user_email_confirmed&lt;/code&gt;, etc., and consumer applications can collect the messages from each topic to do useful things that fit our business needs.&lt;/p&gt;
&lt;p&gt;Let’s say there’s a topic called &lt;code class=&quot;language-text&quot;&gt;new_track_release&lt;/code&gt;. Under this topic, the following events can be saved as distributed logs: “Billie Eilish’s track ‘Not My Responsibility’ was released on 26.05.2020”; “Sébastien Tellier’s track ‘Stuck in a Summer Love’ was released on 29.05.2020”; “Kehlani’s track ‘Can I’ was released on 30.07.2020.”&lt;/p&gt;
&lt;p&gt;There’s a timestamp that corresponds to each event, and the latest record will be appended at the end of the log, and the position of the log and the contents of the logs are immutable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/quotes/339670-time-passes-unhindered-when-we-make-mistakes-we-cannot-turn&quot;&gt;There’s a saying from the Dalai Lama&lt;/a&gt;: “Time passes unhindered. When we make mistakes, we cannot turn the clock back and try again.” The same rule applies to Apache Kafka: Nobody can turn back time or manipulate what has happened.&lt;/p&gt;
&lt;p&gt;Another fascinating fact about Kafka is that Kafka can function as a hybrid system between a database and a messaging system.&lt;/p&gt;
&lt;p&gt;Kafka topics are persisted on disk, not memory, for a certain period. During this period, Kafka provides durable storage of the messages. This is called a retention policy, and the retention period can be configured by engineers as per our needs, depending on the topic. For instance, if the topic is &lt;code class=&quot;language-text&quot;&gt;metrics&lt;/code&gt;, we’d set a short retention period, as we usually don’t need the logs of the metrics for longer than a couple of weeks. On the other hand, a topic like &lt;code class=&quot;language-text&quot;&gt;subscription_purchase_history&lt;/code&gt; will need to be set to a much longer retention period because we need a user’s purchase history stored in the system for years.&lt;/p&gt;
&lt;p&gt;In a Kafka cluster, even if the node that stores messages restarts or dies, the data won’t be lost, since data is stored across multiple servers in the cluster. This means there are multiple copies of the same data in a cluster in case one of the brokers goes down or becomes unavailable.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/56dba73bbf0eeff6972cebb8805831f5/06171/kafka-architecture-o.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.054401120709784%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAByElEQVQoz31STW/TQBT070ZcOSAhjhw4pYdUSiVQg6KG0CZx4lJZKZBUIFWibXAbxTgh/v6IU+/u8NbrltAiRhqvpX07O+/NapwLMCbAhYCEoFVU/xKMMXDOSzJikiRYrVbwff++Tp3h5aphC9sFEnEcw3EcTKfTkvZ8jixN8a8zd9Am52u86YS4sPJHDgNyoes6ms0m9olnxjGC5bLsZrtO8BR5doHb/AbagR7h+etfGI6SSlB+1G1hGKLdbqNer6O+u4vTvo6ULrl3VgnywkXk0l54Cq3Vi/Ds1RKDUlCUcyo4Uw6DAL1eTzl818RkMETqeo864cxD7B0ji8aq5YNBiKsfDJfJDbqLT2j/PIF3GyGJYnQ6HTQaDTT29vCZ2s/8ALko8MExcbQY4Sy4ouQCxL5Bgl+g7R+GePLCQd+UDhnSIkdG5JSabLnVaqFWq6G2s4PRUZcEfXCqTIqsZM4L5dA/IcEJtLcUyNOXCxx+jKvY/iTmeR5s24ZlWZjNZrCtawSui4dQM+yrGX77vsb7YYzL643a3JqNfGuGYZTBSH41TZrV3zMsM+QZNmuLUp7//x2m9OZMEpHBdInn4zE21Tt8WHuH37Ih6v4WH97vAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;kafka-architecture&quot;
        title=&quot;kafka-architecture&quot;
        src=&quot;/blog/static/56dba73bbf0eeff6972cebb8805831f5/8ff1e/kafka-architecture-o.png&quot;
        srcset=&quot;/blog/static/56dba73bbf0eeff6972cebb8805831f5/9ec3c/kafka-architecture-o.png 200w,
/blog/static/56dba73bbf0eeff6972cebb8805831f5/c7805/kafka-architecture-o.png 400w,
/blog/static/56dba73bbf0eeff6972cebb8805831f5/8ff1e/kafka-architecture-o.png 800w,
/blog/static/56dba73bbf0eeff6972cebb8805831f5/6ff5e/kafka-architecture-o.png 1200w,
/blog/static/56dba73bbf0eeff6972cebb8805831f5/2f950/kafka-architecture-o.png 1600w,
/blog/static/56dba73bbf0eeff6972cebb8805831f5/06171/kafka-architecture-o.png 4283w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-2 &lt;/div&gt;&lt;/br&gt;
&lt;p&gt;In Apache Kafka, producers and consumers are the applications you as an engineer develop, as you can see in Image 1-2 above. Producer applications write data into the Kafka cluster. Consumer applications then read the data out of the Kafka cluster and do things like generating reports; creating dashboards; sending notifications; and performing business logic, which includes making campaigns or creating another pipeline to send data to other applications.&lt;/p&gt;
&lt;p&gt;To summarize, the architecture of Kafka is like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Producers act as publishers and consumers act as subscribers.&lt;/li&gt;
&lt;li&gt;Thanks to the decoupling, slow consumers don’t affect producers.&lt;/li&gt;
&lt;li&gt;Engineers can freely add more consumers without affecting producers.&lt;/li&gt;
&lt;li&gt;The failure of consumers doesn’t affect the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/bf49b3856564cbb4e790f008abc59456/4f4e5/scoop-sc.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.13937035726919%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABWklEQVQY012QXSsEURjH5yO5ULjjA7had5Iv4FKulaR8ASIpyoWkFLngwstuJKVsyduy77PbmN0xuztnzrz9nDnY5F9Pp56X/+85j5EkCX/j48Pi6TFPsVjA8wSQEMcQd8qI5y1k6ZAk9FQvWkGlhbNxibN+wef2FUaaTH6q6VOvlagWVim/HfHp9pRZhAxj5OsOjbNZ7OwcUTPXN3T3bnkwMjwa0yqmMEQkdCGKIoTwaZgVnOYmzfo5tu3iOC16IlCrFBVtH6xjRXZRHC0v+0xpcIHiyCLl0WWMvJnH8Rw6rouUIaZZwq6tYVZvaLW7GqRwXN+HXN85nOTaFCpx/0e/kmqZWN3GqHaqiEDg60SCZTWV2RVm/YVu1+sPzMzXGZt8Y2C8wMau/X2/IMb3fUIFlYHUpzP4p5SqfPWbNqSQVEsrFsMT7wxl3jk4batMqE4ktWEaURjqoS9JabfWouFuPwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;scoop-consumer&quot;
        title=&quot;scoop-consumer&quot;
        src=&quot;/blog/static/bf49b3856564cbb4e790f008abc59456/8ff1e/scoop-sc.png&quot;
        srcset=&quot;/blog/static/bf49b3856564cbb4e790f008abc59456/9ec3c/scoop-sc.png 200w,
/blog/static/bf49b3856564cbb4e790f008abc59456/c7805/scoop-sc.png 400w,
/blog/static/bf49b3856564cbb4e790f008abc59456/8ff1e/scoop-sc.png 800w,
/blog/static/bf49b3856564cbb4e790f008abc59456/6ff5e/scoop-sc.png 1200w,
/blog/static/bf49b3856564cbb4e790f008abc59456/2f950/scoop-sc.png 1600w,
/blog/static/bf49b3856564cbb4e790f008abc59456/4f4e5/scoop-sc.png 5654w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-3 &lt;/div&gt;&lt;/br&gt;
&lt;p&gt;At SoundCloud — especially on my team, Marketing Tech — we own multiple consumer applications, which do interesting things by consuming topics from the Kafka cluster. As Image 1-3 illustrates, one of the consumer applications we own is called Scoop. Scoop listens to the events for topics from the Kafka cluster, such as &lt;code class=&quot;language-text&quot;&gt;pageviews&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sound_played&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;user_primary_email_changed&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;user_registered&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;user_reaped&lt;/code&gt;, and &lt;code class=&quot;language-text&quot;&gt;user_email_confirmed&lt;/code&gt;. These events are initially emitted to the Kafka cluster from our legendary &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;Mothership&lt;/a&gt;. Then, Scoop pulls the messages from the Kafka cluster and sends them to another system called Bob the Braze Builder, which acts as a proxy to sync data to our CRM tool, &lt;a href=&quot;https://www.braze.com/&quot;&gt;Braze&lt;/a&gt;. Based on the events persisted in Braze, our Growth Marketing Team can create personalized campaigns for each user.&lt;/p&gt;
&lt;h2&gt;Powerful Design Choice of Kafka Consumer Groups&lt;/h2&gt;
&lt;p&gt;By definition, a consumer group is a group of consumer instances that has the same &lt;code class=&quot;language-text&quot;&gt;group_id&lt;/code&gt;. A consumer group exists to read Kafka topics in parallel as a system scales up. And in this section, I’d like to demonstrate why Kafka consumer groups are such a great design choice and how we benefit from them.&lt;/p&gt;
&lt;p&gt;The simple fact that messages don’t get destroyed after being read and committed is key in enabling different consumer groups to work independently.&lt;/p&gt;
&lt;p&gt;How the Kafka consumer group is designed is as follows.&lt;/p&gt;
&lt;p&gt;A consumer instance always belongs to a consumer group; even if there’s only one consumer instance, a consumer instance belongs to a default consumer group automatically. Certainly, it scales to balance to consume more partitions as the system scales up, which means in one consumer group, multiple consumer instances usually exist.&lt;/p&gt;
&lt;p&gt;Another interesting fact is that a partition is read by only one consumer instance within the same consumer group. For example, if Partition 0 is read and committed by Consumer 1, this partition won’t be read by any other consumer instances from the same consumer group. However, Partition 0 can be read by any other consumer instances from different consumer groups. In other words, multiple reads on the same partition can be happening simultaneously or at a different time, as long as the consumer instances reading the same partition are from different consumer groups.&lt;/p&gt;
&lt;p&gt;Remember what I said about Kafka being a database for a certain amount of time? In Kafka, messages are retained for a certain period, as long as their retention period isn’t over. Therefore, multiple consumer groups can benefit from reading the same messages during the retention period. This is one big difference compared to other messaging systems like &lt;a href=&quot;https://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt;, where once a partition is read and committed, the message is removed from the queue.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a64ad971bf11c0e74f82a0319c774d1e/0d868/consumer-instances.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.67588591184096%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACtklEQVQ4y31TTWsTYRDeP1gvnrwIXtSLeBH0UqFCFaR4UIseFCueSkHEUhGttlJrWysmsV9J05A22c2yTTa72c1+vR95nH0326YKfdlh2dmZZ55nZl4N6gzokUMbID+e78NomdjbK6NcrkBv6vDJpzIoTkgJSTn5O/VpUgj6UJDKCBJCSJXkugTYWMNu6TX2d96gebQGx/FVUTmMwWl9MM6hCUKTrI/I+I6wuQTeNyGlILIB7I6NjvkJtZ0H2C9NwjQW0eq4iEWihHyxNjGx+wJPD+ZgRh0FrqV1uLML5+ck7PUJxIfvCSwBYwlMy4PeKOGotoB6dQGmXiSGHhLBwYjhq/o8bhSncLP4CFvuwRBQZtqlW4G0C5A8UvSjmFFyD5axgnrlJfTaDAGuotP1KD6T244cfGitotAtq29B7VMMhVOGs3EP9o+7iA7nMRA+4shDp90lyYuobk1h/89DmK0lJTnkCRIucH9vBmNrt3Bx/Q7WO9sKVEsHwmMPnr6B3tEK4p6elkAU0kBMD81GATpJa9Tm0Wr+huf21URTeT4LUOxWUPP1TGXKMKWZMA4vSMgYQpKa/vT7MSzrmBh+Rb08Df3gGQxjCVbXBZdcxWzaO3hee4vZxmfYsatWRBsM8oUZjOwAAQYSx20Px8YyDivTqG4/IYbLsGwHgQjBCPRxdRaXNsdx+dcECk4lk5wvaW4p43QP40TAcUMajAPbttHrOej3e2AJKZBZ4SiI0GwbaHdpZQRJpgFrOdDJ9hNgzjJOsmkyNrK9aYcpMY1jjNEOC3BqGadVEjSoE4bpkTKT/e1ngCu3DVwbb2GjFKr7k7IeVaJMRQ/fQ98ZQCGykLmPHi5c1zF2Vce7RV/5hDxVcd45A5hXkSJWvXNdmhwS/NuW8+w/QE4XnDGurl5mXPlGY847fwGfWG4uMeYx3wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;consumer-group&quot;
        title=&quot;consumer-group&quot;
        src=&quot;/blog/static/a64ad971bf11c0e74f82a0319c774d1e/8ff1e/consumer-instances.png&quot;
        srcset=&quot;/blog/static/a64ad971bf11c0e74f82a0319c774d1e/9ec3c/consumer-instances.png 200w,
/blog/static/a64ad971bf11c0e74f82a0319c774d1e/c7805/consumer-instances.png 400w,
/blog/static/a64ad971bf11c0e74f82a0319c774d1e/8ff1e/consumer-instances.png 800w,
/blog/static/a64ad971bf11c0e74f82a0319c774d1e/6ff5e/consumer-instances.png 1200w,
/blog/static/a64ad971bf11c0e74f82a0319c774d1e/2f950/consumer-instances.png 1600w,
/blog/static/a64ad971bf11c0e74f82a0319c774d1e/0d868/consumer-instances.png 3471w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-4 &lt;/div&gt;&lt;/br&gt;
&lt;p&gt;Image 1-4 demonstrates that a partition gets read by one consumer instance exclusively within the same consumer group. When there’s capacity, one consumer instance can read more than one partition.&lt;/p&gt;
&lt;p&gt;As you can see, Partition 0 isn’t read by other consumer instances from the same consumer group. However, it is read by other consumer instances from different consumer groups.&lt;/p&gt;
&lt;p&gt;Scaling consumer instances in a consumer group is automatic. When a consumer instance dies, another instance comes up, or a different consumer instance starts to consume from the partitions that are assigned with the instance that has just died.&lt;/p&gt;
&lt;p&gt;Moreover, Kafka stores the offsets where the consumer group has last read. Consider Image 1-5 below. When a consumer processes data, it commits the offsets. Even if a consumer process dies or something happens, it can read back from where it left off, thanks to the consumer offsets. When different consumers that belong to different consumer groups are reading the same partition, those consumers will probably have different offsets, because the pace of reading likely varies.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d74551675820f2cafeb61d65b552aa38/5373d/offsets-kafka.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.355823863636363%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABVElEQVQY001RO0/CYBTtj3J2UicnExPjpIujiZMxRidNHBydTBgNinHxsSk4EQgxPlDRKlioVbBASx9f6ePr8bYV5CYn5+Z+957cez4hDENEwRwO3QhgWAQz4eaPgbqsQmqoaHcZTJujZ/73RLlJeaQxgMATPZQebKhdeqTc8wE/GAFP2PND+ATPC+F6yaDNOAZLcRITLPEQYSOD3PkFvu5OEEhHYO8ZOISYq8dgb2n0lVw8FOJvAwqnz1EWHXT1YFgTtPwa3Nt1XKb3UM9ug91sQC+MoLgJLb+K3mNqKNXRfeym2phdljG9VMfciozlrW+UygyC23khA0Vc5ypQqk/wtWe4nQrxa4xAE4krCEyZTkoEa58uJhYkjM9/YGpRwiTlYzM17Oyr5GFgRV8SG99qKTCNJty+RrcxhNweAfWRVwPPJcXFVcHCwZmO06yB4j2L67+l1bW4ox+iTAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;offsets-kafka&quot;
        title=&quot;offsets-kafka&quot;
        src=&quot;/blog/static/d74551675820f2cafeb61d65b552aa38/8ff1e/offsets-kafka.png&quot;
        srcset=&quot;/blog/static/d74551675820f2cafeb61d65b552aa38/9ec3c/offsets-kafka.png 200w,
/blog/static/d74551675820f2cafeb61d65b552aa38/c7805/offsets-kafka.png 400w,
/blog/static/d74551675820f2cafeb61d65b552aa38/8ff1e/offsets-kafka.png 800w,
/blog/static/d74551675820f2cafeb61d65b552aa38/6ff5e/offsets-kafka.png 1200w,
/blog/static/d74551675820f2cafeb61d65b552aa38/2f950/offsets-kafka.png 1600w,
/blog/static/d74551675820f2cafeb61d65b552aa38/5373d/offsets-kafka.png 5632w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-5 &lt;/div&gt;&lt;/br&gt;
&lt;p&gt;The offset numbers are generated when a message is produced and sent to a Kafka cluster, just like a page number is already created when an author publishes a book.&lt;/p&gt;
&lt;p&gt;The nice thing here is that the ordering is always guaranteed in the same partition; it isn’t possible for consumers to read Offset120 before reading Offset119 inside Partition 0.&lt;/p&gt;
&lt;p&gt;Let’s take a closer look at Image 1-4 one more time. You see that whenever there’s a &lt;code class=&quot;language-text&quot;&gt;new_track_release&lt;/code&gt; by Billie Eilish, the message gets written into Partition 0, Justin Bieber’s message gets written into Partition 1, and Drake’s message gets written into Partition 2. This isn’t magic. The reason behind this is that we as engineers most likely set the same key for the same artist so that the messages that belong to the same artist can be written into the same partition. This is something we can do by setting up the same key when building a producer application (a subject we won’t cover in this blog post). We’d do it this way because the ordering is guaranteed in the same partition, and it’s convenient for consumers to consume the messages in the order of events.&lt;/p&gt;
&lt;p&gt;For instance, let’s say there are two songs released by Billie Eilish with two different timestamps.&lt;/p&gt;
&lt;p&gt;The message, “‘Therefore I Am’ has been released on 12.11.2020” is written to the Kafka cluster before the message, “‘NDA’ has been released on 09.07.2021.” This means the message, “‘Therefore I Am’ has been released” will always have a smaller offset number than the message “‘NDA’ has been released,” as long as these messages are stored in the same partition. The consumer applications will always read the messages from the smaller offsets.&lt;/p&gt;
&lt;p&gt;To summarize, in Apache Kafka:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offsets are created when a message gets written into Kafka and it corresponds to a specific message in a specific partition.&lt;/li&gt;
&lt;li&gt;Consumers read messages by subscribing to one or more topics and reading messages in the order in which they were produced in every partition. A consumer keeps track of its position in the stream of data by remembering what offset was already consumed.&lt;/li&gt;
&lt;li&gt;It’s a consumer’s responsibility to remember which position they’re at in the partition by storing offsets. By doing so, consumer applications can stop and restart without losing their position in the stream of data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some argue that Apache Kafka has a limit of not being able to guarantee the order across partitions. However, as long as you as an engineer set up the same partition key for the related messages, it’ll serve your needs. Usually, having an order guaranteed within a partition is enough.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/369568e1e8a293dcb874aa235f2f9d7e/a0dd6/consumergroup-soundcloud.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.84383170905633%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAB4UlEQVQoz21SPW/TUBT132JhZUPiHzAx8AMYYUIMiAGpG0srvoK6BETEEDVEaRcIiviSQ5rIcWwnduw2rePE9vs8PD87IRV90vGzr+8997xzn4FrlhAC83AOyxprhGEIKeV1qdv45rdBCQXjDExwUEJKQi4QuA4Gv98qvMNs5qsmUhUJcEYhhapRexG7SiphJNkazmQCx54gISkE5eAZRTCbYmJ1FI7h+z5ywrVy1/XxrTeEH4QFxUanJtOE1FvAvvUM1s0nyL47OoWr4sBzMDTfYNRXCjWh0IW1jxe4fc/We/FdxEulJQyWZDh/+gnhozrYWVL0AlvnCKaeUv1F4etWYVEwtHO8qi/0XizlDgjb0BUenidw7+xhfOMxVsen2j+2Jlrh4Ne+8vCl8nC2Vfi+GePuAxcfjlYAC0FGr7E0D5D+OQC16zB4ShAfdrF40Qb1L3QXobyIokhN19eIojNkeanwRz/F8/0QPwcUoBGodYh0WEM+qoE5DRjyv2tQPkqTUUFWPgk0Pse4/3CKRuuyGocSujMeQ8znIJaNfGiBex5kdXUq5m1q2UBciYl0DX46QGb2kZsm+NhSR+52sWo2ESvQTgdyudwh+IfdE+hY8Z4kyE5OELfbuGy1QHs9/AUmSO8itoWCAAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;soundcloud-consumer-group&quot;
        title=&quot;soundcloud-consumer-group&quot;
        src=&quot;/blog/static/369568e1e8a293dcb874aa235f2f9d7e/8ff1e/consumergroup-soundcloud.png&quot;
        srcset=&quot;/blog/static/369568e1e8a293dcb874aa235f2f9d7e/9ec3c/consumergroup-soundcloud.png 200w,
/blog/static/369568e1e8a293dcb874aa235f2f9d7e/c7805/consumergroup-soundcloud.png 400w,
/blog/static/369568e1e8a293dcb874aa235f2f9d7e/8ff1e/consumergroup-soundcloud.png 800w,
/blog/static/369568e1e8a293dcb874aa235f2f9d7e/6ff5e/consumergroup-soundcloud.png 1200w,
/blog/static/369568e1e8a293dcb874aa235f2f9d7e/2f950/consumergroup-soundcloud.png 1600w,
/blog/static/369568e1e8a293dcb874aa235f2f9d7e/a0dd6/consumergroup-soundcloud.png 4207w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Image 1-6 &lt;/div&gt;&lt;/br&gt;
&lt;p&gt;Image 1-6 shows how the same partition can be read by different consumer groups and do useful things independently at SoundCloud. In this diagram, Scoop listens to a topic, &lt;code class=&quot;language-text&quot;&gt;pageview&lt;/code&gt;, which fires when a user views a &lt;a href=&quot;https://checkout.soundcloud.com/go&quot;&gt;subscription page&lt;/a&gt;. For instance, if user#1 with a Go subscription checks out the subscription page, the &lt;code class=&quot;language-text&quot;&gt;pageview&lt;/code&gt; event gets fired. When this event fires, Scoop will consume the event and sync it to our CRM tool, &lt;a href=&quot;https://www.braze.com/&quot;&gt;Braze&lt;/a&gt;. Then, our Growth Marketing Team can provide personalized campaigns or discount suggestions on a Go+ subscription to user#1. At the same time, our Data Science Team’s data analysis consumer group can consume the same event from the same partition and create a report to analyze user#1’s behavior. This demonstrates how Kafka messages can be consumed by multiple downstream systems to do interesting and useful things.&lt;/p&gt;
&lt;h2&gt;In the End, a Message Will Return in a Different Way&lt;/h2&gt;
&lt;p&gt;Returning to the story with Franz Kafka, remember how the little girl found the tiny letter hidden in the doll many years after Franz Kafka passed away? Now you might understand that the doll acts as a broker, and Franz Kafka himself acts as a producer, and the little girl is like a consumer.&lt;/p&gt;
&lt;p&gt;Of course, this story is oversimplified. In reality, our engineering ecosystem mostly lives in a &lt;a href=&quot;https://www.confluent.io/learn/distributed-systems/&quot;&gt;distributed system&lt;/a&gt;. It requires Kafka to be scalable, reliable, and fault tolerant.&lt;/p&gt;
&lt;p&gt;Kafka’s choice of a publish–subscribe system and the architecture of consumer groups are quite valuable. Ultimately, Apache Kafka:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is a great tool for &lt;a href=&quot;https://en.wikipedia.org/wiki/Extract,_transform,_load&quot;&gt;ETL&lt;/a&gt; and big data ingestion&lt;/li&gt;
&lt;li&gt;Has disk-based — not memory-based – retention&lt;/li&gt;
&lt;li&gt;Has a high throughput&lt;/li&gt;
&lt;li&gt;Is highly scalable&lt;/li&gt;
&lt;li&gt;Is fault tolerant&lt;/li&gt;
&lt;li&gt;Has fairly low latency&lt;/li&gt;
&lt;li&gt;Is highly configurable&lt;/li&gt;
&lt;li&gt;Tackles &lt;a href=&quot;https://en.wikipedia.org/wiki/Integrative_complexity&quot;&gt;integration complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Allows multiple producers and consumers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As an engineer, it’s your decision to design system architecture, data flow, and which messaging system to use for your business needs. Apache Kafka can be a useful choice if you want your event data to be retained and consumed by many other downstream systems, and if you want to fight the ever-growing complexity of integration in the future!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Service Architecture at SoundCloud — Part 3: Domain Gateways]]></title><description><![CDATA[This article is the last part in a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over…]]></description><link>https://developers.soundcloud.com/blog/service-architecture-3</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/service-architecture-3</guid><pubDate>Fri, 17 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is the last part in a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over the past few years, and how we’re attempting to solve some of the most difficult challenges we encountered along the way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-2&quot;&gt;In the second part of this series&lt;/a&gt;, we discussed how we evolved the use of the BFF pattern in SoundCloud by moving existing duplicated logic to a more centralized and elegant solution called Value-Added Services (VAS). We covered how we benefit from this architecture pattern, as we have all the authorization, content policies, and fetching logic of tracks and playlists in a single service.&lt;/p&gt;
&lt;p&gt;In this blog post, we’ll cover how we evolved the concept of Value-Added Services to Domain Gateways, which allow us to extend those services to have read and write operations in a single and centralized service for each business domain.&lt;/p&gt;
&lt;h2&gt;Growing Aggregates&lt;/h2&gt;
&lt;p&gt;As we described in the previous installment, the core responsibility of a VAS is to serve our core aggregates, such as Track and Playlist. To do this, a VAS fetches states for associated entities and value objects from corresponding Foundation&lt;sup&gt;&lt;a href=&quot;#layers&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; services, and then it applies business authorization rules. For example, the Tracks VAS will filter out all tracks that are geoblocked in certain territories.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b61d5a86fd6620f6033bfba9950d83b6/c591e/aggregates.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21.279554937413074%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAArUlEQVQY012PbQ+CMAyE+f9/zw8IOFRUwGRDEFjHlJ0dLzGwpOm63D27BnAOvpzvwDLmfBPcz9xTWCtQ1yH6XqCqMih1AVHp1Yt+9ntCsAd6kR0KrhO+H8HGK4yJ0OsjDKWoVAElc5B+snbcADEBV4ybf/NA0iW6LoHWMaQUnCxE04TTrGTGbxkGU2wSzs4d8A9+TKsSJWw+oG0jvJuYk8a8+g31645xzDee9fwAtvU2lFkE8P0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Aggregates&quot;
        title=&quot;Aggregates&quot;
        src=&quot;/blog/static/b61d5a86fd6620f6033bfba9950d83b6/8ff1e/aggregates.png&quot;
        srcset=&quot;/blog/static/b61d5a86fd6620f6033bfba9950d83b6/9ec3c/aggregates.png 200w,
/blog/static/b61d5a86fd6620f6033bfba9950d83b6/c7805/aggregates.png 400w,
/blog/static/b61d5a86fd6620f6033bfba9950d83b6/8ff1e/aggregates.png 800w,
/blog/static/b61d5a86fd6620f6033bfba9950d83b6/6ff5e/aggregates.png 1200w,
/blog/static/b61d5a86fd6620f6033bfba9950d83b6/c591e/aggregates.png 1438w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Roughly speaking, one can imagine a VAS as a big fanout together with authorization logic. One of the first challenges that we faced while extending our Value-Added Services was the size of this fanout. As we were adding features to the platform, our aggregates — and hence the amount of network calls — were growing as well.&lt;/p&gt;
&lt;p&gt;On the other side, our BFFs often have different needs dictated by their applications. For example, one track feature might only be available on mobile, which makes fetching the entire track aggregate from the Web API unnecessary. Moreover, even within a single BFF, we sometimes support multiple aggregate representations that can be built without fetching all dependencies.&lt;/p&gt;
&lt;p&gt;How can we provide centralized endpoints for serving aggregates that can be customized to the specific needs of BFFs? Luckily, this problem has a pretty straightforward solution — &lt;a href=&quot;https://developers.googleblog.com/2017/04/using-field-masks-with-google-apis-for.html&quot;&gt;partial responses&lt;/a&gt;. This pattern allows API consumers to tell the producer which part of the response they’re going to consume by specifying a &lt;a href=&quot;https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/field-mask&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FieldMask&lt;/code&gt;&lt;/a&gt; in the request. Field masks support protobuf and JSON representations that make them essentially protocol agnostic.&lt;sup&gt;&lt;a href=&quot;#graphql&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;In our particular case, we use &lt;a href=&quot;https://soundcloud.github.io/twinagle/&quot;&gt;Twinagle&lt;/a&gt; — a protobuf IDL based on the Twirp protocol. Protobuf definitions provide type-safe construction and validation out of the box via &lt;a href=&quot;https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/util/FieldMaskUtil&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FieldMaskUtil&lt;/code&gt;&lt;/a&gt;s that we’ve ported to the ScalaPB library.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 705px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b0b9f84c3fe26095b7fec2dc6a24cda0/caaab/aggregates-field-mask.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.297872340425535%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABWElEQVQoz4VSa2/CMAzk//85NMYgLW/adCtJ0/eDNuF2LYxJE9I+nCw5tnNn32wYFIZB/wtrE+BmcLslcC5hVAB+4dyFNR1mzgk+bonNSzi3YcMWbb9ErObQaoksf8clDhAGEjIMEMlPpObE+gIzwGfD4Q+Oj7gndlOsmhVMukRV+WgaD1UZIk0Jc0aWStTViZ+XmJkkQFkcUZYCeS5QFB6yTLBRoOt2ZPSFPD2g7T5Q1CvW+uh7n/GEJJEwRjJGaJvjnaFWZxT5gcPIwKz5q4DWKw5eoW23OOz3MHqLpltCZwvWCOYFWQWUHUJdAsSxJOOn5M0LyXfZ426tFTyKRzk7uJ57tdw319BfN5R75OCQcsNpz5PkV0f5OYRSC0TRG6ScY7A+bO/hWq/57k17HA8iw4gsI+Z8ggytVZMNXqFtL6jreMJoLzda5mkb2gj6YZvRVpr5K74BYyRhcbHj6I0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Aggregates Field Mask&quot;
        title=&quot;Aggregates Field Mask&quot;
        src=&quot;/blog/static/b0b9f84c3fe26095b7fec2dc6a24cda0/caaab/aggregates-field-mask.png&quot;
        srcset=&quot;/blog/static/b0b9f84c3fe26095b7fec2dc6a24cda0/9ec3c/aggregates-field-mask.png 200w,
/blog/static/b0b9f84c3fe26095b7fec2dc6a24cda0/c7805/aggregates-field-mask.png 400w,
/blog/static/b0b9f84c3fe26095b7fec2dc6a24cda0/caaab/aggregates-field-mask.png 705w&quot;
        sizes=&quot;(max-width: 705px) 100vw, 705px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One disadvantage of field masks for partial responses is a tighter coupling between microservice topology and aggregate schemas (IDLs). Field masks can be defined according to service dependencies and network calls to reduce the number of requests necessary to produce a BFF representation. At SoundCloud, our focus is more on the reduction of complexity in the edge layer (specifically in BFFs). While field masks can optimize network calls as well, it isn’t necessary to have a 1:1 mapping between field masks and network calls.&lt;/p&gt;
&lt;h2&gt;Commands&lt;/h2&gt;
&lt;p&gt;While we were extending the scope of the VAS to serve aggregates of our entities, we identified that we could also extend our VAS to those actions that mutate the state of the core entity (i.e. write operations) but at the same time would require authorization logic. To centralize even more core entities, we extended our VAS with commands. Some examples of these command operations in the &lt;code class=&quot;language-text&quot;&gt;Tracks&lt;/code&gt; domain include “download a track,” “like a track,” and “repost a track.”&lt;/p&gt;
&lt;p&gt;Since it’s an operation that lives in the VAS, it also has the benefit that we reduce complex logic in BFFs (in case such logic was duplicated there) and improve reliability in terms of access logic of those operations that require grant access to a given track.&lt;/p&gt;
&lt;p&gt;We can illustrate the case of liking a track in the Track VAS:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/f9c2f615af92a37f90baec6658ec4eb9/372fc/like-track-command.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.426807760141095%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA8UlEQVQY011Qi26DMBDr///dpJVC0pC2W3i0bLwDoTSeoUhIQ3Ic+Q6fcwfwe70meB/zpskXQvMeU9cYR4W6jpAYiab+Yu3K2t7zxpm9d2rAYTm8d2jbG7pWww4hxkGh/DXoe4GqDtA2EsXDUPuGpVaWR3SdxD2/ISd+iivm+Z9h3y2GMXobwlqJPFNMJ9F2ETmEGyPqJwwc6NwZ06SZOCUyDjS74fvJjqfa4l/YLJjuSJZsVASftHFVBSiKT6ZUSE3KVRgON5jcY7HaDedZrPCeu2oCJMkH1xCwKlkXGySeT8GEITlaBzsnyCf+m60J/wAZ33184t1rkQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Like Track Command&quot;
        title=&quot;Like Track Command&quot;
        src=&quot;/blog/static/f9c2f615af92a37f90baec6658ec4eb9/8ff1e/like-track-command.png&quot;
        srcset=&quot;/blog/static/f9c2f615af92a37f90baec6658ec4eb9/9ec3c/like-track-command.png 200w,
/blog/static/f9c2f615af92a37f90baec6658ec4eb9/c7805/like-track-command.png 400w,
/blog/static/f9c2f615af92a37f90baec6658ec4eb9/8ff1e/like-track-command.png 800w,
/blog/static/f9c2f615af92a37f90baec6658ec4eb9/372fc/like-track-command.png 1134w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As we can see in the graph, BFFs would send a request to the &lt;code class=&quot;language-text&quot;&gt;Tracks&lt;/code&gt; service to perform a track operation. The service that usually registers “like” operations lives in the &lt;code class=&quot;language-text&quot;&gt;Likes&lt;/code&gt; service. This service isn’t aware of track authorizations; it only creates/deletes links between tracks/playlists and users. That’s why we need to check first if the user who wants to like a track has access to it. The best place to achieve such logic in a centralized place is the Tracks VAS.&lt;/p&gt;
&lt;h3&gt;Separation of Queries and Commands&lt;/h3&gt;
&lt;p&gt;To summarize, the VAS interface consists of two parts: an endpoint to serve its aggregate according to BFF needs, which we call queries; and endpoints that expose core entity operations, which we call &lt;em&gt;commands&lt;/em&gt;. This separation is the core idea behind the &lt;a href=&quot;https://martinfowler.com/bliki/CQRS.html&quot;&gt;CQRS&lt;/a&gt; pattern and provides some practical benefits, as it’s possible to provide separate upstream services or stores for read and write operations. For example, the foundational service that provides operations to add or remove a follow/er/s relationship between two users (a write) is different from the service that serves follower counts. This relationship between foundational services is now abstracted away from users of the Tracks VAS, which improves consistency and reduces complexity in the BFFs.&lt;/p&gt;
&lt;h2&gt;Beyond Core Entities: Domain Gateways&lt;/h2&gt;
&lt;p&gt;As our VAS grew in scope, we identified that a single core entity (like a track) can be used in different domains, for different purposes, and with different access patterns and authorization requirements. For example, SoundCloud not only provides a consumer application to a music catalog; it also provides tools for creators to upload and distribute their music. Consumer and Creator are different domains, owned by different teams — all of them referencing and using tracks for different purposes within their specific domains.&lt;/p&gt;
&lt;p&gt;A possible approach in this case is to implement everything that can possibly be done with a track (in all the different domains) — including related queries and commands — in a single VAS. This can work well for some time, but eventually there’s a risk of creating large amounts of coupling and complexity, causing friction and decreasing productivity.&lt;/p&gt;
&lt;p&gt;A more scalable approach is to identify the different business domains that need to make use of a given entity and create a &lt;em&gt;Domain Gateway&lt;/em&gt; for each of them. In essence, a Domain Gateway is an implementation of a VAS tied to a specific business domain. Each one can be maintained by different teams and represent different views on a given entity, relying on the same foundational layer of services. This façade can provide stability and act as an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer&quot;&gt;anti-corruption layer&lt;/a&gt; for each of the domains.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://eng.uber.com/microservice-architecture/&quot;&gt;Domain Gateway&lt;/a&gt; approach involves a certain level of duplication in exchange for autonomy and increased scalability, and it makes sense to apply in cases in which the different domains have very different access patterns and highly disjoint feature sets, or when communication and collaboration between teams is difficult (for example, due to geographic location of teams and non-overlapping time zones).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ec181b9744d7fb275664e28f622556ff/99bf1/track-domain-gateways.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.62114537444934%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAACZUlEQVQ4y3VUi3KbMBDM/39dp3nUODzMuyQu5mmEkLzdEwkxk5aZG0mHbrV3t9LD7XaDfJ+jtQrGhBxPtIj+dey6F1wuj+j6Z/oi6DlE1+bouwzLktFnXPzDPaBMrZ24ev0wn5t9KOVhno/8F/CwyPlk/f4Wo/6T0J98AX6xW4GBCWaJeXILrUvU9U9U1SPO52eM4wFDX2BWIdo2QJ7FKPITxqFgnHGENobGLCjLCn1f4fyew/c10vRM0F9k6Dt21oa41AWGISTLENOUQE0x0/9HysYYMiA7M5JBxMCGbFKmRlbDgcEHBvpMMcV1jOj3UBYRzm+ScvodUGvNH1JHtdUPCLj22YxnB7gsAX6XGQET5x+HFNP1PzVUSn00ZSbLhJY6szYjm5gsCTAGrN2J69SxAhLaifsKxtovQElXGK6ymbZ6fZp0tadcxMbxZfuvVMzynByBjaExN9ZC6mY3QEkHCF3Kq0WOyTrG3CNSOrLDCd6qDNbsagg0F8UN+g6QtXOAIU/3URQ/KJ8nHuqhbXLWM+K+I+MS17jbLd7XUL55VjthC6g0Rrqp9XE9hGbYGBH1shyphBP6Nt4D3utQ64WzKxnklMeMpqnoO7BunuuygJ/fC2YTsdO+63hZJGSefRf2sqzC7rrKja+vCkncULgelGPku/Tl7mq9Xr+2yXifM6eGHcNPWyV0JZOE2hsZLGxeyM67SzPnIQLo8S6nzCTb6/D7a7N/HIwhyOWJoHIFA4IUlI/IReYZSyCMyZApy3OwNeX++bI2dsJdT2YnhwDXa+B0V9cZa5o6scs1vNQp2ZYbw7+Ry9CFhP2uGwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Track Domain Gateways&quot;
        title=&quot;Track Domain Gateways&quot;
        src=&quot;/blog/static/ec181b9744d7fb275664e28f622556ff/8ff1e/track-domain-gateways.png&quot;
        srcset=&quot;/blog/static/ec181b9744d7fb275664e28f622556ff/9ec3c/track-domain-gateways.png 200w,
/blog/static/ec181b9744d7fb275664e28f622556ff/c7805/track-domain-gateways.png 400w,
/blog/static/ec181b9744d7fb275664e28f622556ff/8ff1e/track-domain-gateways.png 800w,
/blog/static/ec181b9744d7fb275664e28f622556ff/99bf1/track-domain-gateways.png 1135w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;As we discussed in the &lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-2&quot;&gt;previous blog post&lt;/a&gt;, the evolution of SoundCloud’s architecture into a three-tier architecture with Value-Added Services as authoritative entry points for accessing aggregates has proven successful — even more after evolving them into the concept of Domain Gateways. This is a pattern that we’ll continue adopting and applying in the future.&lt;/p&gt;
&lt;p&gt;We plan to move other operations that are duplicated in our codebases to their respective gateways. This will provide more flexibility to evolve our system as soon as we want to add new functionality without the hassle of duplication in each of the BFFs.&lt;/p&gt;
&lt;p&gt;In parallel, we’ll continue encouraging feature teams to evolve their microservices architecture around their core domain. This will create a more solid landscape where business logic is centralized and more easily accessible from other dependent systems.&lt;/p&gt;
&lt;p&gt;Finally, we’re still exploring the possibilities enabled by Domain Gateways — including improved team autonomy and reduced cycle times for our development process.&lt;/p&gt;
&lt;hr style=&quot;border: 0;
    height: 0;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
    border-bottom: 1px solid rgba(255, 255, 255, 0.3);
&quot;/&gt;
&lt;p&gt;&lt;a name=&quot;layers&quot;&gt;1&lt;/a&gt;: &lt;em&gt;For a review of SoundCloud’s architectural layers, refer to our previous &lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-2&quot;&gt;blog post&lt;/a&gt;.&lt;/em&gt;
&lt;br/&gt;
&lt;a name=&quot;graphql&quot;&gt;2&lt;/a&gt;: &lt;em&gt;GraphQL is an alternative approach to provide an API that can be customized to consumer needs. Although it provides more flexibility, we decided that its benefits won’t offset the cost of migration from our standard Twinagle stack.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Service Architecture at SoundCloud — Part 2: Value-Added Services]]></title><description><![CDATA[This article is part of a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over the past…]]></description><link>https://developers.soundcloud.com/blog/service-architecture-2</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/service-architecture-2</guid><pubDate>Fri, 20 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is part of a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over the past few years, and how we’re attempting to solve some of the most difficult challenges we encountered along the way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/service-architecture-1&quot;&gt;In the first installment&lt;/a&gt;, we covered the use of the BFF pattern within SoundCloud, detailing its pros and — more significantly — its cons. While the BFF architecture comes with many benefits, such as optimizing backends suited for different clients and a higher level of resilience than a shared single backend, its implementation at SoundCloud became problematic over time. Unnecessary complexity and duplicate code developed. Even worse, we had business and divergent authorization logic living in each of the BFFs, which is a dangerous pattern, as the maintenance and synchronicity of this code is of paramount importance. It became clear that we needed a different approach: Enter Value-Added Services (VAS).&lt;/p&gt;
&lt;h2&gt;Value-Added Services&lt;/h2&gt;
&lt;p&gt;First, let’s cover the different service layers at SoundCloud that make up this architecture.&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/f570d/Top_Level_Architecture.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 80.625%; position: relative; bottom: 0; left: 0; background-image: url(&amp;apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAAEfklEQVQ4y43Ue0wTdxwA8CugMFhGorjExCwzJiMjyh+6mCx7mBnIBptzGzHlKSCPyngNTJYxNVFnBGUigo6+rr2212uvUNperw/6oi2964s+aQtmhFdASNZMZFMz/7ldu4X9sf2xb3L53S/53ue+33x/dwBAB4qigFesY6TuPRJdsQ/COX6Rrt8n1nf5hTh/VmK45BZglx3sCVEANn6cynOJVRnAf4XRqAe0uAEgYCyd4BFojro4ajXB1Qg8Yu1VDx83eSB8wCPCRwkeRvilhopUXlhGZFEwlcGq72Z0XmnLkGAgg6Kof79AacSLpgnyG9LruzVlt97R4Dq2kyA5BOkdcrjdVw12y2dy0YOsVO70ieQr9JLz9/VXbO+8YCwvxnKEykfZZdeSWRRft4dCF/Ztz9ryKf7cPv3Q8H5KvHFgm798iBpMvk4VUQxca8kHMcMbI6jyTUhnKHAtWA47YqYjxLyNAdQxS/ek4OQCdOrJEtI2pLZd4uHmEaHZ0cvFrRzIPtPF0ZrHhBZnK4jZ7vO0lrtq3PcuBzNoH0xiTonV3uf72anyLxKO+FYoF+hoKs9Ogb/ERhr/WAM5P2oc13l6k1Jktt8e05k0EovzNltn1kgMMzcg/bQM0tkRIx4uHVPj5nvohE9snb4cXnFjcxsBYunpfB7w+5YrXeGzZWHti2XeEPA/YqDK9erQsO3w4IjpkFRrPxjfDBpjj4NOGs0Flsjr6em+XOFW/LYiECB+2bDSK5rCwsgVdQDpRwmRhV7HNAGkR04Ipsa9EF8bQtonvJBUFRAhhjlZSWIzPBl7HCBoOBdorDqVajk7Yb9ZvxF9yENDiu81ARjCQkiHNoz0qoMwSGM3sJC0QekTCybcksFJt6xV6ZXyVQGYp4ugH9Ggahecs11LV/hkfrRiNTwKIV4YUpCgjwb6VbPi+ygBulV+sQSPSPvkLtCn9InGsaC0T0EKbBMeaEYbkn2Z2ApP7ILP1ycz0y2vgcznqyD7J4esRG6D2tFppHzcBZfDFuiizAJXyq1IicQEtUnNkjoYRz6BcHGHghDV68KKt+ObIS0NumIp0DnF35sCdxZBeij89FBula7l1rxvzf/8qCX/7BGi4NPCqYLK9/D9FYXEgYoi72tniq15ZQwqfTqeUVQGjRlpzJ4Ga786ngIz10J3L7xc4oL+6iY00MLaiLZ3KBa6Otjh5tbVxNftpkRP90CguXVtjsUiF3q7fwi3tvpC9D7R282K7cyr4+uzQbrSPGAxrEq3/OsjwcmnCWHdLLOlbP5CY1W8pfaDGKvpw3hTQ03iPPN0pIFZHG9urIk31p6JNNYcS9S3fBG72MmK9n17IpqM9EfXfWPRDX8O0NHZk1ldfa6g8vy9nHcAihHUbRWScttDAjYq3SpXuc0YOU7KzJBbOsWzOhffmlE6mR7YpPCg03f6KSr9DUtrzqb/VMDef84qg/sdMz3tuNBxkuSp4w72eDIiNne62dpzJE+1TPLVc7Og/jTJ1dyk96tuUO1wsZGDqWeiyPou9CdVz5w9vO+DQgAAAABJRU5ErkJggg==&amp;apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Top-level architecture&quot; title=&quot;Top-level architecture&quot; src=&quot;/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/8ff1e/Top_Level_Architecture.png&quot; srcset=&quot;/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/9ec3c/Top_Level_Architecture.png 200w,
/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/c7805/Top_Level_Architecture.png 400w,
/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/8ff1e/Top_Level_Architecture.png 800w,
/blog/static/42ae04f82d33d92ff6716a3fd6892bd4/f570d/Top_Level_Architecture.png 960w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; loading=&quot;lazy&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edge&lt;/strong&gt;: This layer provides API gateway capabilities and is where our &lt;a href=&quot;https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html&quot;&gt;BFFs&lt;/a&gt; live. The BFFs are published and maintained dedicated APIs that are tailored to specific client needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value Added&lt;/strong&gt;: Services in this layer consume data from other services and process them in some way to build rich experiences for users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foundation&lt;/strong&gt;: This is a low-level service that provides the building blocks around a domain. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s also important to understand the building blocks that come together in Value-Added Services. These are all well-known domain-driven design concepts, which you can read more about in this &lt;a href=&quot;https://martinfowler.com/bliki/DomainDrivenDesign.html&quot;&gt;article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 728px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7e650dd0d433c0434827d005662aff53/a6906/entity_diagram.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.41208791208791%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAAB3ElEQVQoz41Sa2/aQBDk//+USvnQikZJqRJFqWgTKyImgMFgKNjH2eADbJ8f96RruyFOFYmObMnau5kdz27rWOE3Wlj2y3Bsjh1LSHH8P7TgVUrFSWS7wczzo2ivtG7eAK28yLM8Y5x9RJYCkfRusP5pedtDKl8762OpsiGBafWeTGOFV2WxId2qbulDkpvz9aDfiSIipKzPpFKcc/B1ug2mmpXW3yZK5mCM5SCkKkgpwRHU4TTfkdnN1fDqS9B/Ar4Q4o0MTpTk4YHuY3qsrIK8FGJPCwvRVBxTEiKju/p1v5sMgXz6r1bdFpOkO9k+OIftgQpegqaZ4ZDrx0F/gQjypt+/9tsXrtEtI2x2BntRpowJ6j1/20c7JRVjDOQJlf3hYxTaCSHzH7f2XQe/9MCYbJJhEiu0GNjW1DGlKGptAPirs6UktG+vR51L9GzIKpE3shDc3+Klu/A3WL8GVqYN3cts5Wk8ukr73ai0gonqIOazoEghcsaAXBPgo3YBKuUDdv6ZM4yFxNmNuWzfP9guFozxPCuylBfs/HrW6YWJcGyDFeFuPBp9/mS3L6Klo87vtlY4WCPsej6mWcZpEmOUBDhPYrB/hgxAvucsZ9P5eBMGuloU/X6NP8QfOhBb6iia4RsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Building blocks&quot;
        title=&quot;Building blocks&quot;
        src=&quot;/blog/static/7e650dd0d433c0434827d005662aff53/a6906/entity_diagram.png&quot;
        srcset=&quot;/blog/static/7e650dd0d433c0434827d005662aff53/9ec3c/entity_diagram.png 200w,
/blog/static/7e650dd0d433c0434827d005662aff53/c7805/entity_diagram.png 400w,
/blog/static/7e650dd0d433c0434827d005662aff53/a6906/entity_diagram.png 728w&quot;
        sizes=&quot;(max-width: 728px) 100vw, 728px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain&lt;/strong&gt;: A user or business concern that can be used to draw boundaries/scope around service integrations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity&lt;/strong&gt;: An entity is an object that has an independent identifier and a lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value Objects&lt;/strong&gt;: Value objects contain metadata related to a given entity; they’re also tied to the lifecycle of the given entity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggregate&lt;/strong&gt;: An aggregate is a collection of one or more related entities. An aggregate has a root entity called the aggregate root. Aggregates can also contain references to other entities, but not the referenced entity metadata. It’s then up to the consuming services to call other services to synthesize the entity references.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Value-Added Services&lt;/strong&gt; are business services responsible for returning an entity and its associated value objects (in other words, an aggregate) to the caller. It’s important to note that a VAS is &lt;em&gt;not&lt;/em&gt; responsible for synthesizing metadata for any associated entities. This allows for a nice separation of concerns — along with a centralized point where metadata and authorization rules for a given entity can be defined. A VAS can then orchestrate calls to these services to synthesize and authorize aggregates to then return to the BFF.&lt;/p&gt;
&lt;p&gt;Let’s apply these concepts to real-life examples at SoundCloud. An example entity is a track, which has associated value objects like metadata, transcodings, and authorization policies to determine visibility. A track is also connected to an owning user, but since this is another entity, it only contains the user ID as a reference. If a consuming service has a track ID it wants to resolve, it’ll then call the Tracks VAS, which takes care of ensuring that the track is visible, and then it returns the according track aggregate.&lt;/p&gt;
&lt;p&gt;Previously, if an end user wanted to fetch a track, the request would be sent to the BFF. It would then be up to the BFF to determine whether the session user was authorized to view this track and, if so, to synthesize the external track representation to return back to the user. This would involve calls to various Foundation services that are individually responsible for returning both authorization information and track metadata.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e6ec849d92c62433fd487b2d240ef460/981f8/bff-pattern.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.8547418967587%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAAAr0lEQVQoz51QXQuCQBDs//+8BKmzPO/O1C7sQbDue1orSAIfdGHYXWYYZneHv0opfXtECDX6fk/IoHWGe5/DOfnm5tp57ZYMY4wEQWAYhgznQuLEWjzGC7EBk2y1YUoK3jNYy+BdBWc57WJ9wo8uYKQ0VSmpHyCFAi+b7QlTCgi+pnM5/a0gKFijtiX8nSxpYpQwh7426NoO5llv/2EIFe1TuiNuuiRTDmPExC4avgDksCRrgSdQVwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;BFF pattern&quot;
        title=&quot;BFF pattern&quot;
        src=&quot;/blog/static/e6ec849d92c62433fd487b2d240ef460/8ff1e/bff-pattern.png&quot;
        srcset=&quot;/blog/static/e6ec849d92c62433fd487b2d240ef460/9ec3c/bff-pattern.png 200w,
/blog/static/e6ec849d92c62433fd487b2d240ef460/c7805/bff-pattern.png 400w,
/blog/static/e6ec849d92c62433fd487b2d240ef460/8ff1e/bff-pattern.png 800w,
/blog/static/e6ec849d92c62433fd487b2d240ef460/981f8/bff-pattern.png 833w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;However, once the Tracks VAS was introduced, this pattern changed. All the duplicate logic surrounding calls to Foundation services in the BFFs was moved to the Tracks service, which now took care of synthesizing track aggregates for the BFFs, in addition to handling context-specific track visibility and authorization. The BFF was then responsible for mapping those internal track aggregates to external representations for the clients to consume. &lt;/p&gt;
&lt;p&gt;Of course, nuances in how the BFFs behave in fetching tracks remains, but all shared code now lives in a singular codebase. Integrations requiring track aggregates are now as simple as querying endpoints exposed from the tracks VAS, removing the need to reorchestrate calls to Foundation services, and maintaining a guarantee that authorization is properly taken care of.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/981f8/vas-pattern-2021-08-08T09-56-55.865Z.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.45258103241296%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAAAvUlEQVQY052PyQ7CMAxE+/9fBxe6kHRfRNJSSvctgxskxAEkiqVJlLHzbBt4C6WUvpclJ3Gsa4C6NiHlgXRE09jkxZhnn/ISz3L1+reF8Qm4roLOM8klj2v4MJxQ5BaYkyFNLuSJ34Fbd4BhmmxUlQWPZ+hapuFD72EauW66CxiFEe6Vp8Hj4BPcRFk64CxGliR7JxTou5DkQAoXgS9wK1OaiqNtEvR9+M/KDr0ZATxci4hWDsnjeuIt9w34ALSM0pBGE3G1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;VAS architecture&quot;
        title=&quot;VAS architecture&quot;
        src=&quot;/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/8ff1e/vas-pattern-2021-08-08T09-56-55.865Z.png&quot;
        srcset=&quot;/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/9ec3c/vas-pattern-2021-08-08T09-56-55.865Z.png 200w,
/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/c7805/vas-pattern-2021-08-08T09-56-55.865Z.png 400w,
/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/8ff1e/vas-pattern-2021-08-08T09-56-55.865Z.png 800w,
/blog/static/b4cb3d13cf3deb2a73f72037fe059f62/981f8/vas-pattern-2021-08-08T09-56-55.865Z.png 833w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Evolution of VAS at SoundCloud&lt;/h2&gt;
&lt;p&gt;Now that we’ve explained the basis of VAS and how we integrated it into SoundCloud for tracks, we’d like to share how we adapted the same architectural pattern for the case of playlists. We’ll also outline the challenges we encountered during the process of evolving our architecture toward a VAS landscape.&lt;/p&gt;
&lt;p&gt;As we already discussed, in 2019, we started the development of a Tracks service using the concept of a VAS. That was the first implementation of such a concept in the organization, and it helped us validate a model to apply to the rest of our entities. In 2020, we began a major refactoring of our Public API. The codebase of the Public API was divided between the Public API component of the &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;Mothership monolith&lt;/a&gt; and the Public API BFF, which is a facade of the Public API. The refactor involved migrating all endpoints from Mothership and rewriting them in the Public API BFF.&lt;/p&gt;
&lt;p&gt;Rewriting all track-related endpoints (all the endpoints that were returning the representation of tracks) was an easy task, as we already had a Tracks VAS up and running, so we just needed to connect the Public API with the Tracks service.&lt;/p&gt;
&lt;p&gt;However, we also needed to rewrite all the playlist-related endpoints, but we didn’t have such a VAS. So we had to decide whether to duplicate existing authorization and fetching logic from other BFFs and move it to the Public API, or to create a new Playlists VAS to be the central service to handle fetching playlists logic and to make the Public API BFF dependent on it. The latter solution was, for us, the most attractive, as it would require refactoring and cleaning up the rest of the BFFs to also use the Playlists VAS to handle the many different playlists-related endpoints.&lt;/p&gt;
&lt;p&gt;The following graph illustrates the process of migration:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/572b98450e53da9351b389246ecde6c1/d19c0/flow_chart_pre_vas_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAACA0lEQVQoz3VSa5OaQBC8//+vUsmn5FKlnsB5ggLLLi8fqIgPlu30rpY5cxWr2l2Yoad7Zl7A3/VawBgfl4uHNP2G83nKtwH6PrVhDEPLe0nUjBXY7QT2e8H8ElrXLqb1weW+2D89lDh1MYq8hJISZVHhsEv48ZLFDLpOkDRgZkSSN+T5Dwd7B+Yudr2qG6ExVkGBPat60wqep/AerFGolCpmaNszFcU4HqfYbCbYN5LFa3THCse2wKBj0oROpeVyCvu+Iikr6ZRIoJ3VBRPU3XJNBT42aw+TcY7XV4HfxLtPuxfhVPZ98ZXQDAlf2oopbfpYrwOq7KhsQfv2eYw0fkN7EFQoiYzfLZ8JnWVTotkKBKzoexmmk5x9lGx0StihlSzg0b7nPrYW/8I6CZ8VDhxKe0ggRY1M5AjnAqs6JkHoerjdLlwPjVn8B+FXy8DsXjEiPiDld1SVR6ILbSf32PIej2D4bB75d8uPtdF2j0LsGsVpK67PEudT6NbmdOqpMuWkJ2yL76ZqBg7N3GDvZvhHodYFrpcY07cVRiOJ8ahGs1kyKXZJeshJluHXz4SrJTD/sOsl2XPFnpcUkpMj/0xYuimfO0VlintmzxmaZka7J/ZwTtUBVGZ7LLn8JU8FmeXI0pL7KJ4J+35FzKkkYiB6nH2f41awfkz0hujTffG0Nn8AtFGRuBd1n4MAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Migration process&quot;
        title=&quot;Migration process&quot;
        src=&quot;/blog/static/572b98450e53da9351b389246ecde6c1/d19c0/flow_chart_pre_vas_2.png&quot;
        srcset=&quot;/blog/static/572b98450e53da9351b389246ecde6c1/9ec3c/flow_chart_pre_vas_2.png 200w,
/blog/static/572b98450e53da9351b389246ecde6c1/c7805/flow_chart_pre_vas_2.png 400w,
/blog/static/572b98450e53da9351b389246ecde6c1/d19c0/flow_chart_pre_vas_2.png 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the main SoundCloud APIs were calling directly to the Mothership (the original SoundCloud app which is the main interface to our databases) and to Playlist Authorization (a system that authorizes users according to business rules of tracks in a playlist). This summarizes the two main problems of this approach: duplication of logic in the BFFs, and fragile authorization.&lt;/p&gt;
&lt;p&gt;The following graph shows our solution:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/25f1582101e00b17188a3970f9f34f86/e7ad5/flow_chart_post_vas_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.697674418604656%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABiElEQVQoz21S6ZKCMBjz/Z9u1x3XUQuIRTnlsBwCLdm06qw/ZCbTln5N8h0rrW8wJoJSAtMUYllSwhAdzwX6PoXWJWPueH3Lgo/fwouVJeg6H5GUKPII4yiIgWRnzPMWTfNNsh3R8MHENXGiSnkOZkkoGPN/70hJmKOpQwR+Ank6o1V7CigS+QzcMcS6tiLVE2veeQiPFgJde+T9L2OrB6HWOZe9ewgETxjnRKk114Bpb7jPKHTlfo0s9Wkghu/FNHN277VuHoTG5EguEX43MXbbC6rSR9vVdBFAHAKsvyIUmWSoohObliCObwgouifqf4f3IUDbMt1WsnZHqo1EgjzfobxK17DbLWE5Utb6gMMhgycSCsZuX14jir0RThM7PNqGhCQKeKkxDBL3+w9DPJfSPDcurb4TqCqJujo5VKXE0LM55lnDxWRIY5tySsWcQR6d1nQVMki4Gmot8Bivmcg4HwVsMy2Awv17jRUdFlQXmDmD88x0CWNG59IG2b1d7fk1g5/wmsM/rVyy3G+GUXkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Our solution&quot;
        title=&quot;Our solution&quot;
        src=&quot;/blog/static/25f1582101e00b17188a3970f9f34f86/8ff1e/flow_chart_post_vas_2.png&quot;
        srcset=&quot;/blog/static/25f1582101e00b17188a3970f9f34f86/9ec3c/flow_chart_post_vas_2.png 200w,
/blog/static/25f1582101e00b17188a3970f9f34f86/c7805/flow_chart_post_vas_2.png 400w,
/blog/static/25f1582101e00b17188a3970f9f34f86/8ff1e/flow_chart_post_vas_2.png 800w,
/blog/static/25f1582101e00b17188a3970f9f34f86/e7ad5/flow_chart_post_vas_2.png 1075w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;With this architecture, all the logic is centralized in the Playlist VAS.&lt;/p&gt;
&lt;p&gt;This project was divided into the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extracting the logic&lt;/strong&gt; from the BFFs and creating a new Playlist VAS. We investigated and documented all the different implementations of playlists logic in our main services to come up with the base logic for playlist-fetching logic. Once we documented all the information needed and collected feedback from the rest of the organization, we implemented the service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic tests&lt;/strong&gt; to ensure that centralized logic matches refactored services. We added unit tests to ensure that we covered all the possible scenarios of fetching and authorization logic for playlists. We also added integration tests to ensure that response formats with clients weren’t broken, and that integrations with dependent services were working properly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrating the BFFs&lt;/strong&gt; to use the Playlists VAS. This might seem like an easy step, but it actually took us longer than implementing the service itself. We had to migrate more than 50 playlist-related endpoints from the main BFFs. We did this endpoint by endpoint, and by carefully comparing responses from both implementations, as we didn’t want to break anything from the clients of the BFFs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What We Learned&lt;/h3&gt;
&lt;p&gt;VAS comes with many benefits, and it was a logical solution for the problems seen with playlist resolution at SoundCloud. The first and perhaps most obvious mitigation was that a Playlists VAS reduces &lt;strong&gt;duplicated playlists code&lt;/strong&gt; in the BFFs. Each BFF contained the same logic for orchestrating various calls to dependent services for resolving playlist metadata, but with a VAS, we could congregate this logic in one service. Having a centralized service containing business logic means refactoring and optimizations are easier and faster. This also helps simplify cross-platform feature development, as value objects — in this case, playlists — are only exposed in a central place. So it becomes trivial to the rest of the clients to access playlists instead of reimplementing that functionality in each BFF.&lt;/p&gt;
&lt;p&gt;One of the most critical parts of fetching playlists was authorization: Leaking private or unreleased tracks to the general public is a worst-case scenario we want to avoid at all costs. &lt;strong&gt;Authorization logic&lt;/strong&gt; was spread out over multiple services, increasing the risk of inconsistencies (and therefore vulnerabilities) sneaking in over time. The Playlists VAS means having one central codebase for authorization logic, which can be audited easily.&lt;/p&gt;
&lt;p&gt;It’s worth noting here that &lt;strong&gt;VAS-to-VAS communication&lt;/strong&gt; can occur in some circumstances. Let’s say, for example, that a request is sent over to the Playlists VAS to add a track to a given playlist. Before we continue with this write command, we must first check that the track is visible to the session user. Authorization logic for entities is centralized in the VAS, so we’d have to make a request to the Tracks service to determine whether the requested track is authorized to be added. In this case, we ensured that the previous logic for tracks that has depended on playlists was decoupled, meaning we can have VAS-to-VAS communication without circular dependencies.&lt;/p&gt;
&lt;h2&gt;Considerations&lt;/h2&gt;
&lt;p&gt;The migration of entities to their own services has as many benefits, as outlined above. However, implementing this solution has downsides as well, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding a new service to our systems. Creating a new VAS comes with &lt;strong&gt;maintenance costs&lt;/strong&gt; and infrastructure costs. We need to provision new nodes, deploy the monitoring service, maintain a new codebase, add a new service to our &lt;a href=&quot;https://developers.soundcloud.com/blog/building-a-healthy-on-call-culture&quot;&gt;on-call&lt;/a&gt; rotation, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network latencies&lt;/strong&gt;. In previous implementations, when BFFs wanted to fetch playlist metadata, they just needed to do a round trip to Mothership and Playlist Authorization. With this new implementation in place, they need to do an extra roundtrip to the Playlists VAS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to considering downsides, we also considered the alternate solution of having the Playlists VAS as an external library that would be linked during the app build process. This is an approach we used in the past — for instance, with our JVMKit library (you can read more about that &lt;a href=&quot;https://developers.soundcloud.com/blog/did-i-break-you&quot;&gt;here&lt;/a&gt;). However, this solution comes with extra complexity and risk, in that different versions of the library could be used in each service, potentially causing a state with &lt;a href=&quot;https://en.wikipedia.org/wiki/Dependency_hell&quot;&gt;diamond dependencies&lt;/a&gt;. In our case, the benefits of introducing a VAS far outweigh the tradeoffs.&lt;/p&gt;
&lt;p&gt;It’s also worth mentioning that the approach of using a VAS means we can expose new integrations using Twinagle, which is an in-house implementation of the Twirp protocol for Finagle. You can read more about the motivation and benefits behind Twinagle &lt;a href=&quot;https://developers.soundcloud.com/blog/announcing-twinagle&quot;&gt;in our related blog post&lt;/a&gt;, but one bonus is the added ease of integration and maintainability. Twinagle uses an &lt;a href=&quot;https://en.wikipedia.org/wiki/Interface_description_language&quot;&gt;interface description language&lt;/a&gt; (IDL) to generate server stubs and clients, meaning that integrating services have a clearly defined API to work with. Making changes to the VAS therefore becomes safer, given that the API contract stays consistent and will clearly expose any (backward compatible) changes made, with all implementation details being encapsulated behind the API.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The adoption of Value-Added Services at SoundCloud has been well received. It allows for a cleaner architecture and a better separation of concerns. We’re definitely going to move more entities to their own VAS and extend existing ones to have more operations, all of which allows us to have a clear roadmap for the architecture of our microservices.&lt;/p&gt;
&lt;p&gt;In the next blog post — and the last one in this series on the evolution of Service Architecture at SoundCloud — we’ll talk about the next iteration of Value-Added Services and how they evolved into Domain Gateways.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Service Architecture at SoundCloud — Part 1: Backends for Frontends]]></title><description><![CDATA[This article is part of a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over the past…]]></description><link>https://developers.soundcloud.com/blog/service-architecture-1</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/service-architecture-1</guid><pubDate>Thu, 29 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is part of a series of posts aiming to cast some light onto how service architecture has evolved at SoundCloud over the past few years, and how we’re attempting to solve some of the most difficult challenges we encountered along the way.&lt;/p&gt;
&lt;h2&gt;Backends for Frontends&lt;/h2&gt;
&lt;p&gt;SoundCloud pioneered the &lt;a href=&quot;https://www.thoughtworks.com/insights/blog/bff-soundcloud&quot;&gt;Backends for Frontends&lt;/a&gt; (BFF) architectural pattern back in 2013 while moving away from an exhausted model of an eat-your-own-dog-food approach. The exhausted model involved using a single API (the Public API) both for official applications and third-party integrations. But the need to scale operationally and organizationally led to a migration from a monolith-based architecture to a microservices architecture. The proliferation of new microservices, paired with the introduction of a private/internal API for the monolith (effectively turning the monolith into yet another microservice), opened the door for new and innovative dedicated APIs to power our frontends. Thus, BFF was born, and it was really exciting, as it enabled autonomy for teams — along with many other advantages that will be discussed shortly.&lt;/p&gt;
&lt;p&gt;In a nutshell, BFF is an architectural pattern that involves creating multiple, dedicated API gateways for each device or interface type, with the goal of optimizing each API for its particular use case.&lt;/p&gt;
&lt;p&gt;Plenty has been &lt;a href=&quot;https://samnewman.io/patterns/architectural/bff/&quot;&gt;written&lt;/a&gt; about BFF, the theoretical advantages that it provides, and how it compares to other approaches and technologies like &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern&quot;&gt;centralized API gateways&lt;/a&gt; and &lt;a href=&quot;https://philcalcado.com/2019/07/12/some_thoughts_graphql_bff.html&quot;&gt;GraphQL&lt;/a&gt;. However, little can be found on real-life experiences, risks, and tradeoffs encountered along the way, so we decided to write this series to shed some light on these topics.&lt;/p&gt;
&lt;h2&gt;BFFs at SoundCloud in 2021&lt;/h2&gt;
&lt;p&gt;SoundCloud operates dozens of BFFs, each powering a dedicated API. BFFs provide API gateway responsibilities, including rate limiting, authentication, header sanitization, and cache control. All external traffic entering our data centers is processed by one of our BFFs. Combined, they handle hundreds of millions of requests per hour.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/93d127fab948bea0b04e8fba97d22f5a/f3d76/bff-overview.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.565874730021598%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABD0lEQVQY022QXU7CQBSFuwnZAGthJeyAJbAAdQW88uSjibybGINEogETkRhaG6UlnZJhpjOdaT9rffD3JufhnnN/Tk4QhiGz2YwoioCar6qp6u89v/T/KxgMBnS7XUajUUuEb5J1rEjzEmsUrkgp1RZfanxVcxB7RLwlf00ojcU5R5Q8s3pZ8Bg/EPT7fY46HU5PjlElTKZJgx3LjcLqjHB5ztPtGUbG+Obhbh2ymFyyub5HNseNt9ytrpjOL5hvbgiGwyG9Xo/xeNw63OWGTDp0UZCLrHEhMSrHO9vqrnFV5BIrNVVVtdgrgVAZyhwIvPdorUnTtIUrPxetNQghfibYZPrBbZMEkYtmxv7J8B0gjXCZD3Yq+gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;BFF Overview&quot;
        title=&quot;BFF Overview&quot;
        src=&quot;/blog/static/93d127fab948bea0b04e8fba97d22f5a/8ff1e/bff-overview.png&quot;
        srcset=&quot;/blog/static/93d127fab948bea0b04e8fba97d22f5a/9ec3c/bff-overview.png 200w,
/blog/static/93d127fab948bea0b04e8fba97d22f5a/c7805/bff-overview.png 400w,
/blog/static/93d127fab948bea0b04e8fba97d22f5a/8ff1e/bff-overview.png 800w,
/blog/static/93d127fab948bea0b04e8fba97d22f5a/6ff5e/bff-overview.png 1200w,
/blog/static/93d127fab948bea0b04e8fba97d22f5a/2f950/bff-overview.png 1600w,
/blog/static/93d127fab948bea0b04e8fba97d22f5a/f3d76/bff-overview.png 1852w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;BFFs make use of an internal library providing edge capabilities, as well as extension points for custom behavior. New library releases are semi-automatically rolled out to all BFFs within hours.&lt;/p&gt;
&lt;p&gt;Some examples of BFF include our Mobile API (powering Android and iOS clients), our Web API (powering our web frontends and widget), and our Public and Partner APIs.&lt;/p&gt;
&lt;p&gt;BFFs are maintained using an &lt;a href=&quot;https://en.wikipedia.org/wiki/Inner_source&quot;&gt;inner source model&lt;/a&gt;, in which individual teams contribute changes, and a Core team reviews and approves changes based on principles discussed in a collective. The Collective, organized by a Platform Lead, meets regularly to discuss issues and share knowledge.&lt;/p&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;p&gt;One of the key advantages BFFs provide is &lt;strong&gt;autonomy&lt;/strong&gt;. By having separate APIs per client type, we can optimize our APIs for whatever is convenient for each client type without the need for synchronization points and difficult compromises. For example, our mobile clients tend to prefer larger responses with a high number of embedded entities as a way to minimize the number of requests and to leverage internal caches, while our web frontend prefers finer-grained responses and dynamic augmentation of representations.&lt;/p&gt;
&lt;p&gt;Another advantage of BFFs is &lt;strong&gt;resilience&lt;/strong&gt;. A bad deploy might bring down an entire BFF in an availability zone, but it shouldn’t bring down the entire platform. This is in addition to many other &lt;a href=&quot;https://developers.soundcloud.com/blog/hands-off-deployment-with-canary&quot;&gt;resilience mechanisms&lt;/a&gt; in place.&lt;/p&gt;
&lt;p&gt;Additionally, high autonomy and lower risk lead to a &lt;strong&gt;high pace of development&lt;/strong&gt;. Our main BFFs are deployed multiple times per day and receive contributions from all over the engineering organization.&lt;/p&gt;
&lt;p&gt;Finally, BFFs enable the implementation of sometimes ugly but necessary workarounds and mitigation strategies (a client bug fix affecting specific versions) without affecting the overall &lt;strong&gt;complexity&lt;/strong&gt; of the platform.&lt;/p&gt;
&lt;h2&gt;The Bad&lt;/h2&gt;
&lt;p&gt;BFFs provide many advantages, but they can also be a source of problems if they’re not part of a broader service architecture that’s able to keep complexity and duplication at bay.&lt;/p&gt;
&lt;p&gt;In service architectures with very small microservices that do little more than CRUD, and with no intermediate layers between these microservices and BFFs, &lt;strong&gt;feature integration&lt;/strong&gt; (with all the associated business logic) tends to end up in the BFFs themselves. Although this problem also exists with other models, like centralized API gateways, it’s particularly problematic in architectures with multiple BFFs, since this logic can end up duplicated multiple times, with diverging and inconsistent implementations that drift apart over time.&lt;/p&gt;
&lt;p&gt;This issue becomes critical for &lt;strong&gt;authorization&lt;/strong&gt; rules that can only be applied at integration time (for example, because the necessary pieces of information required to make a decision are spread across multiple microservices). This model obviously doesn’t scale with the addition of more and more BFFs.&lt;/p&gt;
&lt;p&gt;At SoundCloud, this problem manifested as the Track and Playlist core entities grew and were decomposed into multiple microservices serving parts of the final representations assembled in each of the BFFs. Suddenly, the authorization logic needed to be moved to the point of integration, which, at the time, was the BFF. This was not too concerning at first, with just a handful of BFFs and very simple authorization logic, but as the logic grew in complexity and the number of BFFs increased, it caused many problems. This will be the focus of the next posts in this series.&lt;/p&gt;
&lt;h2&gt;The Ugly&lt;/h2&gt;
&lt;p&gt;Effective operation of multiple BFFs requires a set of platform-wide capabilities that, in their absence, might lead to an &lt;strong&gt;unnecessary proliferation&lt;/strong&gt; of BFFs. For example, application entitlements (in addition to user entitlements) are needed to restrict access to certain applications and third-party integrations to specific endpoints. In their absence, it’s tempting to spawn an entire new BFF for narrow use cases with specific access control requirements. There needs to be a strategy to decide how many BFFs are too many and when to create one versus when to reuse an existing one. Even though BFFs are designed to provide autonomy, there’s a tradeoff between autonomy and added maintenance and operational overhead that needs to be carefully managed.&lt;/p&gt;
&lt;p&gt;We’ve also seen a tendency to &lt;strong&gt;push complex client-side logic to the BFF&lt;/strong&gt;. This stems from the initial idea that a BFF is an extension of the client, and that therefore it should be treated as “the backend side of the client.” This has worked well in some cases, but in others it has led to problems. For example, pushing pagination to the server (recursively paginating to return an entire collection in one single request) — even though faster for basic use cases — can lead to timeouts, restrictive limits for collection sizes, and fan-out storms that may bring the entire system down.&lt;/p&gt;
&lt;p&gt;Although BFFs enable some form of autonomy, it’s also important to recognize that BFFs are at the &lt;strong&gt;intersection&lt;/strong&gt; of two worlds, and the idea of full autonomy for client developers is just an illusion. Extensive collaboration between frontend and backend engineers is required to ensure optimal API designs that are convenient for client developers to use, in addition to being optimized for distributed environments and their intricacies.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Backends for Frontends is an architectural pattern that can lead to a high degree of autonomy and pace of development. Like all engineering decisions, it comes with a set of tradeoffs that must be well understood and managed. In particular, a good service architecture is critical for scalability, security, and maintainability, and there are limits to how much autonomy can be achieved.&lt;/p&gt;
&lt;p&gt;In future posts, we’ll dive into some of the unintended consequences of using the BFF pattern and discuss how our service architecture has evolved to address them. Stay tuned!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How We Share Knowledge as a Web Collective]]></title><description><![CDATA[There’s no single platform team that consists of only web engineers at SoundCloud, even though we consider ourselves to be part of the “Web…]]></description><link>https://developers.soundcloud.com/blog/how-we-share-knowledge-as-a-web-collective</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-we-share-knowledge-as-a-web-collective</guid><pubDate>Wed, 14 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3db6d79de3a79b4f76a1abbb4525a622/2a1ba/knowledge-sharing-web-collective.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.44951140065146%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAIBAwQF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAee+R6iCsH//xAAbEAEAAgIDAAAAAAAAAAAAAAABAAIDERITIf/aAAgBAQABBQKteUcc8hZDttF2/wD/xAAWEQEBAQAAAAAAAAAAAAAAAAAAERL/2gAIAQMBAT8Byj//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwGq/8QAGBAAAgMAAAAAAAAAAAAAAAAAEBEAASH/2gAIAQEABj8Cwqg5/8QAGxAAAgIDAQAAAAAAAAAAAAAAAAERITFRYUH/2gAIAQEAAT8hvaDEsHVcJaQpmht90SWx/9oADAMBAAIAAwAAABC37//EABURAQEAAAAAAAAAAAAAAAAAABBx/9oACAEDAQE/EKP/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAcEAEBAAICAwAAAAAAAAAAAAABEQAhMUFhcaH/2gAIAQEAAT8QLUUUs046a1jbtXa5MHS3kX3iFTDQDRCfTnGWAXYdZ//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Group of people sharing knowledge&quot;
        title=&quot;Group of people sharing knowledge&quot;
        src=&quot;/blog/static/3db6d79de3a79b4f76a1abbb4525a622/a296c/knowledge-sharing-web-collective.jpg&quot;
        srcset=&quot;/blog/static/3db6d79de3a79b4f76a1abbb4525a622/f544b/knowledge-sharing-web-collective.jpg 200w,
/blog/static/3db6d79de3a79b4f76a1abbb4525a622/41689/knowledge-sharing-web-collective.jpg 400w,
/blog/static/3db6d79de3a79b4f76a1abbb4525a622/a296c/knowledge-sharing-web-collective.jpg 800w,
/blog/static/3db6d79de3a79b4f76a1abbb4525a622/c35de/knowledge-sharing-web-collective.jpg 1200w,
/blog/static/3db6d79de3a79b4f76a1abbb4525a622/8179c/knowledge-sharing-web-collective.jpg 1600w,
/blog/static/3db6d79de3a79b4f76a1abbb4525a622/2a1ba/knowledge-sharing-web-collective.jpg 2456w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There’s no single platform team that consists of only web engineers at SoundCloud, even though we consider ourselves to be part of the “Web Collective.” Instead, all web engineers are split into smaller, cross-functional teams across the organization. This structure definitely has its advantages, but it’s also left engineers in a situation where platform-specific experience and knowledge about platform-specific best practices are more difficult to access than they would be if we were all on the same team. New employees in particular can have difficulties breaking into unfamiliar tech stacks and SoundCloud internal frameworks.&lt;/p&gt;
&lt;p&gt;We did and do still have forums to discuss technical challenges at SoundCloud to share knowledge across multiple teams: Technology Open House is a good example. This is where engineers from the entire organization come and learn from each other every week. While we enjoy these meetings, we as the Web Collective wanted to try out a slightly different format with topics dedicated to our platform. More specifically, we wanted to reduce the pressure to present as much as possible and instead give space to unprepared thoughts and raw ideas. We encourage all questions, and the environment fosters a culture of being able to ask questions that might feel silly or trivial to the people asking them.&lt;/p&gt;
&lt;p&gt;We tried it out, it immediately felt natural to us, and it turned out to be extremely helpful, so we wanted to share a bit about the structure and format and our observations.&lt;/p&gt;
&lt;h2&gt;Meeting Structure&lt;/h2&gt;
&lt;p&gt;The Web Collective Knowledge Sharing meeting is an hour long, and it takes place every Friday at 16:00. This is one hour before our company-wide demos or all-hands sessions that we have on Fridays as well. Fridays are usually also days where developers take their &lt;a href=&quot;https://developers.soundcloud.com/blog/a-happy-new-employee&quot;&gt;self-allocated time (SAT)&lt;/a&gt; to work on their own projects, and since most of our web developers are based in Europe, the majority of us are winding down for the weekend at that point. This allows for a pretty relaxed atmosphere.&lt;/p&gt;
&lt;p&gt;Even though this session primarily focuses on web topics, we occasionally also have non-web developers join to share more about web projects they’ve been involved in. This is because it’s important to us that the guest list stays as open as possible and that everyone feels welcome, no matter which platform they primarily work on.&lt;/p&gt;
&lt;p&gt;People who are presenting don’t have to follow a specific format. We do this to keep a low as possible barrier of participation. Some people like to have slides prepared, while others like to use only some prepared browser tabs, or don’t prepare at all and just run through a piece of code they want to share. We found that by allowing low-key presentations, more people are presenting. There are also no limitations on time other than the one hour allocated for the meeting. (Of course, people shouldn’t and don’t use the full available hour when they know that there are other topics on the agenda.)&lt;/p&gt;
&lt;p&gt;We often find ourselves using most of the time for the meeting, even for days with a light agenda. The Web Collective is pretty good at turning a single question into an hour-long discussion, albeit one that’s both enjoyable and valuable.&lt;/p&gt;
&lt;p&gt;Speaking of agenda, there’s no fixed agenda for the sessions, and topics are often proposed in the meeting itself. We do keep a document with proposed sessions, and it’s good practice for presenters to add their topics to the agenda, but it’s also totally OK to jump the queue in case someone has an urgent need to (e.g. they’ll go on vacation afterward or the topic has a time constraint). These lax rules around the agenda also add to the casual environment of the meeting.&lt;/p&gt;
&lt;h2&gt;Things We Do in Knowledge Sharing&lt;/h2&gt;
&lt;p&gt;The flexibility of the meeting has allowed us to morph it into whatever we needed it to be.&lt;/p&gt;
&lt;p&gt;One thing we regularly do is review PRs together. “I worked on this pull request the other day, let me show it to you…” or “I saw an interesting pull request, can somebody walk us through that?” is something we hear a lot during Knowledge Sharing. We find this really helpful for more complex PRs, because sometimes the collective spots details that other reviewers missed.&lt;/p&gt;
&lt;p&gt;Every so often, Knowledge Sharing also means introducing the collective to new tools or codebases: Sessions that start with “We created a new app and integrated it into soundcloud.com, here’s how it works…” or “We have a new shared component library. It’s in its early days but here’s where we are right now…” are helpful for everyone to stay up to date on what’s new.&lt;/p&gt;
&lt;p&gt;We also had times where we talked about processes and how we could improve them. A few months ago, for example, we were getting bombarded by automated alerts on Slack, and an engineer kicked off the session with “I feel like alerts are getting too noisy and out of control. Should we appoint a point person to deal with them?” Another conversation about how we do releases started with “Here’s an open source tool that could simplify our release processes. What do you think?”&lt;/p&gt;
&lt;p&gt;Other times, we use Knowledge Sharing for technical deep dives. For example, an engineer who worked on our webpack config began a conversation with “I had to dive into how code splitting works for soundcloud.com and I can now explain it!” Other times, an engineer who worked on our famous waveform started with “We are making changes to the waveform and I found that it’s really well architected. Here are interesting patterns that we use there…” And another one who was profiling React went with “I measured the performance overhead of rendering many React roots and here’s what I found…”&lt;/p&gt;
&lt;p&gt;Questions and open rounds are frequently on the table when the agenda is empty. In these cases, Knowledge Sharing is used as an opportunity to seek out expertise and feedback from others. For example, we recently had meetings that started with “It looks like we’re always running into issues with feature flags. What can we do to fix them?” and “Can anybody explain how tracking works? How exactly would I implement firing a click event?”&lt;/p&gt;
&lt;h2&gt;Observations&lt;/h2&gt;
&lt;p&gt;We’ve observed that developers have adapted a sharing mindset and are actively sharing their work and asking peers to share in this new forum. We’ve talked about a vast variety of topics, which results in an increase of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_factor&quot;&gt;bus factor&lt;/a&gt; of the team.&lt;/p&gt;
&lt;p&gt;With our current remote work setup, this meeting also helps make the platform team feel more connected. There are few opportunities to connect to platform peers in such a laid-back environment, and even before we started to work remotely, this meeting was one that web peers always looked forward to, making it even more important now.&lt;/p&gt;
&lt;p&gt;Another thing we noticed is that the barrier to asking “basic” questions has gone down; people aren’t afraid to ask questions they might otherwise be scared to ask in other scenarios. And with engineers from all the different engineering levels present at the meeting, it has become a valuable forum for getting answers to these questions.&lt;/p&gt;
&lt;h2&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;There are some points that we want to improve upon, though we haven’t yet found the best solutions to these issues yet.&lt;/p&gt;
&lt;p&gt;For example, sometimes it’s unclear to peers if they should have a discussion on Slack or whether they should propose a topic for the next Knowledge Sharing session. To date, we haven’t found a great guideline for this. However, when someone asks if a discussion should happen in our Knowledge Sharing meeting, it’s a great indication that it should be discussed there as well, even if it’s just in the format of a summary of the Slack discussion.&lt;/p&gt;
&lt;p&gt;And this is a great segue to another issue: We haven’t yet found a good way to document the outcomes of these sessions. Semi-randomly, colleagues add their notes to the agenda document, but a lot has already been “lost.” We should definitely take on a better documentation cadence: Maybe declaring a dedicated note-taker for each meeting or encouraging presenters to sum up their findings in the agenda document afterward would be a good start. However, we don’t know if this would be detrimental to the casual environment of the meeting. But we’re sure we’ll find a good balance there.&lt;/p&gt;
&lt;p&gt;Nevertheless, we’re very happy with the current setup of the meeting, and we plan to keep our weekly cadence to share knowledge.&lt;/p&gt;
&lt;p&gt;If you have a similar setup at your company, we’d love to hear about it to understand what has worked best for you.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@papaioannou_kostas&quot; target=&quot;_blank&quot;&gt;Papaioannou Kostas&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com&quot; target=&quot;_blank&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Upcoming API Security Updates — Action Required]]></title><description><![CDATA[As part of our continuous effort toward making improvements to our API with the hope that we can relaunch API access to all developers, we…]]></description><link>https://developers.soundcloud.com/blog/security-updates-api</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/security-updates-api</guid><pubDate>Thu, 01 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As part of our continuous effort toward making improvements to our API with the hope that we can relaunch API access to all developers, we’re making some critical security improvements. Please note that these are important and time sensitive and may cause disruptions in your app if you don’t make the necessary security upgrades.&lt;/p&gt;
&lt;h3&gt;TLS Enforcement — HTTPS Everywhere&lt;/h3&gt;
&lt;p&gt;Going forward, all traffic will be served via TLS. If your application doesn’t support TLS or cannot follow &lt;code class=&quot;language-text&quot;&gt;HTTP 301&lt;/code&gt; redirects automatically, you should update it. This is effective immediately.&lt;/p&gt;
&lt;h3&gt;Move OAuth Tokens from URL to Header&lt;/h3&gt;
&lt;p&gt;While we currently support clients using &lt;code class=&quot;language-text&quot;&gt;oauth_token&lt;/code&gt; as a query parameter per the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6750&quot;&gt;OAuth 2.0 RFC 6750&lt;/a&gt;, this poses a few security challenges. We’re stopping support for this to encourage users to provide the authentication header instead, and it’ll be in effect soon.&lt;/p&gt;
&lt;p&gt;Here’s an example of sending an access token in a request header:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;curl --request GET \
--url &amp;#39;https://api.soundcloud.com/me/tracks?limit=1&amp;#39; \
--header &amp;#39;Authorization: OAuth ACCESS_TOKEN&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Deprecated Password Grant&lt;/h3&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;password&lt;/code&gt; grant type will be deprecated. We recommend using &lt;code class=&quot;language-text&quot;&gt;authorization_code&lt;/code&gt; for client-side integrations and &lt;code class=&quot;language-text&quot;&gt;client_credentials&lt;/code&gt; for server-side integrations.&lt;/p&gt;
&lt;h3&gt;Changes in Authorization Code Flow&lt;/h3&gt;
&lt;p&gt;For an &lt;code class=&quot;language-text&quot;&gt;authorization_code&lt;/code&gt; grant type, we’ll only support one response type: &lt;code class=&quot;language-text&quot;&gt;code&lt;/code&gt;. &lt;code class=&quot;language-text&quot;&gt;Token&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;code_and_token&lt;/code&gt; are deprecated and will be considered invalid.&lt;/p&gt;
&lt;h3&gt;Use Client Credentials Grant for Server-Side Integrations&lt;/h3&gt;
&lt;p&gt;Currently, to access the public resources of the platform, server-side integrations with our API only require a &lt;code class=&quot;language-text&quot;&gt;client_id&lt;/code&gt; in the URL’s query parameter. We’ll be strengthening our authorization here by making all public resources on the API only accessible to apps that have been authorized with the &lt;code class=&quot;language-text&quot;&gt;client_credentials&lt;/code&gt; grant. This will enable the app to capture both the &lt;code class=&quot;language-text&quot;&gt;access_token&lt;/code&gt; and the &lt;code class=&quot;language-text&quot;&gt;refresh_token&lt;/code&gt; to then fetch the resources from the API. Please note that the use of &lt;code class=&quot;language-text&quot;&gt;client_id&lt;/code&gt; will be deprecated and deleted soon (from July 2021). Developers should provide the &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt; header for all their requests to the SoundCloud API going forward.&lt;/p&gt;
&lt;p&gt;Here’s an example of getting an access token via the &lt;code class=&quot;language-text&quot;&gt;client_credentials&lt;/code&gt; grant type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;curl --request POST \
--url https://api.soundcloud.com/oauth2/token \
--header &amp;#39;Content-Type: application/x-www-form-urlencoded&amp;#39; \
--data client_id=CLIENT_ID \
--data client_secret=CLIENT_SECRET \
--data grant_type=client_credentials&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Expiring Tokens Are Issued by Default&lt;/h3&gt;
&lt;p&gt;All access tokens issued by our servers will be expiring by default with the TTL set to 6 hours. If you’re currently using non-expiring tokens, you should make the necessary updates in your app to handle the &lt;code class=&quot;language-text&quot;&gt;refresh_token&lt;/code&gt; grant.&lt;/p&gt;
&lt;p&gt;Here’s an example of getting an access token via the &lt;code class=&quot;language-text&quot;&gt;refresh_token&lt;/code&gt; grant type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;curl --request POST \
--url https://api.soundcloud.com/oauth2/token \
--header &amp;#39;Content-Type: application/x-www-form-urlencoded&amp;#39; \
--data client_id=CLIENT_ID \
--data client_secret=CLIENT_SECRET \
--data grant_type=refresh_token \
--data refresh_token=REFRESH_TOKEN&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For future updates, please follow us on &lt;a href=&quot;https://twitter.com/soundclouddev&quot;&gt;Twitter&lt;/a&gt; and on our &lt;a href=&quot;https://developers.soundcloud.com/blog/&quot;&gt;blog&lt;/a&gt;. &lt;/br&gt;
For any bugs or feature requests, please visit our &lt;a href=&quot;https://github.com/soundcloud/api&quot;&gt;GitHub Issue Tracker&lt;/a&gt;.&lt;/br&gt;
You can also follow our &lt;a href=&quot;https://github.com/soundcloud/api/releases&quot;&gt;API Release Notes&lt;/a&gt; page to stay up to date with minor changes.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Did I Break You? Reverse Dependency Verification]]></title><description><![CDATA[SoundCloud was founded 13 years ago, and throughout its history, the company and much of its tech stack has changed. We started with a…]]></description><link>https://developers.soundcloud.com/blog/did-i-break-you</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/did-i-break-you</guid><pubDate>Tue, 25 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e535ede91f1438572e8c66a996936ab5/fb329/guillaume-techer-XiBzEObDp5o-unsplash.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.64583333333334%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAA//aAAwDAQACEAMQAAABpRpUrznYD//EABsQAAICAwEAAAAAAAAAAAAAAAEDAAIREhMh/9oACAEBAAEFAq1ti7AJ6Y5hISvpNRP/xAAYEQADAQEAAAAAAAAAAAAAAAAAAQITIf/aAAgBAwEBPwFQp7Rqf//EABgRAAIDAAAAAAAAAAAAAAAAAAABERIx/9oACAECAQE/AW5wqf/EAB4QAAEEAQUAAAAAAAAAAAAAAAABAhExURIhQWGR/9oACAEBAAY/AoWk5Ib6bIdYNTqwUf/EABsQAAIDAAMAAAAAAAAAAAAAAAABETFBIVFh/9oACAEBAAE/IZpnsFGI3kQvZ1Fo4sQUP//aAAwDAQACAAMAAAAQxw//xAAWEQEBAQAAAAAAAAAAAAAAAAABAFH/2gAIAQMBAT8QIpWX/8QAFhEAAwAAAAAAAAAAAAAAAAAAAAER/9oACAECAQE/EGtBJ//EABwQAQACAgMBAAAAAAAAAAAAAAEAESFBMVFhcf/aAAgBAQABPxAYQ3WK6if7Fy/Icl7Hc0tY9IN7FAG32AqADAE//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Photo by Guillaume TECHER on Unsplash&quot;
        title=&quot;Photo by Guillaume TECHER on Unsplash&quot;
        src=&quot;/blog/static/e535ede91f1438572e8c66a996936ab5/a296c/guillaume-techer-XiBzEObDp5o-unsplash.jpg&quot;
        srcset=&quot;/blog/static/e535ede91f1438572e8c66a996936ab5/f544b/guillaume-techer-XiBzEObDp5o-unsplash.jpg 200w,
/blog/static/e535ede91f1438572e8c66a996936ab5/41689/guillaume-techer-XiBzEObDp5o-unsplash.jpg 400w,
/blog/static/e535ede91f1438572e8c66a996936ab5/a296c/guillaume-techer-XiBzEObDp5o-unsplash.jpg 800w,
/blog/static/e535ede91f1438572e8c66a996936ab5/c35de/guillaume-techer-XiBzEObDp5o-unsplash.jpg 1200w,
/blog/static/e535ede91f1438572e8c66a996936ab5/8179c/guillaume-techer-XiBzEObDp5o-unsplash.jpg 1600w,
/blog/static/e535ede91f1438572e8c66a996936ab5/fb329/guillaume-techer-XiBzEObDp5o-unsplash.jpg 1920w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;SoundCloud was founded 13 years ago, and throughout its history, the company and much of its tech stack has changed. We started with a &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;monolithic&lt;/a&gt; Ruby on Rails app, and since then, have worked to &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-2-breaking-the-monolith&quot;&gt;extract, isolate, and reuse&lt;/a&gt; logic in many subsystems, and later on in separate microservices. We &lt;a href=&quot;https://developers.soundcloud.com/blog/evolution-of-soundclouds-architecture&quot;&gt;introduced&lt;/a&gt; different storage mechanisms and data analysis apps, and our frontend has also expanded from a single website to mobile apps, integrations with third parties, an Xbox app, and more recently, a full-fledged PWA to run on Chromebooks.&lt;/p&gt;
&lt;h2&gt;Golden Paths&lt;/h2&gt;
&lt;p&gt;Internally, we’re committed to giving teams flexibility to pick whatever tools they deem best to solve the technological challenges of everyday product development. While this means a lot of autonomy, it also gradually led to a fragmented environment of languages, frameworks, processes, and documents, making cross-team collaboration and onboardability harder. We have, thus, moved into a model of providing sensible recommendations and supporting an opinionated tech stack.&lt;/p&gt;
&lt;p&gt;In that sense, &lt;a href=&quot;https://builtin.com/software-engineering-perspectives/platform-engineer-twilio-ask-your-developer-excerpt&quot;&gt;complexity became opt-in&lt;/a&gt;: In general, libraries, tooling, and the engineering experience should be as simple as possible, with no configuration, but still provide the ability to support more complex use cases, depending on each problem-requirement set.&lt;/p&gt;
&lt;p&gt;With time, we identified some similarities in the implementations of our solutions and naturally repeated choices. Engineers experienced with a given programming language were more likely to pick the same for a following project, while in other cases, the ease of integration with, say, a specific database engine pushed a new integrator to decide for it again. We &lt;a href=&quot;https://github.com/soundcloud/twinagle&quot;&gt;built more tooling&lt;/a&gt; to help develop these existing apps, we wrote more documentation to cover the “gotchas” of a framework, &lt;a href=&quot;https://www.thoughtworks.com/insights/blog/bff-soundcloud&quot;&gt;standards arose&lt;/a&gt; to organize the system architecture, etc.&lt;/p&gt;
&lt;p&gt;This cycle ended up consolidating some ideas as de facto &lt;a href=&quot;https://engineering.atspotify.com/2020/08/17/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/&quot;&gt;Golden Paths&lt;/a&gt; for our different disciplines: web development, data science, infrastructure, etc. Engineers are still free to choose their tooling, but the positive reinforcement cycle was already set: Why spend time &lt;em&gt;reinventing the wheel&lt;/em&gt; when lots has already been invested in building mature solutions? Drifts from the Golden Paths exist, but they’ve become increasingly rare.&lt;/p&gt;
&lt;h2&gt;JVMKit&lt;/h2&gt;
&lt;p&gt;We &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-3-microservices-in-scala-and-finagle&quot;&gt;settled&lt;/a&gt; on the Golden Path for developing backend applications in the JVM ecosystem. Since adopting &lt;a href=&quot;https://blog.twitter.com/engineering/en_us/a/2014/netty-at-twitter-with-finagle.html&quot;&gt;Twitter’s Finagle&lt;/a&gt;, we’ve also built extensive tooling on top, up to a point in which we could derive a library of our own: JVMKit, which helps building JVM applications by providing an implementation of common patterns and protocols used at SoundCloud.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 552px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e56c64e2e53343c67e94f8e3b5abb660/af83c/jvmkit-stack.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 123.18840579710144%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAAsSAAALEgHS3X78AAAGFElEQVQ4yzWV6VOTVxTG37+g/djpWIRAtLUFDEs2IJCFhKWMSumIEAhJAIOQQAQKRq0iiwrCSGVx2mF0BNQRFVfcxX3f933fd0fHGZdR+7tM/HDm3vfe+557zvM851wpICDgu3Hjxo1MSUkJcLlcAR6PZ1hZWdmw4uLiYSUlJT/l5+cHZGdnC/tp/Pjxw7FATJaRkSEbM2ZMUHp6evDYsWND+F+emJj4g5SUlDRqz549ukePHik3b95s6Ovri123bl1Cf3+/jlEv5itWrIhbvny5WDNwxrJy5Urd2rVrjatXr9Zv375df+3aNeXdu3dVPT09kZLBYJC/fv067MyZMzHz5893b9myJXfTpk2TMNfAwICDH3Mx1759+7Ix+6pVqzK2bdtWsHXr1sLBwcE8AnAeOXJE8/Xr1996e3sjpfj4ePmTJ0+iiVK5a9eubDZyMSc2AfuTW6d4vd62K1euOA8dOpQl1jAb9oeY79+/37ZkyRID8/DOzs4YibzlDx8+VF26dElx8eJF4STffzhPOK+rq6tXKpX9KpVqwOFwtLKWiaX7L8w4ceJEJjCYmEfMmzdPJaWlpY0gQs3p06cj16xZU3n+/PnCU6dOFXPQS9RinkMqU1paWmpIuZzvsuPHj1cyVp89e7aAf8r9DsPnzp0bLTAccfXq1SgWIgHYuGHDBmGmZcuWmZcuXWoBo1SISWEvDVxT+TkJaH4HPzOjCbM8fvxY/B9eX1+vlkhFfufOnZhXr14pYC2Z2w2kbnzx4kX8+/fvEx48eBDHD6abN2/qL1++bGQ0MerFGQKxQIgZhiNwGDZ79myNIGXkvXv3FKSsXb9+vZf0i27fvu3ZuHFj3cKFC6sOHjxYTBTFQOHF+WRGDwGU3r9/v5pLy9n/i+h1OAydNWuWWgoNDZVzMJKbtLBYzIYVm4TzaqvV+p/T6exEyN1Go3EQsf9js9k6ampqBDkThRpu3LghJDXksLGxMUYym81y0o0mCh1WwUaOMEgpI0ofh6dwoberq6uOS3xkUnHr1i2v36EdCMr4Ty8wnDNnjlaQEkyVRF64cEHT3d3tPHbsWObevXuzYNGO2HNPnjyZzfcEsM3cvXu3A+E7IMQOQYWYjSpycrkQdvgQhjqdTgYmkWCnQEc5whYsWOAEP1tHR0cut9phL6+2ttaB2RsaGuykls9oRSZ57FkRvVKkDBQqSa/XB5HyaBSvo6SqP3/+7Hr37l0ppVVBldT6hV4gjEu9kOAT2EGIh9FN9FXUuZH5LzNmzFBJ0dHRgS9fvlQcOHBAj5DL2SgSpQUZ/86cObOFsYvS6ySSlqampnq6Sy94TvtWokjHs3PnzgTmo8guVoqNjZU9e/ZMherjwatagP3hw4d8OshkyKhA2HUIvKaqqqrR7Xa347QZ583+i0vAvhTZCIcK4ImRLBaLHFI00K9dtGjRRAC3ISEHwrVygQ0R516/fj2LluVobW31QFY21VJEybmoIBc4F3JeyCYcjNVScnJyCM5UYgEMI3bs2KElfQ2OI5CDitvVlKKGeQyQiJ4XQzvTwbBaGNrVfPr0KezLly9hzc3NWikhISGYlBWAraF+K0nVQfp5YON4+/atDcuHNNubN28mPn/+vJCIcw4fPlyCczdWsHjxYh8lK9pXKMwP6TDk6dOnSlKJP3r0qMsvbIdoX1zwNz2ugdFHas004FqE7iGiEn9FCclMFk1FYPitUkT7imUj5ty5c0X+5mpHm0W8Jwt5InqysrL6pk+f3gzrTfTEHiIu9ffLQhqHm+qJFf0Q7aqk1NTUn4lQjQ61aK/y48ePIsVJwCB05uKyEkgRpWYVjYPKmMb5IsrPjZVQPZXAFSc4oNJGiwYr5xYFgv4VLMyQYkKsZsBORF+JpGhhLR756HlPzLwzBtg1MjdAkBiNBKHAYRSNOGyoOYBBHD1NjXyi0J6KdhZBe4oWcuJ5ULKmEKUJKVFCs1RJBA9bNIRFQVYU59SQqaX0tJLJZPqeSgiZOnVqUEVFhczn88nKy8sDqVM5B2SsByLYYL5lFH8Q86C2trbg9vb2YJ6FQLQZCHHDGcWb/eP/gLjVNfiVEaYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVMKit stack&quot;
        title=&quot;JVMKit stack&quot;
        src=&quot;/blog/static/e56c64e2e53343c67e94f8e3b5abb660/af83c/jvmkit-stack.png&quot;
        srcset=&quot;/blog/static/e56c64e2e53343c67e94f8e3b5abb660/9ec3c/jvmkit-stack.png 200w,
/blog/static/e56c64e2e53343c67e94f8e3b5abb660/c7805/jvmkit-stack.png 400w,
/blog/static/e56c64e2e53343c67e94f8e3b5abb660/af83c/jvmkit-stack.png 552w&quot;
        sizes=&quot;(max-width: 552px) 100vw, 552px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Because Scala applications make up the vast majority of apps at SoundCloud, JVMKit mainly targets them, but it should be possible to use from any other JVM language as well — we currently run services in Java, &lt;a href=&quot;https://developers.soundcloud.com/blog/building-clojure-services-at-scale&quot;&gt;Clojure&lt;/a&gt;, and &lt;a href=&quot;https://developers.soundcloud.com/blog/category/ruby&quot;&gt;JRuby&lt;/a&gt; as well, even though we try to steer everybody toward the Golden Path. It provides opt-in modules for serving public traffic in the shape of BFFs, as well as integration with MySQL, Kafka, Memcached, and Prometheus, among other functionality.&lt;/p&gt;
&lt;p&gt;With such a broad audience, it’s no surprise that JVMKit was largely adopted across SoundCloud, and it powers much of what we provide to our users — it became a critical component of our technology stack, with hundreds of services depending on it and thousands of instances running it in production.&lt;/p&gt;
&lt;p&gt;JVMKit’s repository sees active development: New features, bug fixes, and security patches land every week. Different from an open source project, in which dependent projects are unknown, within the SoundCloud organization, we can draw a transparent view on all callers to JVMKit’s APIs to see which apps are lagging behind and which are on the straight cutting edge.&lt;/p&gt;
&lt;p&gt;We then ask ourselves: How exactly can we make sure the entire company follows JVMKit’s release cycle? As a platform team and developers of JVMKit, we want to minimize the disruptions to each team’s roadmap, onboarding into new APIs, replacement of deprecated calls, etc. In other words, we want to minimize the work we put on our colleagues’ plates so they can focus on higher objectives. For that, we have built automation to perform batch upgrades across all of our repositories — ideally, JVMKit upgrades are completely transparent.&lt;/p&gt;
&lt;h2&gt;Reverse Dependency Graph&lt;/h2&gt;
&lt;p&gt;To identify which services depend on JVMKit, we rely on these relationships being explicitly defined in terms of code. As part of the previously introduced concept of the Golden Path for backend development, we do that through the build scripts that are part of each repository. For the majority of our backend repositories, this means &lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt; builds. The same idea is applied to our other Golden Paths, with &lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle&lt;/a&gt; on Android and npm for web development, for example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; jvmkitVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;15.0.0&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project in file&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;settings&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    libraryDependencies &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;com.soundcloud&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jvmkit-admin-server&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; jvmkitVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;com.soundcloud&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jvmkit-json-play&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; jvmkitVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Every five minutes, changes to the default branch of each of SoundCloud’s repositories hosted on GitHub are indexed by &lt;a href=&quot;https://github.com/google/zoekt&quot;&gt;Zoekt&lt;/a&gt;, a search engine based on regular expression matching (read &lt;a href=&quot;https://swtch.com/~rsc/regexp/regexp4.html&quot;&gt;this article&lt;/a&gt; for more in-depth information). Out of the box, Zoekt provides a web interface that we then make available to all SoundCloud engineers, but we also added the ability to query its results through a simple scripting API.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7d1b711c1872249e4adadc2adca9a6c9/da6b3/zoekt-arch.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.51396648044693%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABSklEQVQY02NgAAKj//8Z4ICREc6cd+4hggME1TMXgvksLCxsHBwcMiD26tWrGVCAAAMDB9gcJgZOIMEME7/7/z9Yc/60VwEL93xcVjfrTTRMDmggP9BASxMTEyYQv6mpSebMmTOLV65c2c7A4xNlwR5XmAoUlwZiIQZmFhYgzSTlU88GUqxe82iZ9cr//+2bHu0G8VViprECKRAWdXBwAKuZNWuWyc+fP/+fPn36PwNXxwpXvrqew0Cl7EAn8TGwsHKBNHBq+3GCFEuXPp6nMOX/f7Oax5tBfEHbfHYQBcSmvLy84iCxyspK40uXLv3fsWMHOOyUgJ5QANJSIL8woAGJ2J2WsgUXCnQS9joiCYNcJs0GBCCOpKQkT15eXmZ9fX0eiC8DDAhhBiYmdli0wHQxcQlxAylFULCBzGZk5RJjwA6AUcHAA1IDADF7UGBgPL2AAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Zoekt’s architecture&quot;
        title=&quot;Zoekt’s architecture&quot;
        src=&quot;/blog/static/7d1b711c1872249e4adadc2adca9a6c9/8ff1e/zoekt-arch.png&quot;
        srcset=&quot;/blog/static/7d1b711c1872249e4adadc2adca9a6c9/9ec3c/zoekt-arch.png 200w,
/blog/static/7d1b711c1872249e4adadc2adca9a6c9/c7805/zoekt-arch.png 400w,
/blog/static/7d1b711c1872249e4adadc2adca9a6c9/8ff1e/zoekt-arch.png 800w,
/blog/static/7d1b711c1872249e4adadc2adca9a6c9/6ff5e/zoekt-arch.png 1200w,
/blog/static/7d1b711c1872249e4adadc2adca9a6c9/da6b3/zoekt-arch.png 1432w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;By combining Zoekt’s API and the standardization across the company, we can write a simple query like &lt;code class=&quot;language-text&quot;&gt;&amp;quot;jvmkit&amp;quot; f:^build.sbt&lt;/code&gt; and list all the projects that depend on JVMKit — in other words, we can build a level of the reverse dependency graph in which JVMKit is the root.&lt;/p&gt;
&lt;h2&gt;Verification&lt;/h2&gt;
&lt;p&gt;With these tools at hand, we can now start thinking about how we can automatically verify whether a change in JVMKit will integrate smoothly with other projects. For the sake of the example, let’s take an arbitrary service called &lt;code class=&quot;language-text&quot;&gt;likes&lt;/code&gt;, which is responsible for managing which tracks each user liked on SoundCloud. If we were to write such an algorithm in pseudocode, this is how it would look:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Clone &amp;quot;likes&amp;quot; repository
2. Find build.sbt
3. Replace the jvmkitVersion value with the latest snapshot
4. Compile &amp;quot;likes&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If all steps in the algorithm succeed, we guarantee that the new changes in JVMKit didn’t cause issues with &lt;code class=&quot;language-text&quot;&gt;likes&lt;/code&gt;. We can generalize this idea and run the algorithm for each and every repository we previously identified as reverse dependencies of JVMKit — if all of them succeed, the new JVMKit version is good to go!&lt;/p&gt;
&lt;p&gt;Our work is done, right? Not really. There are other cases that our algorithm currently doesn’t consider. For instance, what happens if we were planning to release a JVMKit version and coincidentally on that day, the &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch of &lt;code class=&quot;language-text&quot;&gt;likes&lt;/code&gt; was broken? We shouldn’t consider it the fault of that JVMKit version’s bump. To isolate that scenario, we can compile the target project with no changes whatsoever.&lt;/p&gt;
&lt;p&gt;If JVMKit’s new version introduces an API-breaking change — requiring, thus, a &lt;a href=&quot;https://semver.org/&quot;&gt;major version bump&lt;/a&gt; — we allow for the engineers running the upgrade to automatically fix the breaking change by executing a set of processors directly in the repositories source code with tools like &lt;code class=&quot;language-text&quot;&gt;sed&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;awk&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;lint&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Now, when the release is a minor or patch change, surely the project will compile, but does it behave as we expect it to? We should also run automated tests to ensure our business logic is still respected. Once again, here we can leverage the standardization provided by the Golden Path: All Scala projects that are built with &lt;code class=&quot;language-text&quot;&gt;sbt&lt;/code&gt; will execute their tests with the same command: &lt;code class=&quot;language-text&quot;&gt;sbt test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, the pseudocode presented earlier can be extended to become:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Clone &amp;quot;likes&amp;quot; repository
2. Compile &amp;quot;likes&amp;quot;
3. Run tests on &amp;quot;likes&amp;quot;
4. Find build.sbt
5. Replace the jvmkitVersion value with the latest snapshot
6. Run source code processors for automatic API fixing
7. Compile &amp;quot;likes&amp;quot;
8. Run tests on &amp;quot;likes&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this, it becomes clearer who the culprit for a failure is, depending upon which step of this sequence the algorithm stops. We’re also more confident of a smooth integration if it completes successfully.&lt;/p&gt;
&lt;h2&gt;Taking It a (Pipeline) Step Further&lt;/h2&gt;
&lt;p&gt;Instead of going through the described process manually and in an ad hoc fashion every time we decide to release a new build of JVMKit, we can develop the automation even further and leverage a continuous integration system to perform the checks for every single commit in the library repository.&lt;/p&gt;
&lt;p&gt;As mentioned earlier, at SoundCloud, we have hundreds of projects that rely on JVMKit, which makes compiling and running their tests a time-consuming task. To speed up JVMKit’s development and feedback loop, we decided to split the automation into two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A nightly, long-running pipeline that verifies the new JVMKit against all projects in the organization&lt;/li&gt;
&lt;li&gt;A faster pipeline, limited to a small allowlist of projects, that runs on every pull request&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is achieved through scripts that fetch project lists from Zoekt and translate them &lt;a href=&quot;https://www.thoughtworks.com/radar/techniques/pipelines-as-code&quot;&gt;into pipeline descriptors that can be ingested by our CD system&lt;/a&gt;. The generated pipelines include steps to run tests, depending on whether these exist on each of the target projects.&lt;/p&gt;
&lt;p&gt;On top of that, in the real world, not all projects are well behaved. There are different reasons we might want to skip specific steps of the verification algorithm: when the test suite takes too long, if &lt;a href=&quot;https://developers.soundcloud.com/blog/tests-under-the-magnifying-lens&quot;&gt;it’s known to be flaky&lt;/a&gt;, or when the project needs a specific environment configuration or special dependencies that aren’t provided out of the box. For these cases, we also introduced a denylist that serves as input to the CI pipeline generator script.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/2198e/reverse-deps-pipeline-failure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.190727081138043%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABR0lEQVQY022QTXOiQBRF+UkqBpqG4ltAxEQRIaMyySJVyWJqKpWpMSar/POTh1amZpHFqfv6dvft188oFin1qqFch6RLxbzVZ/K1Q7HRLHbCD831nabsLvVi51Dd+jyfnvj995G7+5/0fU/XdRjFMmUzBDYhyVpR7l3KgybbSqgEVPey7i8630tY75LvFN1DxdvHC7/+PFBvbjjsDzTbBqOMQ5arNVUWkAc2VWxznUhwpFgIq8TmRqhTm+WwNxMvvqKdp7y/vnE8nqiqiqZpCMMQw9fycp5RJCkzX77t+RcNImZhzGxQIZM6Ef/L85RCawcl6jgOnudhWRaGY1sS4klAIJcCijgiEVVao+Sg7SgsZWNeTTGnpjA9M55MGI/H/xiNRkzEM3zfZ1XXbNtWZrCllcEO7edZRhRFuK577mA4/D+maX7LJ+ZW2N9bsBNNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Individual project failure in generated pipeline&quot;
        title=&quot;Individual project failure in generated pipeline&quot;
        src=&quot;/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/8ff1e/reverse-deps-pipeline-failure.png&quot;
        srcset=&quot;/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/9ec3c/reverse-deps-pipeline-failure.png 200w,
/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/c7805/reverse-deps-pipeline-failure.png 400w,
/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/8ff1e/reverse-deps-pipeline-failure.png 800w,
/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/6ff5e/reverse-deps-pipeline-failure.png 1200w,
/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/2f950/reverse-deps-pipeline-failure.png 1600w,
/blog/static/abdc4bbb4a7f0691c2d83fbb62d9dc73/2198e/reverse-deps-pipeline-failure.png 1898w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Summing It All Up&lt;/h2&gt;
&lt;p&gt;Observing the running pipelines is an engaging activity. Every day, we can have a view on how our projects will behave when moving forward, and when failures occur, it’s also easy to pinpoint reasons: If a single project failed due to an API change, we can dive into possible shortcomings of its implementations or uses of JVMKit’s APIs that weren’t originally intended. If many (or all) projects fail, then it’s obvious that the issue is with JVMKit itself, and we can quickly act on providing fixes.&lt;/p&gt;
&lt;p&gt;We’ve reached an interesting point in our development practices for JVMKit after years of investment: With good tooling and automation in every step of the process, we’re confident when releasing a change to the library that powers dozens of teams, hundreds of systems, thousands of instances, and millions of users. We’re fearless about releasing JVMKit more often than ever with no impact or disruption to our team’s planned roadmap, while also ensuring technological standardization across SoundCloud.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[High-Tier Content in the SoundCloud API]]></title><description><![CDATA[You can now retrieve metadata for our full SoundCloud catalog through our API by providing the new  filter in your request. Until recently…]]></description><link>https://developers.soundcloud.com/blog/high-tier-content-in-the-soundcloud-api</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/high-tier-content-in-the-soundcloud-api</guid><pubDate>Thu, 06 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You can now retrieve metadata for our full SoundCloud catalog through our API by providing the new &lt;code class=&quot;language-text&quot;&gt;access&lt;/code&gt; filter in your request.&lt;/p&gt;
&lt;p&gt;Until recently, high-tier content (Go+ content) wasn’t served from our API. This removed the ability for clients to decide how to present restricted or blocked content, i.e. high-tier content. With this change, clients are now able to fully control the user experience:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/playlists/833426378/tracks?access=playable,preview,blocked&amp;amp;linked_partitioning=true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the above example, the &lt;code class=&quot;language-text&quot;&gt;access&lt;/code&gt; parameter indicates which level of playback accessibility for a track is available to the requesting user. There are three possible values supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;playable&lt;/strong&gt; — The user can listen to a full track.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;preview&lt;/strong&gt; — The user gets a snippet of a track (Go+ content for free users).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;blocked&lt;/strong&gt; — The user isn’t allowed to play the track but can see the metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can specify any number of allowed values depending on your use case.&lt;/p&gt;
&lt;p&gt;For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;access=playable&lt;/code&gt; will return only tracks the user can listen to.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;access=playable,preview&lt;/code&gt; will return all tracks that the user can either play fully or preview the snippets of.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;access=playable,preview,blocked&lt;/code&gt; will return metadata for all tracks — even the blocked ones the user cannot listen to due to various restrictions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If no filter is provided, the endpoint falls back to the default behavior and provides the content with the &lt;code class=&quot;language-text&quot;&gt;access=playable,preview&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;All track entities are now also annotated with this new field to indicate the level of playback accessibility available to the user:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;artwork_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://i1.sndcdn.com/artwork.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123456&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;isrc&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;track&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;permalink_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://soundcloud.com/userPermalink/trackPermalink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;stream_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.soundcloud.com/tracks/123456/stream&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ...
  ...
  &lt;span class=&quot;token property&quot;&gt;&quot;access&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;playable&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For an overview of full use and all the supported endpoints, please refer to our &lt;a href=&quot;https://developers.soundcloud.com/docs/api/explorer/&quot;&gt;API specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Encountered an issue? Report it on the &lt;a href=&quot;https://github.com/soundcloud/api&quot;&gt;issue tracker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For future updates, please follow us on &lt;a href=&quot;https://twitter.com/soundclouddev&quot;&gt;Twitter&lt;/a&gt; and our &lt;a href=&quot;https://developers.soundcloud.com/blog/&quot;&gt;blog&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Journey of Corpus]]></title><description><![CDATA[It seems like a simple enough concept: You take data from how your users interact with your product, and you use it to make business and…]]></description><link>https://developers.soundcloud.com/blog/the-journey-of-corpus</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/the-journey-of-corpus</guid><pubDate>Thu, 29 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It seems like a simple enough concept: You take data from how your users interact with your product, and you use it to make business and product decisions. Informed decisions will certainly be better than guesses, so in the end, this should pay off.&lt;/p&gt;
&lt;p&gt;But that’s for anyone who hasn’t seen the dirty underbelly of data — from how broken tracking can lead to incorrect conclusions, to having lots and lots and lots of data but no documentation around what any of it means. If you’ve worked a day in your life as an analyst/data scientist/data engineer or in any other data-heavy role, you know how bad things can get.&lt;/p&gt;
&lt;p&gt;In mid 2019, we at SoundCloud were in a position many companies have probably found themselves in: Our stakeholders wanted to make use of the wealth of data available to us, as we’re the &lt;a href=&quot;https://help.soundcloud.com/hc/en-us/articles/115003570488-What-is-SoundCloud&quot;&gt;world’s largest open audio platform&lt;/a&gt;, but access to data was hard, since our data warehouse was the accumulation of six years of decisions that made sense at the time but naturally grew outdated over the years. As a result, we just weren’t able to support the business and product teams in the data-driven decisions they wanted to make.&lt;/p&gt;
&lt;p&gt;More concretely, a single (small) team owned more than 100 &lt;a href=&quot;https://en.wikipedia.org/wiki/Extract,_transform,_load&quot;&gt;ETLs&lt;/a&gt; (extract, transform, load processes) on &lt;a href=&quot;https://aws.amazon.com/redshift/&quot;&gt;Amazon Redshift&lt;/a&gt; — most with little to no documentation and some inconsistencies between them (they were, after all, built at different points in time, by different people, for different purposes). However, we had signed a contract with Google to switch over to &lt;a href=&quot;https://cloud.google.com/bigquery&quot;&gt;BigQuery&lt;/a&gt; (BQ) as our new data warehousing platform. This was our opportunity to start fresh and build something that would hopefully fulfill SoundCloud’s needs at the time &lt;em&gt;and&lt;/em&gt; scale with time.&lt;/p&gt;
&lt;h2&gt;Corpus Is Born&lt;/h2&gt;
&lt;p&gt;So we set out on our journey to create what would become the Corpus BQ project: a centralized single source of truth for SoundCloud’s most relevant data. To do this, we created the Data Corpus team, whose mission statement is to &lt;strong&gt;“Implement data governance on the datasets that are most critical for product and business decision making.”&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;And just what exactly is data governance? According to Wikipedia, &lt;a href=&quot;https://en.wikipedia.org/wiki/Data_governance&quot;&gt;data governance&lt;/a&gt; is “a data management concept concerning the capability that enables an organization to ensure that high data quality exists throughout the complete lifecycle of the data, and data controls are implemented that support business objectives.” This was what we needed to become a more data-driven company.&lt;/p&gt;
&lt;p&gt;Corpus as a team sits somewhere in the middle of what most people think of as Data Science and Data Engineering. At SoundCloud, there’s a dedicated team of data engineers known as Data Platform, which is responsible for maintaining all the infrastructure around data. We like to think of it as “Data Platform engineers the infrastructure, we engineer the data.” Our job titles are still Data Scientist or Data Engineer, depending on each person’s background and strengths, but we’re closer to what some companies now call &lt;a href=&quot;https://locallyoptimistic.com/post/analytics-engineer/&quot;&gt;analytics engineers&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Corpus Principles&lt;/h2&gt;
&lt;p&gt;After defining a team mission, we needed to decide how we were going to implement data governance. We came up with this list of six principles, which will be covered in detail in the rest of this post:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Quality&lt;/li&gt;
&lt;li&gt;Compliance&lt;/li&gt;
&lt;li&gt;Timeliness&lt;/li&gt;
&lt;li&gt;Usability&lt;/li&gt;
&lt;li&gt;Efficiency&lt;/li&gt;
&lt;li&gt;Maintainability&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This list is based on an &lt;a href=&quot;https://iso25000.com/index.php/en/iso-25000-standards/iso-25012&quot;&gt;ISO standard&lt;/a&gt; that we adjusted to our needs by removing characteristics that were outside our responsibilities and adding “maintainability,” for reasons we’ll explain below.&lt;/p&gt;
&lt;h3&gt;Quality&lt;/h3&gt;
&lt;p&gt;Quality is defined as “providing a consistent and accurate source of truth.” The bottom line is: Our stakeholders need to be able to trust our data, or they won’t use it. Exactly how we achieve this could be the topic of its own blog post, since it involves several different strategies. Here are a couple examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Whenever we perform a change on an existing ETL or table, we create a new version of it and compare it against the currently deployed version. We then use an automated tool we built to validate that the two versions are exactly the same, with the exception of the changes we introduced.&lt;/li&gt;
&lt;li&gt;All our ETLs have data quality checks that run whenever new data is added to a table. These range from cardinality checks (e.g. “Does that table that’s supposed to be one row per user really have one row per user?”) to checking the input versus the output data (e.g. “Did all the clicks in the input make it into the output?”).&lt;/li&gt;
&lt;li&gt;We have an outlier detection algorithm (based on &lt;a href=&quot;https://en.wikipedia.org/wiki/Outlier#Tukey%27s_fences)&quot;&gt;Tukey’s interquartile range method&lt;/a&gt;) that runs daily and warns us of any unexpected data observations. We then investigate these outliers and notify stakeholders if there’s anything of note.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Compliance&lt;/h3&gt;
&lt;p&gt;As required by law, we make sure we comply with the appropriate regulations for data, such as &lt;a href=&quot;https://gdpr-info.eu/&quot;&gt;GDPR&lt;/a&gt;. This means, for instance, that we don’t provide any personally identifiable information (PII) in any of our resources. If there’s a need for this sort of data, there are established internal procedures to go through that don’t involve our team.&lt;/p&gt;
&lt;h3&gt;Timeliness&lt;/h3&gt;
&lt;p&gt;Internally, we define timeliness as having our daily updates finished by 10AM CET. In actuality, most days, they’re finished before 8AM CET. This is important because it ensures the data is available by the time our stakeholders start working.&lt;/p&gt;
&lt;p&gt;We ensure we meet this deadline by working on both the efficiency of our ETLs and the timeliness of their dependencies. When analyzing our overall timeliness, we map out the landing times of both our ETLs and their source data. We then work on improving our code to make it run faster, and we work with the teams that provide the source data to ensure there are no bottlenecks. &lt;/p&gt;
&lt;p&gt;One example of an easy win: One table we needed had its completion unnecessarily coupled with the completion of another table we didn’t actually need, and which took much longer to complete than the first table. We worked with the owners of these tables to decouple them, and we removed four hours from our pipeline delivery time in one fell swoop.&lt;/p&gt;
&lt;p&gt;When we fail to meet our 10AM deadline, which naturally happens from time to time, we get notified automatically, and whoever is &lt;a href=&quot;https://developers.soundcloud.com/blog/building-a-healthy-on-call-culture&quot;&gt;on call&lt;/a&gt; investigates and works on solving whatever the issue is. If the issue isn’t quickly solvable, the matter is escalated to an incident and becomes an engineering-wide priority to resolve.&lt;/p&gt;
&lt;h3&gt;Usability&lt;/h3&gt;
&lt;p&gt;We define this principle as “providing easily accessible and consumable data.” One of the main problems with our old setup wasn’t that we didn’t have enough data; it was that it was very hard to know when to use what and how. As such, with Corpus, we set out to build (as much as possible) a self-service platform to provide resources that can be used by all our stakeholders, as long as they have basic SQL skills. There will always be the necessity for certain analysis to be done by experts (data scientists/data analysts), but this shouldn’t be the case for basic data needs.&lt;/p&gt;
&lt;p&gt;We ensure usability by focusing on the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Descriptions — This might seem too simple to have a real impact, but it’s one of the features our stakeholders most appreciate. There’s no need for them to ping around between Slack channels asking what the meaning of a certain field is — it’s all explained in the same place they get our data from, in the &lt;a href=&quot;https://console.cloud.google.com/bigquery&quot;&gt;BQ UI&lt;/a&gt;. We even add at least one example query.&lt;/li&gt;
&lt;li&gt;Design documents — These focus on documenting more advanced use cases (more complex example queries), data anomalies (there are always some), and context around certain design decisions. This is what our stakeholders dig into if their questions cannot be answered directly by the descriptions.&lt;/li&gt;
&lt;li&gt;Ease of use of resources — We don’t want to build something that’s technically perfect but hard to use. Sometimes we sacrifice on storage or efficiency to make sure the table is still queryable without expert SQL skills. Other times, when the price to pay for usability is too high, we build a table with a schema that’s non-trivial (because it saves a lot of storage, for instance). But then we build a view on top that turns it into a simpler schema and only show that to stakeholders, allowing us to get the best of both worlds.&lt;/li&gt;
&lt;li&gt;Providing training — We provide (and record) company-wide training both on SQL and on how to use the resources we provide.&lt;/li&gt;
&lt;li&gt;Accessibility — The fact that we use a cloud solution (BigQuery) that’s ready to use out of the box means no one actually needs to install anything or ask for access to use any of our resources; they just need to access the &lt;a href=&quot;https://console.cloud.google.com/bigquery&quot;&gt;BQ console&lt;/a&gt; by authenticating through their @soundcloud.com email address. Note that our data is available across the company because it doesn’t have any PII, as explained in the Compliance section above.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Efficiency&lt;/h3&gt;
&lt;p&gt;As hinted at by some of the other characteristics, we want to make sure we optimize execution time and storage, as these relate to both performance and costs. For this, we try to make the most of BQ’s features like &lt;a href=&quot;https://cloud.google.com/bigquery/docs/reference/standard-sql/hll_functions&quot;&gt;sketches&lt;/a&gt;, &lt;a href=&quot;https://cloud.google.com/bigquery/docs/best-practices-performance-input#using_nested_and_repeated_fields&quot;&gt;nesting&lt;/a&gt;, and &lt;a href=&quot;https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions&quot;&gt;UDFs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Getting proficient with BQ was something that took us some time, since it works quite differently from what we were used to before (&lt;a href=&quot;https://aws.amazon.com/redshift/&quot;&gt;Redshift&lt;/a&gt;). A few things helped with this learning curve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The fact that we were able to use it for some time with the old tables before we needed to start building the new versions allowed us to get our feet wet before we began making the big design decisions. We also saw what worked and didn’t work about our old designs in this new platform.&lt;/li&gt;
&lt;li&gt;We hired a consultant who was already an expert on BQ to help us with the migration. Their suggestions opened our eyes to features we didn’t know existed, such as sketches, which made a big difference once we started using them.&lt;/li&gt;
&lt;li&gt;We acknowledge that BigQuery is in constant development and improvement, so we keep a close eye on new releases and even work with Google from time to time to try to identify the features that would be the most helpful to us.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Maintainability&lt;/h3&gt;
&lt;p&gt;Knowing the pain of maintaining something we didn’t build and had no documentation on (who doesn’t?!), we wanted to make sure we were building code that was easy to read, extend, and maintain, so we decided to make this into its own principle.&lt;/p&gt;
&lt;p&gt;We accomplish this by focusing on three concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The “80 percent principle” — We aim to create tables that respond to 80 percent of stakeholder requests, not 100 percent. For 100 percent, one will often get into too many edge cases and end up with an over-complicated setup for something that’s rarely used. As such, we don’t try to answer every single data question out there — only 80 percent of them. This principle, maybe more than anything else, has become a team staple that we turn to often in discussions.&lt;/li&gt;
&lt;li&gt;Shared set of best practices — We maintain a set of data best practices covering the different tools we use and use cases we have. The idea is that there’s a lot of commonality between different team members’ work, which helps us have broad ownership of everything we do. We also do knowledge sharing about these practices with other teams.&lt;/li&gt;
&lt;li&gt;Scalability — We’re well aware that products change, and we’ve seen SoundCloud evolve a lot over the years: Features are launched or deprecated, and stakeholders change, and so do business priorities. We know our data needs to evolve over time as well. As such, we try to build our resources in a way that’s easy to extend (e.g. adding a new metric doesn’t require altering a table’s schema). We also have well-established versioning and release processes, both of which allow us to evolve the data we provide without ever breaking service to our stakeholders (this could be a blog post of its own too).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One aspect of our ETLs that might surprise people is the fact that they’re all in &lt;a href=&quot;https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax&quot;&gt;BQ SQL&lt;/a&gt;. In the past, we owned ETLs in PostgreSQL and Spark/Scala, some with Python mixed in, and even one in Haskell. However, in BQ, every data pipeline we’ve encountered (so far) is actually simpler and more efficient to build using plain old SQL. We’re open to exploring other options — BQ can support many different setups. We just haven’t found a need for this, and we also greatly value the consistency and readability benefits we get from using a language that most of the company is familiar with.&lt;/p&gt;
&lt;h2&gt;The Finished Product (So Far)&lt;/h2&gt;
&lt;p&gt;Putting all of these principles into practice, we were able to build a BQ project that takes up 90 percent less storage space than the raw data it reads. In turn, that empowers anyone in the company to answer most (we are, after all, aiming for 80 percent) of their data questions.&lt;/p&gt;
&lt;p&gt;If you’re wondering what the rest of our stack looks like, it doesn’t stop at BQ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For scheduling our ETLs, we use &lt;a href=&quot;https://airflow.apache.org/&quot;&gt;Apache Airflow&lt;/a&gt;, a versatile scheduler that makes sure our ETLs (along with their checks) run every day — and we’re notified if something goes wrong.&lt;/li&gt;
&lt;li&gt;For dependency management, we use an internal tool (built by our Data Platform team) called Datapub.&lt;/li&gt;
&lt;li&gt;For the BQ project infrastructure, we use &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; to manage access rights, dataset and table/view/UDF creation/deletion, and schemas.&lt;/li&gt;
&lt;li&gt;For powering the automated data validation tool mentioned in the Quality section above, we use &lt;a href=&quot;https://docs.pytest.org/en/stable/&quot;&gt;pytest&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For version control, the whole company relies on &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are of course just our main tools; others will come up from time to time.&lt;/p&gt;
&lt;h2&gt;Final Notes on the Journey&lt;/h2&gt;
&lt;p&gt;It’s only fair to point out that we didn’t have such a clearly defined mission and principles when we were in the midst of putting the first version of the Corpus together and trying to get the team up and running. We started smaller and simpler, but we always had certain principles in mind (like the “80 percent principle”), which made a big difference at times when we faced tough decisions on which direction to take.&lt;/p&gt;
&lt;p&gt;This is to say that it might seem like a daunting task to come up with such clearly defined scope and principles when you start a project like this, but you still shouldn’t skip the step of thinking about these things. The temptation to just get the work started and figure it all out as you go along might be high. For us, though, making sure everyone had clear foundations to fall back on paid off in the long run. When we didn’t have a pre-existing principle to rely on, we discussed which way to go and extended our principles to make sure it was covered for the next time it came up.&lt;/p&gt;
&lt;p&gt;These principles also served many times as a first line of defense when outside teams or stakeholders tried to change our scope. Often, once we explained our thoughtful reasoning behind why we thought we should or shouldn’t do certain things, they’d agree with us.&lt;/p&gt;
&lt;p&gt;However, we are of course open to changing these principles if there’s a strong enough reason to do so. One big lesson we learned from this migration (a reminder, really) was that just because a decision made sense at a certain point in time doesn’t mean it still makes sense five years later. We try to never lose sight of the fact that this current setup can easily become as outdated as the one we had pre-migration and, hence, we keep an open mind about possible changes.&lt;/p&gt;
&lt;p&gt;Another aspect of this journey that might be easy to forget is that, when we started the migration, we planned for a first implementation followed by a refactoring. Instead of waiting for the perfect conditions for us to be able to do exactly what we wanted, we got started with what we had. This allowed us to gain crucial experience in tools that were new to us (BQ and Airflow) and immensely improve from the first iteration of Corpus to the second. As a matter of fact, the road to a Data Corpus that we could proudly say abided by all of these principles was actually quite long:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Early 2018 — There was a first attempt at migrating using a &lt;a href=&quot;https://www.vxchnge.com/blog/understanding-lift-and-shift&quot;&gt;lift and shift approach&lt;/a&gt;, which was later aborted for a variety of reasons, but provided many precious lessons.&lt;/li&gt;
&lt;li&gt;Mid 2019 — We started planning a new migration from scratch.&lt;/li&gt;
&lt;li&gt;December 2019 — The first intermediate version of Corpus was released, still using the old data warehouse as a source in some places.&lt;/li&gt;
&lt;li&gt;April 2020 — The release of Corpus Alpha, the first iteration that was truly independent from our old data warehouse.&lt;/li&gt;
&lt;li&gt;June 2020 — The release of Corpus Beta, where we did a major refactoring of the code, unified table schemas, and saw big gains on storage and execution time.&lt;/li&gt;
&lt;li&gt;July 2020 — The release of Corpus 1.0, the first version of Corpus that abided by all the principles described in this post.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We recently released Corpus 2.2 in April 2021, with Corpus 2.0 and 2.1 coming before that, and we’re now starting the work on Corpus 3.0. At this point, our release work is mostly focused on extending Corpus’ coverage by adding tables that cover new entities or metrics, which are driven by the natural evolution of the product itself. We also fix the few bugs that get discovered from time to time, which is an inevitable reality of building anything. Corpus really is a continuously evolving project, which is what makes working on this team interesting and fun!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If all of this sounds like an exciting challenge to you, then &lt;a href=&quot;https://jobs.soundcloud.com/job?gh_jid=5223507002&quot;&gt;apply for the Data Corpus team&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;None of this would’ve been possible without the work of many SoundClouders outside the Data Corpus team, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The old Data Science Analytics team, which later got separated into the Data Science and Business Research teams. Many members of these teams contributed to the first version of Corpus when we weren’t yet a dedicated team. Nowadays, they are our stakeholders and help us prioritize what to work on next.&lt;/li&gt;
&lt;li&gt;Data Platform team — In addition to providing much helpful guidance and support during the migration, this team is continuously ensuring we have the necessary infrastructure to do our work.&lt;/li&gt;
&lt;li&gt;Content Authorization and Payments teams — Producers of data sources we consume that worked closely with us to meet our requirements.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[How to Successfully Hand Over Systems]]></title><description><![CDATA[In a product company, changes are inevitable so as to best support the strategy and the vision. Often during such a change, new teams are…]]></description><link>https://developers.soundcloud.com/blog/how-to-successfully-hand-over-systems</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-to-successfully-hand-over-systems</guid><pubDate>Tue, 20 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In a product company, changes are inevitable so as to best support the strategy and the vision. Often during such a change, new teams are formed and other ones are restructured. While there are many challenges to be solved during a big change, there’s one in particular that’s often overlooked: system ownership. &lt;/p&gt;
&lt;p&gt;Who will take ownership of the systems that were owned by a team that doesn’t exist anymore or that are better suited to be owned by another team? It’s in everyone’s interest that the ownership be given to a team familiar with the system’s domain, so that they can continue the maintenance and evolution.&lt;/p&gt;
&lt;p&gt;Regardless of when the system handover will happen, how it’s executed is important, since the cost of failure can be high, and that could result in an outage or a significant amount of unplanned work. &lt;/p&gt;
&lt;p&gt;Having experienced not-so-successful handovers — some of which took place over the course of a one-hour meeting — I was inspired to create a guideline that will help other teams do handovers differently. At the same time, my colleague Antonio N. went through an ownership change with his team. This also had few mishaps, so we joined forces to write a proposal document for doing system handovers at SoundCloud. &lt;/p&gt;
&lt;p&gt;We used the RFC approach to gather input, experiences, and opinions from the entire organization. It was welcomed with enthusiasm and since then has been used multiple times.&lt;/p&gt;
&lt;p&gt;I’m posting the complete guideline below not only to document what we did at SoundCloud, but also in hopes of providing a template for other companies to use when faced with a similar scenario.&lt;/p&gt;
&lt;h2&gt;Guideline for Internal System Handovers&lt;/h2&gt;
&lt;p&gt;The guideline is a list of questions, tasks, and actions that the involved parties should consider as part of the system handover. The topics listed can be covered in different ways: through documentation, meetings, pairing sessions, workshops, tasks, PR reviews, etc. &lt;strong&gt;The goal is to help the new team understand the what, why, and how of the system, and to empower them to maintain, change, and improve it.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;General Recommendations&lt;/h3&gt;
&lt;p&gt;As the system ownership change is a process itself, we recommend that it’s driven by the new team with the help and support of the previous system owner. Both teams should collaborate on the planning and execution of the tasks. &lt;/p&gt;
&lt;p&gt;There will be some new documents and artifacts produced as an outcome of the system ownership change. We recommend storing them in the system’s repo when possible, or else including a link in the repo (e.g. from the README file). This will help with both onboarding new team members and potential ownership changes in the future.&lt;/p&gt;
&lt;h3&gt;Why Are We Changing the Ownership?&lt;/h3&gt;
&lt;p&gt;Help everyone involved in the handover understand why there’s a system ownership change. This impacts the team engagement in the handover process. &lt;/p&gt;
&lt;p&gt;What’s best is to document the reasoning and to add additional information to the history of the system. In turn, this can reveal different things, such as if the ownership changed or underwent restructuring multiple times in a short period of time, or if the current organization isn’t set up to own such a system or if the system doesn’t belong to any team. Uncovering this information helps us ask important questions, such as is the system too complex, is it not in any team’s domain, or is it even needed anymore?&lt;/p&gt;
&lt;h3&gt;What Does the System Do? What Problem Does It Solve? What Is the Vision?&lt;/h3&gt;
&lt;p&gt;Here, we’re looking to understand the system from a product perspective. It’s helpful to know some history about the system, how it evolved, and what its vision for the future is.&lt;/p&gt;
&lt;p&gt;This could be a product session where product managers are involved. As an outcome of this session, it’d be nice to have a document to help onboard new people to the system.&lt;/p&gt;
&lt;h3&gt;High-Level Architecture of the System&lt;/h3&gt;
&lt;p&gt;Get an overview of the system, the main components, and their interfaces. A more detailed diagram will probably lead to more detailed discussions. It’s best to have an online diagram so that it’s easily available for future reference. &lt;/p&gt;
&lt;h3&gt;Use, Availability, and Criticality of the System&lt;/h3&gt;
&lt;p&gt;Get familiar with who’s using the system, what the criticality of the system is, and what it means if the system isn’t available. This is an opportunity to look into the available runbooks, metrics, and monitoring.&lt;/p&gt;
&lt;p&gt;In cases when the previous and the new team owning the system are not part of the same &lt;a href=&quot;https://developers.soundcloud.com/blog/building-a-healthy-on-call-culture&quot;&gt;on-call rotation&lt;/a&gt;, there &lt;strong&gt;must&lt;/strong&gt; be an additional session where the system is introduced and explained to the engineers in the rotation group, so that all of them can respond to incidents related to the system. This helps prevent bigger outages and maintain the healthy on-call culture.&lt;/p&gt;
&lt;h3&gt;Maintenance&lt;/h3&gt;
&lt;p&gt;As part of the maintenance of our system, we have daily, weekly, and monthly tasks that need to be executed by the team. In this section, we need to identify what those tasks are and what their periodicity is (if needed).&lt;/p&gt;
&lt;h3&gt;Data Storage Overview&lt;/h3&gt;
&lt;p&gt;Most of the systems have their own data storage. When taking ownership of a system, the new team takes ownership of that data and the infrastructure that comes with it. This is to get an overview of what, how, and where that data is stored and/or purged.&lt;/p&gt;
&lt;h3&gt;Batch (Offline) Jobs Overview&lt;/h3&gt;
&lt;p&gt;Some systems are using data that’s an outcome of a batch job. Also, many systems have batch jobs that produce datasets to be consumed for analytics or reporting. Get an overview of the batch jobs, their usage, outcomes, and maintenance.&lt;/p&gt;
&lt;h3&gt;Decision History&lt;/h3&gt;
&lt;p&gt;This is helpful to understand the architecture choices and the evolution of the system, as well as to learn about any constraints the system might have. It’s best if this is documented.&lt;/p&gt;
&lt;p&gt;We recommend using &lt;a href=&quot;https://www.thoughtworks.com/de/radar/techniques/lightweight-architecture-decision-records&quot;&gt;Lightweight Architecture Decisions Records&lt;/a&gt;, which has the following format: &lt;a href=&quot;https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Context, Decision, and Consequences&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;Tech Debt&lt;/h3&gt;
&lt;p&gt;Make sure you’re aware of the existing tech debt and you understand its implications. This is best if it’s documented.&lt;/p&gt;
&lt;h3&gt;Known Bugs&lt;/h3&gt;
&lt;p&gt;When ownership changes, user-facing bugs will be reported to the new team; however, many of them might already be known. Make sure you understand them and why they’re present. This helps not only by decreasing the time to investigate, but also by providing a good service to our users.&lt;/p&gt;
&lt;h3&gt;To Dos&lt;/h3&gt;
&lt;p&gt;In addition to the above, here are some tasks (not in order and not complete, since they are SoundCloud specific) that can ease the ownership change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project ownership update on GitHub&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grant permissions to the new team&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update offline jobs configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local development&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can the engineers build the project locally?&lt;/li&gt;
&lt;li&gt;Does the system have integration tests? If so, do they run in a local environment? Does the new owner need any additional information to run them?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How is the system deployed?&lt;/li&gt;
&lt;li&gt;Is there a CI/CD?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitoring and Alerting&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check if the monitoring graphs need to be updated&lt;/li&gt;
&lt;li&gt;Update the system-related runbooks &lt;/li&gt;
&lt;li&gt;In case the runbook location changes, please reflect that change in the corresponding alerts to avoid broken links from the alerts&lt;/li&gt;
&lt;li&gt;Add the system to a corresponding on-call group and have a knowledge transfer session &lt;/li&gt;
&lt;li&gt;Update alerts&lt;/li&gt;
&lt;li&gt;Update PagerDuty&lt;/li&gt;
&lt;li&gt;Our suggestion is to update the on-call rotation at the end, once the team has gained sufficient knowledge and confidence in the system&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 The list of things to do is quite long. Take your time with each task and don’t rush. Use the help of the previous team, and pay attention to details and to the alerts.&lt;/p&gt;
&lt;p&gt;💡 You can use your project management tool (e.g. JIRA) to track the progress of the handover. That will help both involved teams stay up to date on the status of the handover, the next steps, and when it will be completed.&lt;/p&gt;
&lt;p&gt;💡 If you’ve discovered other helpful tasks or topics, please update the guideline with them.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;As the name suggests, the above document is a guideline, and it’s up to the parties involved in the system ownership change to decide &lt;strong&gt;if they’re going to use it and how they’re going to use it&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;In most cases, ownership change is a collaborative process that enables the new owners to be motivated and have a solid understanding of the system to maintain and continue evolving it. In some cases, it can happen that there’s no one in the company that previously contributed to the system. However, even then, this guideline can help the team keep the focus on topics that are important to know, and not only on the codebase.&lt;/p&gt;
&lt;p&gt;The most important thing is to not be judgmental of the choices others made and understand that, at that time, it was the best decision. For example, instead of making statements like “You could have used A instead of B,” or “You could have done it like this,” or even the harsher “That is wrong!” or “That is a huge mistake!”, try to be curious and ask open-ended questions like “What made you use A?” or similar.&lt;/p&gt;
&lt;p&gt;I’d also recommend that the team taking ownership takes the time to go through each of the topics and gain understanding and knowledge — not only from an engineering perspective, but from the perspective of the product. One might think they can copy the guideline and fill in the sections, and that writing everything they know of and handing it over to the new team will complete the transfer. I would argue that this isn’t the intention, and I don’t believe it will have the same positive impact that can be seen when doing this collaboratively and dedicating time to exploration.  &lt;/p&gt;
&lt;p&gt;Additionally, the guideline is meant to be a live document, updated as teams are learning through the process.&lt;/p&gt;
&lt;h2&gt;Side Effects / Other Impacts&lt;/h2&gt;
&lt;p&gt;This guideline should inspire teams to have useful and up-to-date documentation; a README on how to contribute, test, and run locally; and high-level architecture diagrams. This helps not only when changing ownership, but also when onboarding new team members.&lt;/p&gt;
&lt;p&gt;Furthermore, it’s important to embrace the use of architectural decision records and help to reason about them in the future.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This guideline exists to help engineering managers, product managers, and teams acknowledge that system ownership change is a process that should be well planned and done at a time that works best for everyone involved. It’s a process that requires effort and has its cost. However, it can inspire the organization to nurture a healthy engineering culture with a high bus factor and systems that are easy to maintain, evolve, and reason about.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud’s New API Track Object]]></title><description><![CDATA[As a part of our efforts to improve API use, we’re introducing a new  object that’s more up to date with our current data model. The…]]></description><link>https://developers.soundcloud.com/blog/soundclouds-new-api-track-object</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundclouds-new-api-track-object</guid><pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a part of our efforts to improve API use, we’re introducing a new &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; object that’s more up to date with our current data model. The following change is also intended to standardize and simplify the response data. A new object will be used for all endpoints returning track-related information, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/playlist/:playlist_id/tracks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/tracks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/tracks/:track_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;New Track Object&lt;/h2&gt;
&lt;p&gt;The following shows a schema of our new &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; model:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;artwork_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;available_country_codes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;HashSet&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;bpm&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;comment_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;commentable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;download_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;download_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;downloadable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;duration&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;favoritings_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;genre&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;isrc&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;is_explicit&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Boolean&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;key_signature&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;label_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;permalink_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;playback_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;purchase_title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;purchase_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;release_day&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;release_month&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;release_year&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;reposts_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;secret_uri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;sharing&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;stream_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;streamable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tag_list&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; User&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;user_favorite&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Boolean&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;user_playback_count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Int&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;waveform_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Deprecated Fields&lt;/h2&gt;
&lt;p&gt;In the next couple weeks, we’re planning on removing the following deprecated fields from the response. These fields are either superfluous, outdated, or not in use by developers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;downloads_remaining&lt;/li&gt;
&lt;li&gt;domain_lockings&lt;/li&gt;
&lt;li&gt;embeddable_by&lt;/li&gt;
&lt;li&gt;label&lt;/li&gt;
&lt;li&gt;label_id&lt;/li&gt;
&lt;li&gt;last_modified&lt;/li&gt;
&lt;li&gt;permalink&lt;/li&gt;
&lt;li&gt;original&lt;em&gt;content&lt;/em&gt;size&lt;/li&gt;
&lt;li&gt;original_format&lt;/li&gt;
&lt;li&gt;secret_token&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;track_type&lt;/li&gt;
&lt;li&gt;user_id&lt;/li&gt;
&lt;li&gt;user_uri&lt;/li&gt;
&lt;li&gt;video_url&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your application is using the &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; object and/or deprecated fields, please update your API in a timely manner. If you deem any of the above fields necessary for your use case and want them to be preserved, please let us know by filing a feature request on our &lt;a href=&quot;https://github.com/soundcloud/api&quot;&gt;issue tracker&lt;/a&gt; by &lt;strong&gt;31 March 2021&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For future updates, please follow us on &lt;a href=&quot;https://twitter.com/soundclouddev&quot;&gt;Twitter&lt;/a&gt; and our &lt;a href=&quot;https://developers.soundcloud.com/blog/&quot;&gt;blog&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building a Healthy On-Call Culture]]></title><description><![CDATA[Paging Doctor Software In the past, on-call duty was often associated with doctors, but in recent years, it’s become common for software…]]></description><link>https://developers.soundcloud.com/blog/building-a-healthy-on-call-culture</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-a-healthy-on-call-culture</guid><pubDate>Thu, 11 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Paging Doctor Software&lt;/h2&gt;
&lt;p&gt;In the past, on-call duty was often associated with doctors, but in recent years, it’s become common for software engineers to be asked to be available for support work on short notice. As software has grown to power so much of the world, the need for high availability and rapid incident response has likewise grown.&lt;/p&gt;
&lt;p&gt;Another driver of the popularity of on-call work is the ubiquity of smartphones. The days of bulky, unreliable pagers are gone. Now when automated software monitoring detects a system anomaly, an on-call engineer gets an alert in the form of a phone call, a text, or a loud noise from a mobile app.&lt;/p&gt;
&lt;h2&gt;On-Call Work at SoundCloud&lt;/h2&gt;
&lt;p&gt;First and foremost, on-call duty for SoundCloud engineers is optional. We believe this is important to our engineering culture for reasons I’ll discuss below. Secondly, on-call duty outside of normal office hours is compensated at an hourly rate, with additional hourly payments when responding to pages.&lt;/p&gt;
&lt;p&gt;On-call engineers are organized into rotations. Each rotation consists of a group of engineers representing a team or, more typically, several teams. At any time, there’s always one engineer on call for the rotation. That engineer is expected to provide first-level support for the systems belonging to all the teams in the rotation.&lt;/p&gt;
&lt;p&gt;Additionally, every engineer in the rotation is always on call to provide second-level support for the systems belonging to that engineer’s team. Second-level support is on a best-efforts basis, meaning that while engineers can receive a second-level page at any time, they’re not required to answer if they’re unavailable to help for any reason.&lt;/p&gt;
&lt;h2&gt;Why On-Call Work Is Good for Engineers&lt;/h2&gt;
&lt;p&gt;Having a wide range of engineers, and not just DevOps and Site Reliability Engineers, on call has a number of benefits both for the company and for the engineers themselves.&lt;/p&gt;
&lt;p&gt;Perhaps most obviously, it lightens the burden on the operational engineers, who often have substantial out-of-hours support commitments as part of their core job descriptions.&lt;/p&gt;
&lt;p&gt;It also equips and motivates engineers to build reliable, well-documented systems. Seeing firsthand how things go wrong in production powers insights into how systems can be improved and made more robust.&lt;/p&gt;
&lt;p&gt;And finally, supporting both their own and others’ systems is a great learning opportunity for engineers. It provides valuable hands-on experience with infrastructure such as databases, as well as experience diagnosing faults and making operational decisions.&lt;/p&gt;
&lt;h2&gt;Procedural Best Practices&lt;/h2&gt;
&lt;p&gt;Every engineering organization is different, but through trial and error we’ve found the following practices work well for SoundCloud.&lt;/p&gt;
&lt;p&gt;Different rotations have different shift cadences, but most shifts last only one or two days.&lt;/p&gt;
&lt;p&gt;The optimal frequency for being on call is about three days a month. More than that and people risk burning out over time. Less than that and people get rusty and aren’t as effective at dealing with incidents. This means the optimal size for a rotation is between eight and twelve engineers, with ten being just about perfect. In fact, I was once part of a rotation that had a waitlist to join because we collectively agreed to not grow bigger than twelve people.&lt;/p&gt;
&lt;p&gt;Most rotations have a formal or informal rotation administrator drawn from the engineers in the rotation. The administrator maintains the shift schedule, deals with personnel changes, and performs other ad hoc tasks that assure the rotation’s health. For example, in many rotations, the administrator organizes a meeting to set the shift schedule for the holiday period. Deciding together how to cover times of low engineer availability has proved the fairest and least stressful way of handling these situations.&lt;/p&gt;
&lt;h2&gt;A Word about Rotations and Teams&lt;/h2&gt;
&lt;p&gt;Most SoundCloud on-call rotations started as groups of engineers from related teams in the same area of the engineering organization. But, as is the case with most engineering organizations, SoundCloud’s has evolved over time. Teams have merged or split, new teams have been created, teams have been moved into different divisions, and so on. However, the on-call rotations have generally not evolved at the same pace as the engineering organization. This means the rotation structure has come to bear less and less resemblance to the wider organization structure, to the point where many rotations now represent what seem like random groups of unrelated teams.&lt;/p&gt;
&lt;p&gt;On the whole, this has not proved to be a problem. Furthermore, attempts to reorganize the rotations to match the current company structure have usually stalled due to objections from the on-call engineers. These engineers have, in some cases, supported the systems in their rotations for years and acquired deep knowledge of those systems. Change for the sake of matching the current organizational chart represents a significant upheaval for a comparatively modest benefit. (And when the organizational chart changes again, the upheaval repeats itself.)&lt;/p&gt;
&lt;h2&gt;Cultural Best Practices&lt;/h2&gt;
&lt;p&gt;We try hard to foster the following norms and attitudes for the benefit of the on-call engineers, and by extension, the company as a whole.&lt;/p&gt;
&lt;p&gt;The people who are on call want to be on call. Engineers who’ve freely taken on the obligation (and are being compensated for it) are more motivated when responding to incidents. Having experienced workplaces where on-call work was mandatory or close to mandatory, I can personally attest to the positive atmosphere engendered by a voluntary on-call policy.&lt;/p&gt;
&lt;p&gt;Practical matters like shift cadences are decided collectively by the engineers in the rotation. The rotation administrator leads the decision-making process but does not act unilaterally. This is why not all of SoundCloud’s rotations have the same shift patterns, shift handover times, procedures for trading or giving away shifts, and so on. Each rotation is free to do what works best for its members.&lt;/p&gt;
&lt;p&gt;On-call engineers often spend some of their normal working day looking into minor operational issues to prevent these issues worsening and having to page someone out of hours.&lt;/p&gt;
&lt;p&gt;When responding to incidents, engineers can always ask for help by paging other engineers. This is something we stress multiple times during on-call onboarding. It’s a cornerstone of a healthy, high-trust on-call culture. Nobody enjoys getting a second-level support page in the middle of the night, but responding (if possible) is an important act of empathy for the engineer who needs help. It’s also an investment in training them to handle the situation autonomously in the future.&lt;/p&gt;
&lt;p&gt;In addition to feeling free to ask for help, engineers also feel free to hand work over to others after a reasonable length of time. For major or long-running incidents, we encourage engineers to hand over after four hours — or sooner if they become too tired to work effectively.&lt;/p&gt;
&lt;p&gt;The most important cultural practice — which influences all the other cultural practices I’ve discussed — is fostering a learning culture rather than a blame culture. This truly can’t be emphasized enough. Mistakes are an inevitable part of incident response. Learning from mistakes builds a stronger, more technically proficient engineering organization. Punishing people for mistakes makes engineers afraid to act in new situations, afraid to ask for help when they need it, and afraid to be transparent. Ultimately, engineers will choose to leave the on-call rotations or even the company if there’s a culture of blame.&lt;/p&gt;
&lt;h2&gt;When a Major Incident Happens&lt;/h2&gt;
&lt;p&gt;Responding to a total site outage or other severe incident is stressful for all involved. It’s also a stress test for a company’s on-call culture. It’s more important than ever for engineers to work well together and trust each other. They’ll resolve the incident faster if they feel comfortable admitting what they don’t know, asking for help from others, being honest if they make mistakes, and speaking up if they’re too tired to carry on.&lt;/p&gt;
&lt;p&gt;The time to foster these behaviors is before a major incident occurs. Engineers learn them from experience, by responding to smaller incidents and interacting with their colleagues. In this sense, smaller incidents are essentially practice for larger incidents.&lt;/p&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Underpinning all the practices and behaviors I’ve described in this article is one thing: respect. Companies that respect their engineers institute policies that enable those engineers to do their best work. Engineers who respect each other help each other succeed, and in turn, help the company succeed. This applies to on-call rotations as well as the wider engineering culture. When mutual respect exists, everyone wins.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dependency Inversion as a Driver to Scale Mobile Development]]></title><description><![CDATA[SoundCloud’s iOS codebase faced a radical change a couple of years ago. The team had decided to modularize the codebase into frameworks…]]></description><link>https://developers.soundcloud.com/blog/dependency-inversion-as-a-driver-to-scale-mobile-development</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/dependency-inversion-as-a-driver-to-scale-mobile-development</guid><pubDate>Wed, 24 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud’s iOS codebase faced a radical change a couple of years ago. The team had decided to modularize the codebase into frameworks, mainly to speed up development and give it a shape where it scales as new contributors join. The reasoning and roadmap of this transformation are explained in our &lt;a href=&quot;https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1&quot;&gt;Leveraging Frameworks to Speed Up Our Development on iOS — Part 1&lt;/a&gt; blog post.&lt;/p&gt;
&lt;p&gt;In this move toward modularized architecture, the team has benefited directly from faster development every day. However, when making architectural decisions on such a large scale, tradeoffs are almost always inevitable. Ours was dependency management. Without supervision of dependencies, dependency graphs end up being convoluted. The famous &lt;a href=&quot;https://en.wikipedia.org/wiki/Spaghetti_code#:~:text=Spaghetti%20code%20is%20a%20pejorative,and%20insufficient%20ability%20or%20experience&quot;&gt;Spaghetti Code&lt;/a&gt; analogy stands out as one of the outcomes of these kinds of graphs.&lt;/p&gt;
&lt;p&gt;Restricting dependencies is an excellent start toward loosely coupled frameworks. Modules — frameworks in our case — need other modules for interoperability. This relationship can be cleaned up by providing an API of a module as needed instead of exposing the entire module. This technique is called dependency inversion.&lt;/p&gt;
&lt;p&gt;The dependency inversion principle (DIP) is the D in the widely regarded &lt;a href=&quot;https://en.wikipedia.org/wiki/SOLID&quot;&gt;SOLID principles&lt;/a&gt;. We’ll begin with a quick definition of the concept. A Swift example will follow to show how the concept works in real-life examples. At the end, we’ll explain how we’re using DIP among frameworks in our modularized architecture.&lt;/p&gt;
&lt;h2&gt;The Dependency Inversion Principle&lt;/h2&gt;
&lt;p&gt;The dependency inversion principle suggests that a module shouldn’t directly depend on another module. Direct dependency leads to a tight coupling between high-level (consumer) and low-level (consumed) modules. With tightly coupled modules, chances are that a high-level module needs to reflect every alteration in the low-level module. Autonomous modules are better for maintainability, as we already know from microservice architectures. High- to low-level dependency looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModuleA&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//High Level&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; foo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ModuleB&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//Low Level&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Loosening the connection is key, but modules are meant to work together, so we still need a connection. The principle solves the problem with a supervisor contract that has the rules of the connection. It says modules should depend on the contract instead of each other. This contract determines what goes out of the higher module and what goes into the lower module, and it guarantees both modules that the communication will be done according to its terms. So, the initial dependency is broken into two and reverted to the contract. This is what we call dependency inversion.&lt;/p&gt;
&lt;h2&gt;A Swift Example of DIP&lt;/h2&gt;
&lt;p&gt;The best way to understand is by applying the principle to a real-life example. Imagine we have a module called &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt;. This module is responsible for logical decisions about what to show on our screen, and it’s the standard architectural component in most modern mobile development.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt; is our high-level module, or the consumer module. It needs a view or views to present what it needs to present. &lt;code class=&quot;language-text&quot;&gt;ProfileViewController&lt;/code&gt; (the low-level module) is our view layer for visualizing profiles under the service of the &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt;. Let’s see how this looks without DIP:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfileViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; fullName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfilePresenter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;weak&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; profileView&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewController&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;profileView&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profileView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; profileView
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;presentProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fullName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;surname
        profileView&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Profile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; surname&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; artworkUrl&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt; receives the full profile entity, extracts the full name, and commands the view to show it. It’s the responsibility of the &lt;code class=&quot;language-text&quot;&gt;ProfileViewController&lt;/code&gt; to show the full name. In other words, a unidirectional flow.&lt;/p&gt;
&lt;p&gt;What’s the problem here? Using concrete types seems straightforward. The &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt; knows which component it’s talking to, so there are no surprises. But this is what we call an overly attached relationship or a tightly coupled dependency: We can’t easily separate the low-level module from the high-level module right now, in spite of this example being basic.&lt;/p&gt;
&lt;p&gt;If we need an entirely different profile layout for our premium users (which we’d accomplish by injecting different views for different subscription models), and if we need to create a new view for it, we cannot easily replace the current view with the new premium user view.&lt;/p&gt;
&lt;p&gt;Currently, our dependency relationship looks like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 560px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/68c8c788d4e7e85fa67fccb3a62da0fa/02744/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.392857142857142%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABKElEQVQY042Rz0oCcRSFf9FeqJdpYRC0qSDaBW5b9gRuypUhQateoMBNRVhEmpQFKo1mRTsZazKVaqaphTqj5r/5mhlr0UovfPdeLocDhysYUpZl2Tizz9CyNUKvVbh82iVZCrukymGuijtIpQjdXudP6faGnsJ4DNB4DmEqG7+E3JupXbgakS0fsy5NsZaYwR/z4o96CaSn2bpd4lUvYdRNTLNGsw1ywoeyL5APxlEOBcWI4OXI3vcE5fQyPcfwrhIjmJ1lM7NIMDlvs0DoZo7tex9fVZVup0e73cQJ/JZbRYuOocYn0OIee3r4OJ9EPRVouZWBodGqklclZD1DweEzS167RtEf6Petf5G/60XM9xOa2hkNNTbA2e1bq1oYRB79KRajaH8AxxKmm/PvK8AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Direct Dependency&quot;
        title=&quot;Direct Dependency&quot;
        src=&quot;/blog/static/68c8c788d4e7e85fa67fccb3a62da0fa/02744/1.png&quot;
        srcset=&quot;/blog/static/68c8c788d4e7e85fa67fccb3a62da0fa/9ec3c/1.png 200w,
/blog/static/68c8c788d4e7e85fa67fccb3a62da0fa/c7805/1.png 400w,
/blog/static/68c8c788d4e7e85fa67fccb3a62da0fa/02744/1.png 560w&quot;
        sizes=&quot;(max-width: 560px) 100vw, 560px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So the dependency is from &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;ProfileViewController&lt;/code&gt;. DIP says that this dependency needs to be broken and inverted — not all the way back to &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt;, but as a new contract between these two, i.e. an abstraction. So we break the current dependency into two weaker dependencies and point them to the new abstraction:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 535px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/085d46a254acc7e67798c67da6a6107d/fd7a0/2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.8411214953271%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABz0lEQVQoz4WST2vTYBzHc3ATdC/Aky9BBIfgYYgvYBf/4KHsVXiSoYeyy+pRRWUInlTWOca6FoaCMMuwQyc4Wd0stkubxrK2yZpkSZomH5+kUWdX8Qtfvs/hx+f3PN9EYkBBEMQJvZ4r8m97nsiB2aOSjsP8CNbeX6Kp3EApTyGXEiITqJUpNPUmWisfz/vHoEOBpgVq9QF6/Rzl4kW+bV2gtDVO8dM4mnKeenUe2yFePggM+qBwmx9t9Ol2e+i6jGWsY1sFnMP3kV27QEdb40BXCPxw1hPuHXGANKyHUO2WIToc6Fe4qYlu+beiJxu2Tsusox3+oG2pIhs0NJmDTjsa8n0vSkNX2S+9wFYXMZVXscW5No/ZeBt1L3mex7PNW8yuT5J6d41U/ir38tdJbUyytpOOrhM+LZSyu8Le0gnkzBi1ldMo2VPUc2Oo2RHK2bN4roHkuA73PyZIFiaYXr3M7dwEd99cIbl5idWvc/ENu1E2Kzkqi5KAnUReHqWWGRHnUZSMxPflM3TdDpLvB7zeecrLL3dYKM6Q3k6ysD3D88/T7DY+9IFBvzXPadEpz2HtPRJ+gll5LCyy/BCjmiZkSfxHf3704PeHGeZf+gnCQdZeF5R3qgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Inverted Dependency&quot;
        title=&quot;Inverted Dependency&quot;
        src=&quot;/blog/static/085d46a254acc7e67798c67da6a6107d/fd7a0/2.png&quot;
        srcset=&quot;/blog/static/085d46a254acc7e67798c67da6a6107d/9ec3c/2.png 200w,
/blog/static/085d46a254acc7e67798c67da6a6107d/c7805/2.png 400w,
/blog/static/085d46a254acc7e67798c67da6a6107d/fd7a0/2.png 535w&quot;
        sizes=&quot;(max-width: 535px) 100vw, 535px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ProfileViewing&lt;/code&gt; is the contract that enforces the relationship rules of both parties. What &lt;code class=&quot;language-text&quot;&gt;ProfileViewing&lt;/code&gt; does not guarantee is the concrete types of both modules. This is intended by the principle, in order to have flexibility:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; fullName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfileViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; fullName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfilePresenter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;weak&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; profileView&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewing&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;profileView&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProfileViewing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profileView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; profileView
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;presentProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fullName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;surname
        profileView&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not much has changed, actually. There’s now a contract visible to both modules to break the unhealthy relationship. If we have a new profile view — something like &lt;code class=&quot;language-text&quot;&gt;PremiumProfileViewController&lt;/code&gt; — we need to conform to &lt;code class=&quot;language-text&quot;&gt;ProfileViewing&lt;/code&gt;. We can then hook it up to &lt;code class=&quot;language-text&quot;&gt;ProfilePresenter&lt;/code&gt; with no additional work, and it would work just like the original profile view.&lt;/p&gt;
&lt;p&gt;The above example is a standard use of DIP for Swift. The initial behavior of creating relationships between modules should follow this approach. There may be exceptions, but it’s good to start with the DIP approach and tighten the relationship if needed. Direct dependency is tempting because it’s straightforward, whereas we create a lot of boilerplate code to have DIP. And it might seem like it requires a lot of work to take the DIP route, but we’re actually saving some refactoring time in the long term.&lt;/p&gt;
&lt;h2&gt;DIP for Framework Dependencies&lt;/h2&gt;
&lt;p&gt;Now that we understand the mechanics of DIP, let’s see how we can use it on the level of frameworks.&lt;/p&gt;
&lt;p&gt;If you’ve been modularizing your project into frameworks, you probably had issues with the dependencies among them. When the number of frameworks in a project increases, their dependency on each other becomes complicated, and it introduces problems ranging from broken syntax highlighting to longer compile times. By inverting some dependencies, we can save ourselves from this hectic dependency graph.&lt;/p&gt;
&lt;p&gt;The problem is similar to the previous example. Framework-A needs a piece of Framework-B, but Framework-A doesn’t want to depend on the entirety of Framework-B. Who can blame Framework-A, right? Instead, Framework-A can be given the exact piece it needs from Framework-B: an injection per se.&lt;/p&gt;
&lt;p&gt;Again, the two modules don’t like their direct dependency, so they need a lightweight, more manageable contract. Since we’re working on the framework level, the contract should be placed in its own framework. This new framework holds the contract and a minimum number of pieces that the contract needs. It’s important to keep this framework thin because other frameworks will depend on it. Since certain pieces need to be injected into the framework, and since the main app target is the one injecting, the main app also needs to depend on the new contract framework:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/e199c/3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82.78560250391236%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACYElEQVQ4y41Ty04UURCdJWsNcaGfMJIIKmrED4CFQQ1R4w+4M8TEBzOw1A2CutAdERITTNhoTNy4VVETjcYHMGBUpmeYR093335Md9/bx/voHseZBqnkpDpV556uW7cqg8SiKHbC020RxTzORJpl0JaOWqIAY/x4B0RccDqRFCLwVzBOMEbhWotokvsI3YcInQeg3PvkHo8/QZRSWLtolyClIXzzIkKjDxtfDqG80Y+f3/tBzT40jUvw/RBhqMD4NXZRYQivcQXUGoFdHYVTOw1raxShNQy3Mc5/qESEmBANgoDH6L8VKnVFjDgx8Ovw3BI8R4NNfnNflN+Br6sfJ4hNCCeimSilKSLVsFx4gcq5TQqDuGDYvn+EELiuqypkgQ9KTES2hcDQ4TfqYJYB5lignoOIe0YMGRd5cJ7gM37dxHzfl6JSkLxYQnkkC23sODbPHEXx3DFo3FcuDKEyO4XK+ZPQzor4oMxrYydQGs6CvHyqqqRqPsW1paCzNIfywR4UBvdh5Ugvvh3uxfrAHtRPHUBt8jKqQ/uxNrAXqzwnwXmlbA+cZ49jwbD1SErw4xsYt6/Cns2D3JmAPZODMzMB8+4U9OeLMEV8+gYsDnP6OufloN8ah/v5PeLRaBMULytiMcS3FTJ4KQ8gOuawrldROd5POTZRa9eoAh+fkDfY0HXZG8czUaytoVgt4EfxKwq/PkGrrWOzugrbM+RRISYqbO1y+m4yWe565QPm393Eo1d5jhwWlvOYfz2JueVrWNl6KzmU0e7V69zJtPncyRJ+l2Cn6G6RWuFO1f5PKLE/PtzxByiY4hQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Framework Architecture&quot;
        title=&quot;Framework Architecture&quot;
        src=&quot;/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/8ff1e/3.png&quot;
        srcset=&quot;/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/9ec3c/3.png 200w,
/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/c7805/3.png 400w,
/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/8ff1e/3.png 800w,
/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/6ff5e/3.png 1200w,
/blog/static/e3ce439986321e5a13f1bf7c0f871fa0/e199c/3.png 1278w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To visualize this, let’s say we have two frameworks named &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt;, each of which contains code associated with its name. &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt; needs an authenticator when the user wants to make the payment. We have a class named &lt;code class=&quot;language-text&quot;&gt;Authenticator&lt;/code&gt;, located in the &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt; framework, exactly for this purpose:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Authenticator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt; framework needs this piece in its &lt;code class=&quot;language-text&quot;&gt;PaymentManager&lt;/code&gt; class:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentManager&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; authenticator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticator&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authenticator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authenticator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authenticator
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        authenticator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So far, it looks almost exactly like the previous example. We learned from the previous example that we shouldn’t use the concrete &lt;code class=&quot;language-text&quot;&gt;Authenticator&lt;/code&gt; type; we need to invert this dependency to a contract. So we need a new framework called &lt;code class=&quot;language-text&quot;&gt;AuthenticationContract&lt;/code&gt; to put the protocol in. The &lt;code class=&quot;language-text&quot;&gt;AuthenticationContract&lt;/code&gt; framework needs to be imported to &lt;code class=&quot;language-text&quot;&gt;Payments&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt;, and the &lt;code class=&quot;language-text&quot;&gt;Main App&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticating&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AuthenticationContract&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentManager&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; authenticator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticating&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authenticator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticating&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authenticator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authenticator
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        authenticator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AuthenticationContract&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Authenticator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authenticating&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So dependencies are inverted to a new protocol called &lt;code class=&quot;language-text&quot;&gt;Authenticating&lt;/code&gt;, and it’s safe to import &lt;code class=&quot;language-text&quot;&gt;AuthenticationContract&lt;/code&gt; to both frameworks. One last thing to do here is to give &lt;code class=&quot;language-text&quot;&gt;Authenticator&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;PaymentManager&lt;/code&gt;, which will be done in the &lt;code class=&quot;language-text&quot;&gt;Main App&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AuthenticationContract&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Payment&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Authentication&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; authenticator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Authenticator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; paymentManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PaymentManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authenticator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; authenticator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Briefly, &lt;code class=&quot;language-text&quot;&gt;PaymentManager&lt;/code&gt; needed a piece of the &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt; framework, the &lt;code class=&quot;language-text&quot;&gt;Authenticator&lt;/code&gt;. Instead of creating a dependency from the &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt; framework to the &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt; framework, we inverted this dependency to the new &lt;code class=&quot;language-text&quot;&gt;AuthenticationContract&lt;/code&gt; framework, which is way more lightweight than &lt;code class=&quot;language-text&quot;&gt;Authentication&lt;/code&gt;. We saved ourselves from the tightly coupled relationship between two rather heavy frameworks.&lt;/p&gt;
&lt;p&gt;This process can be replicated for every framework that needs to serve others. For example, a framework named &lt;code class=&quot;language-text&quot;&gt;Subscriptions&lt;/code&gt; needs some parts from &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt;, and then a contract framework named &lt;code class=&quot;language-text&quot;&gt;PaymentContract&lt;/code&gt; can be created for &lt;code class=&quot;language-text&quot;&gt;Payment&lt;/code&gt;. Ultimately, frameworks come with their own companion contract frameworks.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 689px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/77d131f9dabb310148df83236849857e/5d21f/4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.46298984034833%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABOElEQVQY063MPUtCYRjG8T5Ek+FinyAQhF6WgsTBoMYoKCNrt7U1+gzBEcuUWioyzXwhwsGhcAvCo8fj21BHLSsMknP+PUcDpbkLrmf4PTfXCCK6rmN0v2Goerf722EbOH/cEBtmRsznM3lBfXWWmneButdNbW0ebXeb5v4OjXWncGGbbpobLhp7PjTfCjWPq38r+uJx0sllB4OdU4naxCilmXHK0zZUu4Xm8hztrSXeHBZKpk3ZeJ+08updpOW2U3ZYe6aKv5ZjjK+7m8Fgq/KIHD+glD5ESQcoJiTU7DnVXAw5KVFMBVBEC2k/lVwUJXOCkvT3zLyXUxIfWnkwqLafuMwHiMlhYoUwEfmITDXCg5YkpgSFh4SHiKsh7rUEt5UzrvJBoj0Pc1065rlT7Q8ahsF/xdz6AcLbkiTQTRXTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Distributed Framework Architecture&quot;
        title=&quot;Distributed Framework Architecture&quot;
        src=&quot;/blog/static/77d131f9dabb310148df83236849857e/5d21f/4.png&quot;
        srcset=&quot;/blog/static/77d131f9dabb310148df83236849857e/9ec3c/4.png 200w,
/blog/static/77d131f9dabb310148df83236849857e/c7805/4.png 400w,
/blog/static/77d131f9dabb310148df83236849857e/5d21f/4.png 689w&quot;
        sizes=&quot;(max-width: 689px) 100vw, 689px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;So What?&lt;/h2&gt;
&lt;p&gt;When building software on any scale, individual modules need to work in harmony; hence we need dependencies. The dependency inversion principle doesn’t remove the dependency need for modules; they still need to work together. Rather, it sets up contracts that regulate the traffic between modules while assuring them that their internal changes won’t affect the other modules.&lt;/p&gt;
&lt;p&gt;Inverted dependencies increase the chance that changes won’t be propagated into our software. Even if we want to replace a module whose dependency is inverted entirely, a new module assembly and integration will be way easier than a tightly coupled version. This is a good practice to lighten the burden of dependencies, but of course it’s not the only one!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Tests Under the Magnifying Lens]]></title><description><![CDATA[Testing is at the heart of engineering practices at SoundCloud. We strive to build well-balanced test pyramids within our code repositories…]]></description><link>https://developers.soundcloud.com/blog/tests-under-the-magnifying-lens</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/tests-under-the-magnifying-lens</guid><pubDate>Tue, 02 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Testing is at the heart of engineering practices at SoundCloud. We strive to build well-balanced test pyramids within our code repositories and have as much test coverage as possible for different service use cases.&lt;/p&gt;
&lt;p&gt;Here’s an x-ray view of the test pyramid in one of our largest codebases, the Android application:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/1e33e5f44812901930b934ab63b29dbd/Test-Pyramid.svg&quot; alt=&quot;Android Test Pyramid&quot;&gt;&lt;/p&gt;
&lt;p&gt;As test suites grow and, more interestingly, cover more complicated integration cases, engineers have had difficulties when answering questions about the value, validity, and correctness of their tests. Some of the main challenges in building and testing at SoundCloud are pipeline-waiting times and test flakiness, respectively.&lt;/p&gt;
&lt;h2&gt;Data First&lt;/h2&gt;
&lt;center&gt;&lt;img alt=&quot;xkcd mandatory comic strip&quot; height=&quot;280&quot; src=&quot;https://imgs.xkcd.com/comics/compiling.png&quot;&gt;&lt;/center&gt;
&lt;p&gt;Test flakiness impacts teams in different technology stacks, both frontend and backend. In parallel, a challenge we commonly face is weighing &lt;em&gt;tech health&lt;/em&gt; work against feature work to help product and engineering teams prioritize their work iterations.&lt;/p&gt;
&lt;p&gt;As one of the efforts in leveling up our testing infrastructure — inspired by our peers in the industry (e.g. &lt;a href=&quot;https://engineering.atspotify.com/2019/11/18/test-flakiness-methods-for-identifying-and-dealing-with-flaky-tests/&quot;&gt;Spotify&lt;/a&gt;, &lt;a href=&quot;https://github.blog/2020-12-16-reducing-flaky-builds-by-18x/&quot;&gt;GitHub&lt;/a&gt;, &lt;a href=&quot;https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html&quot;&gt;Google&lt;/a&gt;, &lt;a href=&quot;https://martinfowler.com/articles/nonDeterminism.html&quot;&gt;Fowler&lt;/a&gt;, and &lt;a href=&quot;https://www.cypress.io/blog/2020/10/20/introducing-flaky-test-detection-alerts/&quot;&gt;Cypress&lt;/a&gt;) — we identified a potential improvement to our tooling, and we came up with a path to enabling cross-repository, cross-team, and cross-feature data aggregation and analysis that could potentially help us solve the aforementioned challenges by informing engineers and other non-technical stakeholders.&lt;/p&gt;
&lt;p&gt;To do that, we must flip a common engineering view that test reports are outputs of the build process, and instead &lt;strong&gt;look at build artifacts as inputs to a next processing step&lt;/strong&gt; in the engineering workflow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/62198e6a756fc02916d04ad6829f1c06/pipeline-sketch.svg&quot; alt=&quot;Collecting test reports as data points&quot;&gt;&lt;/p&gt;
&lt;p&gt;In other words, every test report becomes a data point to help draw a curve that will support driving insights from the build pipelines themselves; track the test suites’ health, retries, and execution times; and more interestingly, help predict or bias an expected movement.&lt;/p&gt;
&lt;p&gt;Finally, we aim to help prioritize refactoring or fixing tests so that they speed up test pipelines to reduce running cost and developer waiting times.&lt;/p&gt;
&lt;h2&gt;Backstage&lt;/h2&gt;
&lt;p&gt;With these targets in mind, we’ve come up with a service architecture backed by a data storage. For our requirements, all we need is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Create,_read,_update_and_delete&quot;&gt;CRUD&lt;/a&gt; for data ingestion and manipulation, with some extended capabilities for rendering.&lt;/p&gt;
&lt;h3&gt;Ingestion&lt;/h3&gt;
&lt;p&gt;Given the service is aimed at integration with pretty much any stack, framework, or programming language — so as to attain wide internal adoption — we picked an agnostic scheme and designed an interface with as few assumptions about the data format as possible.&lt;/p&gt;
&lt;p&gt;The first implementation for the tool is a thin Scala application with a &lt;a href=&quot;https://twitter.github.io/finagle/&quot;&gt;Finagle+MySQL&lt;/a&gt; setup that ingests data via a REST API. It’s similar in skeleton to our production microservices, which means we leverage a lot of our existing toolkit for building and deploying the service, while also empowering engineers in the organization to contribute to improving the system by collaborating on a stack they already have experience with.&lt;/p&gt;
&lt;p&gt;To minimize the effort of integration even further, we also provided a command-line tool, distributed through our private Docker registry, that knows how to translate some common test report formats (such as &lt;a href=&quot;https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html&quot;&gt;JUnit&lt;/a&gt;) into the expected schema for the service’s API.&lt;/p&gt;
&lt;p&gt;That covers most of our use cases, since many of the testing libraries we rely on — such as the ones we run for our Android and pure Java applications — are capable of generating these XML reports. For other codebases, with &lt;a href=&quot;https://www.scalatest.org/user_guide/using_the_runner&quot;&gt;ScalaTest&lt;/a&gt;, &lt;a href=&quot;https://github.com/sj26/rspec_junit_formatter&quot;&gt;RSpec&lt;/a&gt;, and even &lt;a href=&quot;https://github.com/fastlane-community/trainer&quot;&gt;Xcode&lt;/a&gt;, we rely on third-party and open-source integrations.&lt;/p&gt;
&lt;p&gt;In practical terms, most teams simply add a step to their build pipelines, specifying a few parameters for the command-line uploader. Here’s an example from one of our &lt;a href=&quot;https://www.gocd.org/&quot;&gt;GoCD pipelines&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;acceptance-test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
 &lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;acceptance-test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; make acceptance&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;test
   &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;run_if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; any
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;docker run&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; automated&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;monitor&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;uploader \
     &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;system=api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;mobile \
     &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;reports_dir=target/test&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;reports&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At the end of the day, each of the teams can opt for a “vanilla” integration of their system by using the Docker image, but they can also choose to write their own &lt;em&gt;report-uploading script&lt;/em&gt; to maximize the information they store in the database for querying later. This has even greater potential to allow teams to group and slice data through test cases’ metadata — for example, our Web Collective might tag their tests according to the browser they were run on, while our iOS team can use the device/OS version metadata for their use case.&lt;/p&gt;
&lt;h3&gt;Rendering&lt;/h3&gt;
&lt;p&gt;Once the data is stored in a structured way, the next step is to provide rendering capabilities so that teams can follow trends and the evolution of their datasets.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d3d26f6609daf5caff1aa948b5827564/bd828/test-count-rate.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.030698388334613%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA5ElEQVQY0yWPu3KDMBREqWwjgUAgISEDQjZ+jVOlTZX//6gTmRRn9hY7d3cLNwXs6ChPkhhXttsDpXsWOxKtR6qWWjVUH+p/HXzADB4h6x2pNJW2O4X2PY3RHEWDDZElJuxgsbIiNB2ttbjJ42aPX8b9HqaBtu94vt5crnesC8zrFRdmCiFqjqeK92r5/XJIIZieP/jrN4dSYJxj2SbSYyZcJnwMu/Y5dMwP/HhGd4be5BBtKGSlcm3F2TZsQaPypHZIufUZISp6O3B7Jl7vxLqtLJdIuiU6YzgcBaWQu6/MfOb/AeyyZFTqvUPQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test count rate&quot;
        title=&quot;test count rate&quot;
        src=&quot;/blog/static/d3d26f6609daf5caff1aa948b5827564/8ff1e/test-count-rate.png&quot;
        srcset=&quot;/blog/static/d3d26f6609daf5caff1aa948b5827564/9ec3c/test-count-rate.png 200w,
/blog/static/d3d26f6609daf5caff1aa948b5827564/c7805/test-count-rate.png 400w,
/blog/static/d3d26f6609daf5caff1aa948b5827564/8ff1e/test-count-rate.png 800w,
/blog/static/d3d26f6609daf5caff1aa948b5827564/6ff5e/test-count-rate.png 1200w,
/blog/static/d3d26f6609daf5caff1aa948b5827564/2f950/test-count-rate.png 1600w,
/blog/static/d3d26f6609daf5caff1aa948b5827564/bd828/test-count-rate.png 2606w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Here, we rely on &lt;a href=&quot;https://grafana.com/grafana/plugins/mysql&quot;&gt;first-party&lt;/a&gt; and &lt;a href=&quot;https://grafana.com/grafana/plugins/simpod-json-datasource&quot;&gt;third-party&lt;/a&gt; Grafana data source plugins, with some generic prebaked metrics we believe are useful for most cases. The plugins talk back to the ingestion service, executing queries to manipulate the data. This includes the number of builds and a drill down of test case outcomes, as well as rankings for the flakiest tests or the ones that might represent a bottleneck for the entire build pipeline.&lt;/p&gt;
&lt;p&gt;Tests that take too long to run, on average, rapidly add up to decrease the overall engineer productivity by increasing waiting times in build queues and when running the suite locally. Not only that, but this slowdown potentially means higher costs for SoundCloud due to virtual/physical device time allocation in third-party device farms.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/884864f880bfb1609ced917abde03167/79fdd/rankings-bottleneck-flakes.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.36813922356091%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABIklEQVQY0zWQy2rDMBREvWz8tvWwZEm2ZSeOGwgE2pJSSKGrQNeF0k0WWfX/f2B6ZehK3NeZGUVcOYimBRcKtdBw04Leb8E19ZUFoz5ve7DGglPNpYKQGtp06PwIaVoYq2FUS/sOUTdsV0DNJJKsACd4WXFkRQ2pDNK8JAgduAF+2mOcZqjWIt7kmN9mvH6/4H4b8XUaYUuFqPcTtvMjOWzwsInRaIOqFoiTbBUJb6gtAQNs2i3kziFNCiyXBeefZ/zePT4OGk3CEPlph+VwhNKWgAlailIzgSTNKZpagYzEumFchY2j+EIizyos7wsutzOuV4dBMjhdIQqgECdANnG6HhdlvQJD79+hVOGv+jVBmAeH49OI0+cRfifRFBXtVPgD532EAxcXk2kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;rankings bottleneck flakes&quot;
        title=&quot;rankings bottleneck flakes&quot;
        src=&quot;/blog/static/884864f880bfb1609ced917abde03167/8ff1e/rankings-bottleneck-flakes.png&quot;
        srcset=&quot;/blog/static/884864f880bfb1609ced917abde03167/9ec3c/rankings-bottleneck-flakes.png 200w,
/blog/static/884864f880bfb1609ced917abde03167/c7805/rankings-bottleneck-flakes.png 400w,
/blog/static/884864f880bfb1609ced917abde03167/8ff1e/rankings-bottleneck-flakes.png 800w,
/blog/static/884864f880bfb1609ced917abde03167/6ff5e/rankings-bottleneck-flakes.png 1200w,
/blog/static/884864f880bfb1609ced917abde03167/2f950/rankings-bottleneck-flakes.png 1600w,
/blog/static/884864f880bfb1609ced917abde03167/79fdd/rankings-bottleneck-flakes.png 2988w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We found that &lt;a href=&quot;https://engineering.atspotify.com/2019/11/18/test-flakiness-methods-for-identifying-and-dealing-with-flaky-tests/&quot;&gt;Spotify’s Odeneye&lt;/a&gt; was a great way to identify both test flakiness and infrastructure problems, as the company itself describes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you see a scattering of orange dots this usually means test flakiness. If you see a solid column of failures this usually represents infrastructure problems such as network failures.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Leveraging the thin Scala application layer in front of the Grafana data source gives us extra power, allowing for further filtering and grouping to help understand outage scenarios before they hit developer productivity. On top of that, we have a better overview of the assumptions our code is making and where coverage might be lacking.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/83aa416b6c087372f14aafdc95b0a829/63048/odeneye-graph.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.39776951672862%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAACLElEQVQoz0XK608SAQAA8PsstAIU6TjL4WMpueFs+MUskw8cdycQHAjIQx6GiAY+wpYzUipxTASB46GwiQrcwckRr1ar9Z9ln9p+H3/AA76wlw+Cwkc8Lo/bK+Dxeu9z+dw+UND/cAAagECIzxewWPd6etjsf1j/sdgAODgCDgze4YieckYn+0Ri4fA4NDrRLxqDJDPghLRf9AQaFguHxniicc6IhDskvpvcEQnn8SgA78kdF3YnYQxFJ5PJ54dJFD6BtYRhK4kdRyVpQuaOIfIYas6YP8Vn4mfSFAGHo89SudkPe3OAOoFt/Fj1tV3+yosDBtlndFpS4WqaNhndDjV73MI9NTVexbyt5bWqwk/Lg03DdmXusINuJlBAn9IGfvn8He98yaJprNta2wrSbG1tLDV8MtJm6b4z3brx6pK3u3VEv07fwrm2A6lb1X/WdecrgDKKrrVc3vqbHfJzqBn72ooH6uEv3+Ohb6e7tfBJ98x1SyD0uaNbdNNxXyP2sZEOHkSikez7QBDQJTW7v7cDndVEearIvCrRymxGWi4iVyRKkFO15kKwprRT8H7bvkjhKhp/y7iPd2WFoDK0pQWUp4i34/Y3HJHr6Swznz/HstaXxTU8F1GeUtOFOpal5rIVabmOusoLekrrYexITbX4cwkndIAqjHmqzuUrE5KRGy9x+7UZzcOWksFSNKI52Fmy6As4eoE4SjZNXqUpLKzc2NQZxHyDG460fwGozdVSYkMuKAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;odeneye graph&quot;
        title=&quot;odeneye graph&quot;
        src=&quot;/blog/static/83aa416b6c087372f14aafdc95b0a829/8ff1e/odeneye-graph.png&quot;
        srcset=&quot;/blog/static/83aa416b6c087372f14aafdc95b0a829/9ec3c/odeneye-graph.png 200w,
/blog/static/83aa416b6c087372f14aafdc95b0a829/c7805/odeneye-graph.png 400w,
/blog/static/83aa416b6c087372f14aafdc95b0a829/8ff1e/odeneye-graph.png 800w,
/blog/static/83aa416b6c087372f14aafdc95b0a829/6ff5e/odeneye-graph.png 1200w,
/blog/static/83aa416b6c087372f14aafdc95b0a829/2f950/odeneye-graph.png 1600w,
/blog/static/83aa416b6c087372f14aafdc95b0a829/63048/odeneye-graph.png 2152w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, we also provide engineers, data scientists, managers, and other SoundClouders with direct access to our SQL entry point into the schema for a more “heads-down” approach, in which more flexibility allows for discoveries of team-specific metrics or even extraction of the whole dataset via dumping.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c73a047d5b4273c2008fa68b37f4bf66/cf854/flaky-subscription-expired.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.01492537313433%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABLElEQVQoz32R63KCMBCF+VtHRC65IAloBcUg44yObd+g7/9Ep5uAXKrtj2/Obnb3sAmevwrhB2sEUUhEEx3jdUh1i41/MZzFEZarAJ7vrxEmMWSRQuZzRK8bIs2f6x1yiIMwnBhaAy0HZI+N00n8H6tXhnaQ6RRtzvFdxCjy/gOq3+SF0WPLPw0FGWpqqnPhkErAGAPTtNDbAlyL8VkG06crz69rNSbjPQ3f9xxlXWFflfSeG6oJV2eZBKfN9a7bfL7hNqOfQM3FiCSY3uB4VDieMjRnRWicGk25RlUr7A7Kqe11houFD6EyHC4GVVujJKoHZ8qJ9nrC7bPB9eOBcfn9q3Ha3gzeqS/iHN7bYokoZmCMg3GBOJnEdJ4kFu56rCZMdDFpl3Oa6bDnP+yy6jOyy5ziAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;flaky subscription expired&quot;
        title=&quot;flaky subscription expired&quot;
        src=&quot;/blog/static/c73a047d5b4273c2008fa68b37f4bf66/8ff1e/flaky-subscription-expired.png&quot;
        srcset=&quot;/blog/static/c73a047d5b4273c2008fa68b37f4bf66/9ec3c/flaky-subscription-expired.png 200w,
/blog/static/c73a047d5b4273c2008fa68b37f4bf66/c7805/flaky-subscription-expired.png 400w,
/blog/static/c73a047d5b4273c2008fa68b37f4bf66/8ff1e/flaky-subscription-expired.png 800w,
/blog/static/c73a047d5b4273c2008fa68b37f4bf66/6ff5e/flaky-subscription-expired.png 1200w,
/blog/static/c73a047d5b4273c2008fa68b37f4bf66/2f950/flaky-subscription-expired.png 1600w,
/blog/static/c73a047d5b4273c2008fa68b37f4bf66/cf854/flaky-subscription-expired.png 2680w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Action&lt;/h2&gt;
&lt;p&gt;Having data is the first step to be able to act on it, and each team has different practices for how they deal with test coverage, flakiness, and failures.&lt;/p&gt;
&lt;p&gt;Some may visit the graph weekly to check on the trends, while others may only check when they experience build failures; some could create tasks in their ticketing system to regularly increase the codebase’s overall quality, while others might decide to set up alerts when specific metrics go above or below a certain threshold. The tool doesn’t enforce a given workflow, but it’s ready as support when teams decide to invest in technical health.&lt;/p&gt;
&lt;p&gt;Eventually, we want to make the tracker for test execution available in a holistic way, so that we can answer questions like “How many failures did we have across all of SoundCloud over the last two weeks?” or “How has the rate of flakiness moved across all of SoundCloud in the last year?”&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Leveraging a Manager Weekly Newsletter for Team Communication]]></title><description><![CDATA[I started my journey as an Engineering Manager at SoundCloud close to a year ago. This came after working as a Software Engineer for more…]]></description><link>https://developers.soundcloud.com/blog/manager-weekly-newsletter-for-team-communication</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/manager-weekly-newsletter-for-team-communication</guid><pubDate>Wed, 25 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I started my journey as an Engineering Manager at SoundCloud close to a year ago. This came after working as a Software Engineer for more than a decade, most of which I spent as an iOS Engineer. Since then, I’ve been trying to figure out my role and expand my knowledge on the subject. This entails constantly meeting with other peers, along with reading books I’ve found or that have been recommended to me about how to be a better people manager.&lt;/p&gt;
&lt;p&gt;One of these books is “&lt;a href=&quot;https://www.goodreads.com/en/book/show/38821039-the-making-of-a-manager&quot;&gt;The Making of a Manager: What to Do When Everyone Looks to You&lt;/a&gt;” by Julie Zhuo. In this book, the author mentions that during the week, she jots down her thoughts about her team, what she hears others say about it, how the projects are going, etc; basically anything that’s happening during the week that is directly or indirectly related to her team, she writes down.&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Zhuo said she does this to properly digest the daily events — if possible, she writes them as they happen, if not within hours of them happening. Then, at the end of the week, she reviews them, which allows her to digest the different situations and process stuff she might have forgotten or that she remembers but which are a bit fuzzy. For her to cement this new habit, she started sending the resulting compilation to her team.&lt;/p&gt;
&lt;p&gt;When I first read this, it struck a chord in me, as a few weeks prior to reading this book, I had started working on my own journaling habit. I did this by leveraging it with &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; for my notetaking and daily jots in a way that allowed me to connect topics organically and learn from the patterns that emerged.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Thanks to my nature that pushes me to try new things and introduce changes and tools to my system to see if they can bring me any value, it didn’t take much for me to get in the habit of writing my daily notes in a way similar to Zhuo. As mentioned above, I was already taking daily notes as a reference for my weekly reviews, as well as to not forget what was important during meetings. However, I wasn’t sharing them with the team. After reading Zhuo’s book, the idea of sharing them with the team made a lot of sense and required little adapting on my part, as I was already compiling a daily journal.&lt;/p&gt;
&lt;p&gt;Obsidian has a plugin for daily note taking; you can provide a template in Markdown and it’ll be used any time you create a new daily note. So with this in place, I can quickly, and with just one click, be on my daily note.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 189px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/bca6a68e874405a89ef357116b0b0713/d1ea9/obsidian-daily-notes.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 170.37037037037035%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAiCAYAAABfqvm9AAAACXBIWXMAAAsSAAALEgHS3X78AAADJElEQVRIx42W2VLCQBBF8ybIomyCIqIIuLCKgoobIKUP4v9/zlinqy41JkF86JpJT9Lp07dnkuDo6MhVKhVXLpddtVp1x8fH7uDgwK5Z0zq+Uqlk418WtFotV6vV3MXFhVutVu7z89O9vLy4wWDg5vO5WywW7uPjwzUaDVcoFCIBwi8JisWiww4PD129XrcMz87OLDMyxnghGRNQ9/vB/KCB/yYeIDDBCMycQJozysJBIwG1+PT05N7f3w2T+d3dnV33+30rwWQyWd8zHA5dPp+PD6g3SRgwyUrGukrA9enpqc23ZoiiCiL1hawSEIj79/f3I3X8N3Kv1zPfbDazOb7xeGz13igKI9mcnJyYqQ+FRhloH0Z8dAMZRwL6aXODTL5wc2OoDHI4oQgyKoJFQz8+PrrRaGTX3W7XMF9fX60M39/fpnpY6QgySOfn544dRKODjqJkiL/dbq/Hy8tLW1dP/kKW0dwYb9WoNea+Hws3eAT5/v5+jfzw8GBIzNnry+XSFMaPwrGHQxgZVFCur68NjRI0m01D1hp+yiCFY1WWE4xcLvfLhKY11MWYb21sVEZJFAQfZOZkxrEG8vPzs5WFdf+giEWmVp1Ox9oERPAIBjIjxhov4szUPt+I7CMJS2i6Bj2dTpttRUY90IREFm9vb5bZdDo1sfBRGrIMn+IRZB6kTjwEPgoTBGSVAuVvb2/tvtiAYeS9vT0zH5nC42PkOpVK/Q8ZDPYweGRBRqjKVkN9RKKxKQslYXv6WUaQwbi5ubEDAFSw8XHi8BkAWy/FYtsmjJzNZs2EzUgWmUzGRnzgcs9WZLJAaVREGEQiE1ApA+jcQznw/flNwcAlGEFAo4bgc0rrfKThCbixbXxkcMLIzFUKdQF+BYv9pmgBAXg7WTEHmYzJiowQhSzB9cXYGJCbaQu+eOATlIC0h/YvNaaWcf86scggbUJmH6vx435HAv/4x9RnZMecTKQombHtrq6ubI3viV9HLNC/oIyH2Bn6uoFIu/Aw5yIloQRfX19WX7LUHwYWqGFlu7u7LplMrk3X7N1EImGoNPXOzo75/GexH7Helii+ud02AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screenshot of Obsidian’s sidebar showing “daily notes” as the title along with a list of dates.&quot;
        title=&quot;Screenshot of Obsidian’s sidebar showing “daily notes” as the title along with a list of dates.&quot;
        src=&quot;/blog/static/bca6a68e874405a89ef357116b0b0713/d1ea9/obsidian-daily-notes.png&quot;
        srcset=&quot;/blog/static/bca6a68e874405a89ef357116b0b0713/d1ea9/obsidian-daily-notes.png 189w&quot;
        sizes=&quot;(max-width: 189px) 100vw, 189px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The image above shows a sample of my notes and the format I’ve chosen for naming them (this is also automatically filled by Obsidian). I just set up the date, and then at the end, I include the name of the day (this is useful and important for my newsletter, since I only need to take a look at the notes from Monday to Friday for it).&lt;/p&gt;
&lt;h2&gt;What Does a Daily Note Look Like?&lt;/h2&gt;
&lt;p&gt;Honestly, there’s not much magic behind the daily notes. I literally write down anything and everything that happens; later on, when I’m revisiting my notes, I can ignore what’s not relevant or even delete something if I feel like it’s not adding value or it isn’t clear enough.&lt;/p&gt;
&lt;p&gt;My template is divided into two sections: &lt;strong&gt;Happenings&lt;/strong&gt; and &lt;strong&gt;Grateful For&lt;/strong&gt;. Originally, it also had action items at the bottom, but since I follow the &lt;a href=&quot;https://gettingthingsdone.com/what-is-gtd/&quot;&gt;Getting Things Done&lt;/a&gt; principle, I write every action item that comes my way in my tracking system (at the moment, it’s a combination of &lt;a href=&quot;https://todoist.com&quot;&gt;Todoist&lt;/a&gt; and &lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt;), so it doesn’t make much sense to duplicate efforts.&lt;/p&gt;
&lt;h3&gt;Happenings&lt;/h3&gt;
&lt;p&gt;This is where everything goes. It’s usually a list of one or two liners describing things that happened in a meeting, a conversation I noticed on Slack, an email that caught my attention, etc. I try to not write a lot per topic, but I do tend to make a lot of entries (usually ending up with 20 or more per day). The idea is just a single line that will jog my memory and put me in the same state of mind as when the thing it’s describing happened.&lt;/p&gt;
&lt;h3&gt;Grateful For&lt;/h3&gt;
&lt;p&gt;This is part of the journaling exercise that has come in handy for the newsletter. Here I try to write two to three things that I’m grateful for each day. The idea is to not have to think too hard, which gives me the freedom to write small things like “Having flexibility today to move some meetings and get more work done.” Wherever it is that I’m grateful for, no matter how minor, I write it down.&lt;/p&gt;
&lt;h2&gt;Wiring Everything Together for Team Communication&lt;/h2&gt;
&lt;p&gt;At the end of the week, I go through all my notes related to work and to the team and compile them into a list. Here I try to be succinct and just provide a small update for the team.&lt;/p&gt;
&lt;p&gt;This way, I can highlight the strengths and remarkable qualities the team showed during the week, as well as point to some areas where we can do better.&lt;/p&gt;
&lt;p&gt;I also copy my manager in the email to take the opportunity to show them all the things the team is doing that might not be as visible (Jira tasks and meetings can be visible for externals, but the other most important topics could go unnoticed) and make my manager aware of all the great things the individuals on the team are doing. The purpose here is to bring to light what the team is doing in a more pointed way by highlighting specific things they accomplish during the week.&lt;/p&gt;
&lt;p&gt;I find this important because it reminds me of things that might not be too “flashy” but that were super important. It has also given me a tool to recognize weekly work that the team executed.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/260a93baf80390b48f657b8078e86e66/c20f6/soundcloud_mail_2020-09-25.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.51395007342144%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABG0lEQVQoz51S2Y7CMAzs//8eL5TS0kIPekIaWnrPdrzKCtCiRRtpFMeJx2M7Vt+2qKoK1+tVoFSNutYYxxGfrGVZBHx/u91gVacA53OKsizFQXRdJ4/nef6BCXwFibq+xzAMmKYJFgObpsHxeEQQBLJHUYQkSZDnuahXSkFrLTDnuq5xuVwEPNPPeyGkc7PZwHEcuK4L27bFZoIwDIWcSTzPw+FwEJ9JXBSFJDYVCiGzGRIGbbdb7Pd7sU0S3/cFcRw/kdAmsiz7Vsi6ecmBsHSSczdgOe06OPbxdRCPMH22+rWhlM/gx6B3E/1rCeFuZ+MURmjv97fT/BQWVSVphSAqodsJs4hYPlLzq0L+nzRbe6g0+n586tF/1hcEPgXd3FJY4QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Redacted email subject: Week 27 Thoughts about the week. Mentions updates of the projects and focus week. Ways of working, Jira and knowledge sharing approaches. Ends with an overall positive week for Core Clients&quot;
        title=&quot;Redacted email subject: Week 27 Thoughts about the week. Mentions updates of the projects and focus week. Ways of working, Jira and knowledge sharing approaches. Ends with an overall positive week for Core Clients&quot;
        src=&quot;/blog/static/260a93baf80390b48f657b8078e86e66/8ff1e/soundcloud_mail_2020-09-25.png&quot;
        srcset=&quot;/blog/static/260a93baf80390b48f657b8078e86e66/9ec3c/soundcloud_mail_2020-09-25.png 200w,
/blog/static/260a93baf80390b48f657b8078e86e66/c7805/soundcloud_mail_2020-09-25.png 400w,
/blog/static/260a93baf80390b48f657b8078e86e66/8ff1e/soundcloud_mail_2020-09-25.png 800w,
/blog/static/260a93baf80390b48f657b8078e86e66/6ff5e/soundcloud_mail_2020-09-25.png 1200w,
/blog/static/260a93baf80390b48f657b8078e86e66/c20f6/soundcloud_mail_2020-09-25.png 1362w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One key aspect of this for me is to keep it casual, since the objective is not a report but rather a sharing of one’s thoughts and ideas during the week, and also to let the team know how their work and the team are being perceived by others outside of the group of people that they directly work with on a daily basis.&lt;/p&gt;
&lt;p&gt;On that note, it’s important that the email is short and concise; I aim to exclude details that are either too small to bear any relevance or that are just part of the operational overhead of running the team and adjacent projects. I’m trying to keep everyone up to date on the latest happenings of the team but not achieve such a granular level of details that the reader might get lost without active technical knowledge of each and every project.&lt;/p&gt;
&lt;p&gt;We’re starting to adopt this technique at SoundCloud outside of my team (other teams, project-specific updates, etc.) and so far, it has proven quite successful. Often, team members feel happy when something they did gets mentioned in the email; they might have not thought it was that remarkable or they might think it went unnoticed, and then reading it in the weekly newsletter definitely brings some unexpected joy. This was expressed to me by a couple of team members during our 1:1s, confirming the hypothesis that highlighting these achievements helps build the team’s morale.&lt;/p&gt;
&lt;p&gt;On a parting note, one last detail I derived from this exercise is that while I’m compiling the list of achievements, improvements, and random thoughts, I get to do a “retrospective” of the week. This helps me understand how things are going; define a better trajectory for the team and the projects; and adjust direction more quickly, because I take the time to process things regularly and within a “short” period of time so as to avoid having to go through lots of different events. This obviously has the potential to keep the team on track and informed constantly and regularly. Only time will tell how beneficial this practice might be, but at least for the time being, it’s proving to be quite useful.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;Note: It seems a similar concept is also mentioned in the book “&lt;a href=&quot;https://www.goodreads.com/book/show/23232941-soft-skills?ac=1&amp;#x26;from_search=true&amp;#x26;qid=CVhAT4F1tw&amp;#x26;rank=1&quot;&gt;Soft Skills: The Software Developer’s Life Manual&lt;/a&gt;” by John Z. Sonmez.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Testing SQL for BigQuery]]></title><description><![CDATA[“To me, legacy code is simply code without tests.” — Michael Feathers If untested code is legacy code, why aren’t we testing data pipelines or ETLs (extract, transform, load)? In particular, data pipelines built in SQL are rarely tested. However, as software engineers, we know all our code should be tested. So in this post, I’ll describe how we started testing SQL data pipelines at SoundCloud.]]></description><link>https://developers.soundcloud.com/blog/testing-sql-for-bigquery</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/testing-sql-for-bigquery</guid><pubDate>Fri, 16 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;“To me, legacy code is simply code without tests.” — Michael Feathers&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If untested code is legacy code, why aren’t we testing data pipelines or ETLs (extract, transform, load)? In particular, data pipelines built in SQL are rarely tested. However, as software engineers, we know all our code should be tested. So in this post, I’ll describe how we started testing SQL data pipelines at SoundCloud.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Now when I talked to our data scientists or data engineers, I heard some of them say &lt;em&gt;“Oh, we do have tests! After creating a dataset and ideally before using the data, we run anomaly detection on it/check that the dataset size has not changed by more than 10 percent compared to yesterday etc.”&lt;/em&gt; That’s not what I would call a test, though; I would call that a validation.&lt;/p&gt;
&lt;p&gt;Validations are important and useful, but they’re not what I want to talk about here. &lt;em&gt;Validations&lt;/em&gt; are what increase &lt;em&gt;confidence in data&lt;/em&gt;, and &lt;em&gt;tests&lt;/em&gt; are what increase &lt;em&gt;confidence in code&lt;/em&gt; used to produce the data. And &lt;em&gt;SQL is code&lt;/em&gt;. Ideally, &lt;em&gt;validations&lt;/em&gt; are run regularly at the end of an ETL to produce the data, while &lt;em&gt;tests&lt;/em&gt; are run as part of a continuous integration pipeline to publish the code that will be used to run the ETL.&lt;/p&gt;
&lt;p&gt;Some of the advantages of having tests and not only validations are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some bugs can’t be detected using validations alone. For example, if your query transforms some input data and then aggregates it, you may not be able to detect bugs in the transformation purely by looking at the aggregated query result. Hence you need to test the transformation code directly.&lt;/li&gt;
&lt;li&gt;Depending on how long processing all the data takes, tests provide a quicker feedback loop in development than validations do.&lt;/li&gt;
&lt;li&gt;Validations are code too, which means they also need tests.&lt;/li&gt;
&lt;li&gt;Improved development experience through quick test-driven development (TDD) feedback loops.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Backstory&lt;/h2&gt;
&lt;p&gt;My team, the Content Rights Team, used to be an almost pure backend team. We handle translating the music industry’s concepts into authorization logic for tracks on our apps, which can be complicated enough. However, since the shift toward &lt;a href=&quot;https://developers.soundcloud.com/blog/a-better-model-of-data-ownership&quot;&gt;data-producing teams owning datasets&lt;/a&gt; — which took place about three years ago — we’ve been responsible for &lt;em&gt;providing published datasets&lt;/em&gt; with a &lt;em&gt;clearly defined interface&lt;/em&gt; to consuming teams like the &lt;a href=&quot;https://developers.soundcloud.com/blog/how-not-to-build-datasets-and-consume-data-at-your-company&quot;&gt;Insights and Reporting Team&lt;/a&gt;, content operations teams, and data scientists.&lt;/p&gt;
&lt;p&gt;We’ve been using technology and &lt;em&gt;best practices&lt;/em&gt; close to what we’re used to for live backend services in our dataset, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Spark&lt;/em&gt; to conveniently code in the same language as our backend service, &lt;em&gt;Scala&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Prometheus&lt;/em&gt; to &lt;em&gt;monitor&lt;/em&gt;, using custom metrics pushed through the Pushgateway (these metrics give us insight through alerting and visualization)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Tests&lt;/em&gt; built like the tests for our live backend services&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, Spark has its drawbacks. Even though the framework advertises its speed as “lightning-fast,” it’s still &lt;em&gt;slow for the size of some of our datasets&lt;/em&gt;. This affects not only performance in production — which we could often but not always live with — but also the feedback cycle in development and the speed of backfills if business logic has to be changed retrospectively for months or even years of data.&lt;/p&gt;
&lt;p&gt;Of course, we educated ourselves, optimized our code and configuration, and threw resources at the problem, but this cost time and money. For some of the datasets, we instead filter and only process the data most critical to the business (e.g. only export data for selected territories), or we use more complicated logic so that we need to process less data (e.g. rolling up incrementally or not writing the rows with the most frequent value).&lt;/p&gt;
&lt;p&gt;Other teams were fighting the same problems, too, and the Insights and Reporting Team tried &lt;em&gt;moving to Google BigQuery first&lt;/em&gt;. Inspired by their initial successes, they gradually left Spark behind and moved all of their batch jobs to SQL queries in BigQuery. But with Spark, they also left tests and monitoring behind. In their case, they had good automated validations, business people verifying their results, and an advanced development environment to increase the confidence in their datasets. Through BigQuery, they also had the possibility to backfill much more quickly when there was a bug.&lt;/p&gt;
&lt;p&gt;In the meantime, the Data Platform Team had also introduced some monitoring for the timeliness and size of datasets. But still, SoundCloud didn’t have a single (fully) tested batch job written in &lt;em&gt;SQL&lt;/em&gt; against BigQuery, and it also lacked best practices on how to test SQL queries. I searched some corners of the internet I knew of for examples of what other people and companies were doing, but I didn’t find a lot (I am sure there must be some out there; if you’ve encountered or written good examples, I’m interested in learning about them).&lt;/p&gt;
&lt;p&gt;It struck me as a &lt;em&gt;cultural problem&lt;/em&gt;: Testing didn’t seem to be a standard for production-ready data pipelines, and SQL didn’t seem to be considered code.&lt;/p&gt;
&lt;h2&gt;The Project&lt;/h2&gt;
&lt;p&gt;After I demoed our latest dataset we had built in Spark and mentioned my frustration about both Spark and the lack of SQL testing (best) practices in passing, Björn Pollex from Insights and Reporting — the team that was already using BigQuery for its datasets — approached me, and we started a collaboration to &lt;em&gt;spike a fully tested dataset&lt;/em&gt;. His motivation was to add tests to his team’s untested ETLs, while mine was to possibly move our datasets without losing the tests.&lt;/p&gt;
&lt;p&gt;We used our &lt;em&gt;“self-allocated time”&lt;/em&gt; (SAT, 20 percent of engineers’ work time, usually Fridays), which is one of my favorite perks of working at SoundCloud, to collaborate on this project. As the dataset, we chose one: the last transformation job of our &lt;em&gt;track authorization dataset&lt;/em&gt; (called the &lt;em&gt;“projector”&lt;/em&gt;), and its &lt;em&gt;validation&lt;/em&gt; step, which was also written in Spark. We already had test cases for example-based testing for this job in Spark; its location of consumption was BigQuery anyway; the track authorization dataset is one of the datasets for which we don’t expose all data for performance reasons, so we have a reason to move it; and by migrating an existing dataset, we made sure we’d be able to compare the results. Also, it was small enough to tackle in our SAT, but complex enough to need tests.&lt;/p&gt;
&lt;h2&gt;Challenges and Solutions&lt;/h2&gt;
&lt;p&gt;Not all of the challenges were technical.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Organizationally&lt;/em&gt;, we had to add our tests to a continuous integration pipeline owned by another team and used throughout the company. Fortunately, the owners appreciated the initiative and helped us.&lt;/p&gt;
&lt;p&gt;I’ve already touched on the &lt;em&gt;cultural&lt;/em&gt; point that testing SQL is not common and not many examples exist. We shared our proof of concept project at an internal Tech Open House and hope to contribute a tiny bit to a cultural shift through this blog post.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;technical&lt;/em&gt; challenges weren’t necessarily hard; there were just several, and we had to do &lt;em&gt;something&lt;/em&gt; about them. I don’t claim whatsoever that the solutions we came up with in this first iteration are perfect or even good — but they’re a starting point.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tests had to be &lt;em&gt;run in BigQuery&lt;/em&gt;, for which there is &lt;em&gt;no containerized environment&lt;/em&gt; available (unlike e.g. MySQL, which can be tested against Docker images). We at least mitigated security concerns by not giving the test account access to any tables. The next point will show how we could do this.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Test execution performance&lt;/em&gt; had been the biggest blocker of previous attempts to add tests against BigQuery. BigQuery is performant for, well, big queries, but it has high constant time overhead, especially for loading data from and into tables. Single queries also take two to three seconds, which is still a lot, but not as much as loading a table first. Therefore, our tests don’t access any tables at all, and we &lt;em&gt;inject all test data as literals&lt;/em&gt; in our queries. An example would be:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; launch_dates &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; territory_code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;launched&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; launched
  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;US&apos;&lt;/span&gt;               &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; territory_code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2000-01-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; launched
        &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;DE&apos;&lt;/span&gt;               &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; territory_code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2050-01-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; launched&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;SQL&lt;/em&gt; itself &lt;em&gt;does not have language support or frameworks&lt;/em&gt; for testing. OK, one could argue that SQL’s ability to throw exceptions is all one needs, but that’s pretty basic. So we decided to use &lt;em&gt;Python&lt;/em&gt; and &lt;a href=&quot;https://pytest.org&quot;&gt;pytest&lt;/a&gt;. Extra benefits we get from this are that it’s the same language as our scheduler (Airflow), it has good failure reporting, and it has easy execution of e.g. a single test file and dependency injection through fixtures.&lt;/li&gt;
&lt;li&gt;Still, &lt;em&gt;SQL&lt;/em&gt; has &lt;em&gt;no language support for queries to be parametrized in the table&lt;/em&gt;. This is necessary for switching between the literals as tables in tests and the actual table name in production. We use the &lt;em&gt;templating engine Jinja&lt;/em&gt;, which is conveniently also used in Airflow.&lt;/li&gt;
&lt;li&gt;Already for Spark, it’s a challenge to express test data and assertions in a _simple-to-understand way_ — tests are for reading. We tried our best, using Python for abstraction, speaking names for the tests, and extracting common concerns (e.g. connecting to BigQuery and rendering templates) into pytest fixtures. We created &lt;a href=&quot;https://docs.python.org/3/library/dataclasses.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dataclasses&lt;/code&gt;&lt;/a&gt; for the data schema with &lt;code class=&quot;language-text&quot;&gt;to_sql&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;from_row&lt;/code&gt; methods for better readability, e.g.:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@dataclass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eq&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frozen&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TrackPolicyInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    track_urn&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;
    content_policy&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;

    @&lt;span class=&quot;token builtin&quot;&gt;classmethod&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;from_row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cls&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; row&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cls&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track_urn&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;track_urn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content_policy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content_policy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    @&lt;span class=&quot;token builtin&quot;&gt;property&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;as_sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&quot;&quot;
          (
            SELECT
            &apos;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;track_urn&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;      AS track_urn,
            &apos;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content_policy&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos; AS content_policy
          )
            &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The production queries use &lt;em&gt;Airflow templating&lt;/em&gt; (e.g. &lt;code class=&quot;language-text&quot;&gt;{{ execution_date }}&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;{{ ds_nodash }}&lt;/code&gt; ) to be runnable in Airflow. As we run the tests independently from Airflow, this part of the queries is unfortunately not yet tested. As a substitute, the team that maintains our Airflow now supports running Airflow locally, so we can at least manually check the queries after rendering (i.e. expansion of the template).&lt;/li&gt;
&lt;li&gt;SQL is not as &lt;em&gt;decomposable&lt;/em&gt; as we’re used to for other high-level programming languages. Thus writing unit tests isn’t trivial. We’ve approached this by using common table expressions (CTEs aka “WITH clauses”) extensively and adding templating with defaults to be able to only test single CTEs. Example:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; raw_rollup &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; {{ params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rollup_data }}{{ ds_nodash }}&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;-- replace monetizing policies in non-monetizing territories and split intervals&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  policies &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;-- now deduplicate / merge consecutive intervals with same values&lt;/span&gt;
  windowed_policies &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; {{ params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;policies_to_deduplicate&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;policies&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  deduplicated_policies &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; {{ params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;selected_cte&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;deduplicated_policies&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can test the CTE&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;policies&lt;/code&gt; by setting &lt;code class=&quot;language-text&quot;&gt;params={&amp;#39;rollup_data&amp;#39;: example.input_query, selected_cte&amp;#39;: &amp;#39;policies&amp;#39;}&lt;/code&gt; and&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;windowed_policies&lt;/code&gt; by setting &lt;code class=&quot;language-text&quot;&gt;params={&amp;#39;rollup_data&amp;#39;: empty_rollup_data, &amp;#39;policies_to_deduplicate&amp;#39;: example.input_query}&lt;/code&gt;
with the right selection of &lt;code class=&quot;language-text&quot;&gt;example&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;empty_rollup_data&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Although this approach requires some fiddling — e.g. for testing single CTEs while mocking the input for a single CTE — and can certainly be improved upon, it was great to develop an SQL query using TDD, to have regression tests, and to gain confidence through evidence. When I finally deleted the old Spark code, it was a net delete of almost 1,700 lines of code; the resulting two SQL queries have, respectively, 155 and 81 lines of SQL code; and the new tests have about 1,231 lines of Python code. A substantial part of this is boilerplate that could be extracted to a library.&lt;/p&gt;
&lt;p&gt;I’m looking forward to getting rid of the limitations in size and development speed that Spark imposed on us, and I’m excited to see how people inside and outside of our company are going to evolve testing of SQL, especially in BigQuery.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pagination Updates on Our API]]></title><description><![CDATA[As part of our efforts to improve our APIs, we’re introducing updates on how we paginate over tracks. This only affects developers and apps…]]></description><link>https://developers.soundcloud.com/blog/pagination-updates-on-our-api</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/pagination-updates-on-our-api</guid><pubDate>Mon, 21 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As part of our efforts to improve our APIs, we’re introducing updates on how we &lt;a href=&quot;https://developers.soundcloud.com/docs/api/guide#pagination&quot;&gt;paginate&lt;/a&gt; over tracks. This only affects developers and apps that aren’t strictly relying on the &lt;code class=&quot;language-text&quot;&gt;next_href&lt;/code&gt; field, but rather custom manipulating the &lt;code class=&quot;language-text&quot;&gt;offset&lt;/code&gt; to request a page.&lt;/p&gt;
&lt;h2&gt;Affected Endpoints&lt;/h2&gt;
&lt;p&gt;The following endpoints are affected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/users/:userId/tracks&lt;/li&gt;
&lt;li&gt;/me/tracks&lt;/li&gt;
&lt;li&gt;/users/:userId/playlists&lt;/li&gt;
&lt;li&gt;/me/playlists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Going forward, the paginated responses for the above endpoints will be represented via a &lt;code class=&quot;language-text&quot;&gt;cursor&lt;/code&gt; in the &lt;code class=&quot;language-text&quot;&gt;next_href&lt;/code&gt; field. The &lt;code class=&quot;language-text&quot;&gt;cursor&lt;/code&gt; field will replace the need for an offset to paginate, while also improving the performance of this endpoint. We recommend that developers strictly rely on the &lt;code class=&quot;language-text&quot;&gt;next_href&lt;/code&gt; field to request paginated results.&lt;/p&gt;
&lt;h2&gt;Current Format&lt;/h2&gt;
&lt;p&gt;Here’s the current format, which uses &lt;code class=&quot;language-text&quot;&gt;offset&lt;/code&gt; for &lt;code class=&quot;language-text&quot;&gt;next_href&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/users/123/tracks?linked_partitioning=true&amp;amp;limit=1&amp;amp;offset=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;New Format&lt;/h2&gt;
&lt;p&gt;Here’s the new format, which uses &lt;code class=&quot;language-text&quot;&gt;cursor&lt;/code&gt; for &lt;code class=&quot;language-text&quot;&gt;next_href&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/users/123/tracks?linked_partitioning=true&amp;amp;page_size=10&amp;amp;cursor=2018-02-27T14:37:10.000Z,tracks,00405959469&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Example Response&lt;/h2&gt;
&lt;p&gt;Here’s an example response:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;collection&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;//... track&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;next_href&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.soundcloud.com/users/123/tracks?linked_partitioning=true&amp;amp;page_size=10&amp;amp;cursor=2018-02-27T14:37:10.000Z,tracks,00405959469&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;This change will be in effect by 28 September 2020.&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;A Quick Note about the linked_partitioning Field&lt;/h2&gt;
&lt;p&gt;Setting the &lt;code class=&quot;language-text&quot;&gt;linked_partitioning&lt;/code&gt; field to &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; is our recommended way of fetching paginating responses. The shape of the response is an object with a &lt;code class=&quot;language-text&quot;&gt;cursor&lt;/code&gt; and the collection of results, rather than an array of the collection. In the near future, all responses for a collection will be paginated by default and served as an object. We highly recommended that developers include this parameter in their requests, as it’s the only way the API can guarantee paginated responses.&lt;/p&gt;
&lt;p&gt;To get information about future updates, please follow us on &lt;a href=&quot;https://twitter.com/soundclouddev&quot;&gt;Twitter&lt;/a&gt; and subscribe to the &lt;a href=&quot;https://developers.soundcloud.com/blog&quot;&gt;Backstage Blog&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Breaking Loose from Third-Party Lock-In with Custom Refactoring Tools]]></title><description><![CDATA[Code refactoring is an essential part of the job of software developers. As time goes on, technology evolves, product requirements change…]]></description><link>https://developers.soundcloud.com/blog/breaking-loose-from-third-party-lock-in-with-custom-refactoring-tools</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/breaking-loose-from-third-party-lock-in-with-custom-refactoring-tools</guid><pubDate>Tue, 11 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Code refactoring is an essential part of the job of software developers. As time goes on, technology evolves, product requirements change, and new features are built into a codebase. These things have the potential to negatively impact both the codebase’s quality and its capacity to be extended to retain its value for as long as possible. In addition to refactoring, it’s also part of our job to timely detect when changes in our code are necessary, so as to avoid code becoming too complex or growing to unmanageable levels.&lt;/p&gt;
&lt;p&gt;However, even though it’s essential, refactoring can at times be intimidating, and introducing improvements to a codebase without affecting its behavior can impose huge challenges on software developers.&lt;/p&gt;
&lt;h2&gt;Deprecating Specta&lt;/h2&gt;
&lt;p&gt;At SoundCloud, we create all our new features in our iOS app using Swift, but since our codebase was started several years ago, we still have a significant amount of Objective-C code to maintain (including third-party libraries). Years ago, before adopting Swift, we decided to use &lt;a href=&quot;https://github.com/specta/specta&quot;&gt;Specta&lt;/a&gt;, “A light-weight TDD / BDD framework for Objective-C,” to write our unit tests. It brought us a lot of value for a long time, but around two years ago, after experiencing issues like not being able to run a single test without the entire test suite being run, and being unhappy with Xcode’s support for Specta, we decided to deprecate it.&lt;/p&gt;
&lt;p&gt;From the moment of deprecation onward, we decided to write all our new tests using plain &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;s, but we were still locked in to using Specta, as we had more than 900 spec files in our codebase.&lt;/p&gt;
&lt;p&gt;The number of specs in our project was not negligible, and manually transforming those specs into &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;s was a very tedious, error-prone, and time-consuming task; even though we would have loved to transform all the specs into &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; at once, it would have taken too much time to be viable. Given this interesting challenge, I decided to take advantage of one of the &lt;a href=&quot;https://developers.soundcloud.com/blog/a-happy-new-employee&quot;&gt;benefits of working at SoundCloud&lt;/a&gt; and set out to find a way to automate the transformation of these specs during my self-allocated time (SAT). Luckily, the project succeeded and Specta is no longer part of our codebase. So in this post, I’ll cover how it was done.&lt;/p&gt;
&lt;h2&gt;Specta vs. XCTest&lt;/h2&gt;
&lt;p&gt;Let’s start by comparing how tests are structured in both &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;. Due to Specta’s domain-specific language (DSL), you can structure your tests in complex ways. This can be a blessing if you’re trying to avoid duplication and a curse if it’s taken too far, as it makes specs hard to navigate with all the nesting. For the sake of this blog post, we’ll use a rather simple spec to demonstrate the work done.&lt;/p&gt;
&lt;p&gt;The image below shows how testing the same simple &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt; class is done using both formats:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/27134d3f37ae0be10d5b2522f4f96095/8013a/specta-vs-xctest.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.62962962962963%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABrklEQVQoz3VT27KjIBD0azYqyh0Eb8mp3fP/n9TbYEw82dqHqVEYerqbofHO45Eysg4Yeo22U+iY36HQyhmbj3jEjGgTRh3RDRbdP7UajZQR2m8YWChGh/4J2pZcisq3mpH0gmRWjGGFsBmdMO+6kp/RWL8gph3aTJDs3LPzlWX7BLTcX9Kj1txa+ar5VNRoP8Owo+SP4Kb4kFAPEFCpiBgWWJehCDpQzWfjQzI77/tvepSwhYxQ/OHG7cqAgIbrIWwVrD+9vUg9a5tReqzzHTltcIWBSZAyVAZvwIWAE3LcUSxqhT0uZXBHXBmOyrNrgGQu4OXb2Kky+QQsYDqy8UTPaVNPG0qcF3RIJkAgM0dApz00mf26jTVecvQBWECkmyGZBVW0hd3ofwIWVtv6VSWHuMLZXFmW9auHgUDr+gfT/RvT/o0wf8GkOyTPCKr5Aego0XJRKgKRqTbxMP/s/GToKVkRQDPMtMFy3CTViWqPqTPblDEQNPiUWGbsjJfkOocJkf6ZzIthGCqy+Q41rfVRnLKbwkT85xmdknsytGSo1HF5R4RqjagP4T3ofwEPe3Y8Zbh+IgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;specta vs xctest&quot;
        title=&quot;specta vs xctest&quot;
        src=&quot;/blog/static/27134d3f37ae0be10d5b2522f4f96095/8ff1e/specta-vs-xctest.png&quot;
        srcset=&quot;/blog/static/27134d3f37ae0be10d5b2522f4f96095/9ec3c/specta-vs-xctest.png 200w,
/blog/static/27134d3f37ae0be10d5b2522f4f96095/c7805/specta-vs-xctest.png 400w,
/blog/static/27134d3f37ae0be10d5b2522f4f96095/8ff1e/specta-vs-xctest.png 800w,
/blog/static/27134d3f37ae0be10d5b2522f4f96095/6ff5e/specta-vs-xctest.png 1200w,
/blog/static/27134d3f37ae0be10d5b2522f4f96095/8013a/specta-vs-xctest.png 1350w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Test Declaration&lt;/h3&gt;
&lt;p&gt;In &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt;, tests are declared with the use of the &lt;code class=&quot;language-text&quot;&gt;SpecBegin&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;SpecEnd&lt;/code&gt; macros, while in &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;, tests are classes that inherit from the &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class.&lt;/p&gt;
&lt;h3&gt;Scope of Variables and Properties&lt;/h3&gt;
&lt;p&gt;In Specta, the scope of variables is determined by the enclosing scope they’re declared in. The &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt; DSL works by using functions that get passed blocks, where every block defines a new context. To make variables mutable from within blocks nested at a deeper level, they need to be declared with the &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Blocks/Articles/bxVariables.html&quot;&gt;__block&lt;/a&gt; keyword. In &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;, member properties or ivars are declared to make them accessible by methods, just like a regular Objective-C class.&lt;/p&gt;
&lt;h3&gt;Setup and Teardown&lt;/h3&gt;
&lt;p&gt;When writing tests, we need to prepare the initial state before our tests run and clean it up after they complete. In &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; provides class and instance methods that can be overridden to set and clean the state. In &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt;, the &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;after&lt;/code&gt; methods serve the same purpose.&lt;/p&gt;
&lt;h3&gt;Test Methods&lt;/h3&gt;
&lt;p&gt;In &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt;, test methods need to comply with a set of conventions in order for Xcode to find and execute them. As per &lt;a href=&quot;https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods&quot;&gt;Apple documentation&lt;/a&gt;, “a test method is an instance method on an &lt;code class=&quot;language-text&quot;&gt;XCTestCase subclass&lt;/code&gt;, with no parameters, no return value, and a name that begins with the lowercase word &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;.” When using &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt;, we call the &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; function and pass the block that contains the assertions for our test (specs are processed at runtime to dynamically generate test classes that comply with these conventions, which makes it possible for Xcode to execute them).&lt;/p&gt;
&lt;h2&gt;From Specta to XCTest&lt;/h2&gt;
&lt;p&gt;Knowing how these two ways of writing tests differ, I figured that to systematically transform a &lt;code class=&quot;language-text&quot;&gt;Specta&lt;/code&gt; spec into an &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class, we needed to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find top-level calls to the &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;after&lt;/code&gt; methods and use them to generate &lt;code class=&quot;language-text&quot;&gt;setUp&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;tearDown&lt;/code&gt; methods.&lt;/li&gt;
&lt;li&gt;Find calls to the &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; method and generate test methods from them.&lt;/li&gt;
&lt;li&gt;Find calls to the &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; method that are within a context and generate setup methods from them that can be called from test methods to set the initial state as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to generate intuitive names for our test methods, the context names needed to be extracted from calls to the &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;context&lt;/code&gt; functions and then processed to make them valid Objective-C method names. In the same manner, calls to &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;after&lt;/code&gt; methods from within the associated contexts needed to be detected to make sure the state was being prepared accordingly before the tests were run. If this sounds confusing, the image below might give you a better idea of how elements from the spec were translated into &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; test methods.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/01b8f840504e9c517d2ac908a65d39c6/1589a/specta-vs-xctest-method-names.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.81191222570533%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9klEQVQ4y22SaZabMBCEOY3HBoOEdsRmx868yf0PVKkWxh5P8qOfABVfVy+VMxZrCMjaQ517HGuF01toHI4dlJlxCR7ZBHjt0LS23J1+6KtORdh4QWsHHJu+iI4SJ4kXsDMr7u6KIVCbRD/iRL1oXnqNqncZPi7QfURvErrOPUH7+UHgmQ57HWGYuD6b8u1d93CoKRBRU/fFSRHW/wIbApObkIYLtIlQyqMheNfs+qqjq3m5sz8ZtzRhDiM69uf4EL0cLvA2w4YZLas4twb1o+Q3oFyO+YLoBjjGEPlDs5X0E+j6hORndHR3+Gg3mPTwEaItQE+QhGIfNR3n8QrnR7qwT2Br1wLUboSmS8Wz1Qm1DqhVeA6okmyaHzuujGIZurOlNzIgAYpoBxoBsuyeyTSHKRtS01Dd+W89JHCarkhxQk4zxmEtk5YBtYTLvUyvtQvbwqFMN6T1Ez03o2WCE93VPHdoKTn6jExgYi8dhzIyQeazTF+cSvlp+YPrfMfIAVreW97bvJZThQkNqyxAcSArIKf87HlpWc4W20pJUjPcMecrJrpL62849jlMvxDmG0xaoPyEmi2qjN16dfjYdlBC1kG+CUgSybMAJwLX6yfmR6y3L1zuX4gjXXI3i0NFq/9b0O/vksBEOpEh8Fn0cu4G9pB//gJgLJoCEiq+gQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;specta vs xctest&quot;
        title=&quot;specta vs xctest&quot;
        src=&quot;/blog/static/01b8f840504e9c517d2ac908a65d39c6/8ff1e/specta-vs-xctest-method-names.png&quot;
        srcset=&quot;/blog/static/01b8f840504e9c517d2ac908a65d39c6/9ec3c/specta-vs-xctest-method-names.png 200w,
/blog/static/01b8f840504e9c517d2ac908a65d39c6/c7805/specta-vs-xctest-method-names.png 400w,
/blog/static/01b8f840504e9c517d2ac908a65d39c6/8ff1e/specta-vs-xctest-method-names.png 800w,
/blog/static/01b8f840504e9c517d2ac908a65d39c6/6ff5e/specta-vs-xctest-method-names.png 1200w,
/blog/static/01b8f840504e9c517d2ac908a65d39c6/1589a/specta-vs-xctest-method-names.png 1276w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Code transformations can be done in multiple ways, and depending on the problem, one tool might be better than another. Some of the options I considered were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text replacement using regular expressions.&lt;/li&gt;
&lt;li&gt;Refactoring features available in Xcode.&lt;/li&gt;
&lt;li&gt;Low-level libraries that operate on the abstract syntax tree (AST).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While possible, using regular expressions to perform the type of source transformations described above would have been incredibly cumbersome and error-prone. The available Xcode features for refactoring were also not advanced enough for the task at hand, so I was left with option three. I started investigating different alternatives that allowed me to operate on the AST. Fortunately, I found &lt;code class=&quot;language-text&quot;&gt;LibTooling&lt;/code&gt;, which provides a way to conveniently analyze the abstract syntax tree using a set of high-level abstractions to match against sections of the code you’re interested in. In the following sections, I’ll expand on what LibTooling is and how I used it.&lt;/p&gt;
&lt;h2&gt;LLVM’s LibTooling&lt;/h2&gt;
&lt;p&gt;LibTooling is a library you can use to create your own standalone tools based on the Clang compiler. The LLVM compiler takes our code through three different stages to create the executable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The frontend stage takes the code through the lexical, syntactic, and semantic analysis phases to generate the IR (intermediate representation) that is used as an input for the subsequent phases.&lt;/li&gt;
&lt;li&gt;The middle-end stage applies target-independent optimizations — like the removal of useless or unreachable code — on the IR.&lt;/li&gt;
&lt;li&gt;Finally, the backend stage converts the IR into machine code to generate the target program.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to efficiently perform a source transformation, we need to be able to operate on the AST, which means we need to somehow be able to tap into the compiler’s frontend to have it generate the AST for our source and later be able to analyze it. Fortunately, this is exactly what LibTooling’s frontend actions do!&lt;/p&gt;
&lt;h3&gt;ASTMatchFinder&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ASTMatchFinder&lt;/code&gt; is a frontend action class that can be used to match against the AST. To better explain what is meant by “matching against the AST,” I’ll provide a concrete example.&lt;/p&gt;
&lt;p&gt;Consider the following function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let’s see what its AST looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;`&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;FunctionDecl &lt;span class=&quot;token number&quot;&gt;0x5aeab50&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; f &lt;span class=&quot;token string&quot;&gt;&apos;int (int)&apos;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ParmVarDecl &lt;span class=&quot;token number&quot;&gt;0x5aeaa90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;line&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; x &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt;
  `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;CompoundStmt &lt;span class=&quot;token number&quot;&gt;0x5aead88&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DeclStmt &lt;span class=&quot;token number&quot;&gt;0x5aead10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;line&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;VarDecl &lt;span class=&quot;token number&quot;&gt;0x5aeac10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; result &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ParenExpr &lt;span class=&quot;token number&quot;&gt;0x5aeacf0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;BinaryOperator &lt;span class=&quot;token number&quot;&gt;0x5aeacc8&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ImplicitCastExpr &lt;span class=&quot;token number&quot;&gt;0x5aeacb0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;LValueToRValue&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DeclRefExpr &lt;span class=&quot;token number&quot;&gt;0x5aeac68&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; lvalue ParmVar &lt;span class=&quot;token number&quot;&gt;0x5aeaa90&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;x&apos;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;IntegerLiteral &lt;span class=&quot;token number&quot;&gt;0x5aeac90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;
    `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ReturnStmt &lt;span class=&quot;token number&quot;&gt;0x5aead68&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;line&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ImplicitCastExpr &lt;span class=&quot;token number&quot;&gt;0x5aead50&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;LValueToRValue&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        `&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DeclRefExpr &lt;span class=&quot;token number&quot;&gt;0x5aead28&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;col&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt; lvalue Var &lt;span class=&quot;token number&quot;&gt;0x5aeac10&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;result&apos;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;int&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The AST shows what type of nodes are generated when compiling the function above. With &lt;code class=&quot;language-text&quot;&gt;MatchFinder&lt;/code&gt;, you can attach instances of &lt;code class=&quot;language-text&quot;&gt;AstMatcher&lt;/code&gt; to selectively match nodes of this AST. Some of the matchers we can use are &lt;code class=&quot;language-text&quot;&gt;callExpr&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;declStmt&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;functionDecl&lt;/code&gt;, and &lt;code class=&quot;language-text&quot;&gt;CompoundStmt&lt;/code&gt;. For more matchers, have a look at the &lt;a href=&quot;https://clang.llvm.org/docs/LibASTMatchersReference.html&quot;&gt;reference site&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Refactoring with ASTMatchers&lt;/h3&gt;
&lt;p&gt;As described in the section above, &lt;code class=&quot;language-text&quot;&gt;ASTMatcher&lt;/code&gt;s help us easily identify the presence of specific nodes in our source code. Once we’ve identified the nodes, we can also find out their specific locations. This allows us to analyze our spec source code and extract the sections we’re interested in. Once extracted, we can use the output to build a new source — in this case, our &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class. Let’s look at some of the &lt;code class=&quot;language-text&quot;&gt;ASTMatcher&lt;/code&gt;s I created for matching against parts of a Specta spec. The matcher used to match against the &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; functions looks as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; ItExprMatcher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callExpr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;functionDecl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;it&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;token function&quot;&gt;hasArgument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasDescendant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringLiteral&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;testName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;token function&quot;&gt;hasArgument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blockExpr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;itExpr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can see how the DSL allows us to express in a very natural way that we want to find a call expression, where the callee is a function with the name &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt;. The function should have two arguments — the first one being of type string literal, and the second being a block expression. Also, notice the calls to the &lt;code class=&quot;language-text&quot;&gt;bind&lt;/code&gt; function; as we’ll see in a moment, calling this function allows us to refer to specific nodes via the names that are passed to it. Let’s look at a more complex example to better illustrate what &lt;code class=&quot;language-text&quot;&gt;ASTMatcher&lt;/code&gt;s can do:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; TopLevelBeforeExpr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callExpr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                                         &lt;span class=&quot;token function&quot;&gt;callee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;functionDecl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;before&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                         &lt;span class=&quot;token function&quot;&gt;hasArgument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blockExpr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;beforeExpr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                         &lt;span class=&quot;token function&quot;&gt;unless&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasAncestor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callExpr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;functionDecl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;anyOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;describe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token function&quot;&gt;hasName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                         &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;topLevelBeforeExpr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As mentioned before, we need to distinguish between calls to &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; that are within a context and those that are top-level ones. This is necessary in order to produce setup methods that will set the initial state according to our tests. To identify top-level &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; calls, we can add a condition to our matcher where it’ll accept calls to functions with the name “before” that take a block expression as the first argument &lt;em&gt;as long as&lt;/em&gt; they have an ancestor within a &lt;code class=&quot;language-text&quot;&gt;context&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt; expression.&lt;/p&gt;
&lt;h3&gt;Handling Matches&lt;/h3&gt;
&lt;p&gt;Every time we register a matcher in our &lt;code class=&quot;language-text&quot;&gt;MatchFinder&lt;/code&gt;, we also need to pass an object that implements the &lt;code class=&quot;language-text&quot;&gt;MatchCallback&lt;/code&gt; abstract class. This class declares the following methods:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MatchCallback&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MatchCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; MatchResult &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onStartOfTranslationUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onEndOfTranslationUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As their names suggest, the &lt;code class=&quot;language-text&quot;&gt;StartOfTranslationUnit&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;onEndOfTranslationUnit&lt;/code&gt; methods are called at the start and end of processing a translation unit (source file). More importantly, the &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; method is the one that will be called whenever a match is detected. In order to know what type we need to process, we can check every result for nodes we’re trying to match using the type of node and the string we passed to the &lt;code class=&quot;language-text&quot;&gt;bind&lt;/code&gt; function, like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; ASTHandler&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; MatchFinder&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;MatchResult &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getNodeAs&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;clang&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;Expr&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;itExpr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we’re checking if the result we got contains a node of type &lt;code class=&quot;language-text&quot;&gt;Expr&lt;/code&gt; that’s bound to the &lt;code class=&quot;language-text&quot;&gt;itExpr&lt;/code&gt; identifier. Once we have identified the type of result, we can get more details from it — for instance, when we match a call to the &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; function, we want to be able to extract the first parameter in order to use it to generate our &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; method. We can extract this string as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getNodeAs&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;clang&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;Expr&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;itExprWrapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; itText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getNodeAs&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;clang&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;StringLiteral&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;testName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Unique Identifiers&lt;/h3&gt;
&lt;p&gt;In order to identify sibling nodes after extracting the code from the AST, I needed to mark nodes in a way that allowed me to identify the ones that shared the same parent. For this, I created a helper function that generates a unique ID based on the beginning and end location of a node:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cpp&quot;&gt;&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;string Utils&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getIdForNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; std&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;string key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; MatchFinder&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;MatchResult Result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clang&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;Stmt &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getNodeAs&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;clang&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;Stmt&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; beginLoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBeginLoc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; endLoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEndLoc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;std&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SourceManager&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;beginLoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; std&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SourceManager&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;endLoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Matching Preprocessors&lt;/h3&gt;
&lt;p&gt;In addition to matching nodes in the AST, I also needed to handle preprocessors in order to find inclusion directives that needed to be copied over to the &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; output file. LibTooling provides the &lt;code class=&quot;language-text&quot;&gt;PreprocessOnlyAction&lt;/code&gt; class, which allows us to attach preprocess actions capable of matching against preprocessors like inclusion and pragma directives, among others.&lt;/p&gt;
&lt;h3&gt;Collecting Results&lt;/h3&gt;
&lt;p&gt;To make things simpler for me given my limited C++ experience, I decided to parse the match results into JSON and do the rest of the work to generate the &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; source using a different set of tools. The example below shows the resulting JSON for the previously presented &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt; sample spec:&lt;/p&gt;
&lt;details open&gt;&lt;summary&gt;Sample json result&lt;/summary&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;afters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n  subject = nil;\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;314:341&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;befores&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n  subject = [[User alloc] init];\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;266:310&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n      subject.hasConfirmedEmail = NO;\n    &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;424:477&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;389:579&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n      subject.hasConfirmedEmail = YES;\n    &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;629:683&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;585:787&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;contexts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;389:579&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;isSharedExample&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email confirmed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;345:794&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;585:787&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;isSharedExample&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email not confirmed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;345:794&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;345:794&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;isSharedExample&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-canSubscribeToNewsletter&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;includes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;its&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n      NSAssertTrue([subject canSubscribeToNewsletter]);\n    &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;489:568&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;is true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;389:579&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\n      NSAssertFalse([subject canSubscribeToNewsletter]);\n    &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;695:776&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;is false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;585:787&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;spec-name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;UserSpec&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;vars&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__block User *subject&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0:263&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;h2&gt;Interpreting JSON Results&lt;/h2&gt;
&lt;p&gt;To correctly generate an &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; test from a spec, we needed to perform multiple tasks on matched nodes in order to get valid Objective-C code that Xcode would be able to execute. For the sake of convenience, I decided to do the rest of the work in Ruby, given the vast number of gems — like &lt;a href=&quot;https://github.com/erikhuda/thor&quot;&gt;Thor&lt;/a&gt; and &lt;a href=&quot;https://rubygems.org/gems/activesupport/versions/5.0.0.1&quot;&gt;activesupport&lt;/a&gt; — available to create command-line tools and easily manipulate strings.&lt;/p&gt;
&lt;h2&gt;Generating an XCTestCase Class&lt;/h2&gt;
&lt;p&gt;It’d make for an excessively long post to go over all the tasks involved in the transformation process whose main purpose is to replicate the flow of control of a spec. So instead, here’s a list of the most relevant ones I had to perform on the resulting JSON file to generate the &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate the &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; skeleton class.&lt;/li&gt;
&lt;li&gt;Copy over the inclusion directives.&lt;/li&gt;
&lt;li&gt;Copy the variable declarations and handle potential duplicates coming from different contexts of the same spec.&lt;/li&gt;
&lt;li&gt;Generate a main setup method that overrides &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;setUp&lt;/code&gt; method and other setup methods specific to every test from the elements inside &lt;code class=&quot;language-text&quot;&gt;befores&lt;/code&gt; in the JSON file.&lt;/li&gt;
&lt;li&gt;Generate a teardown method from the spec’s &lt;code class=&quot;language-text&quot;&gt;after&lt;/code&gt; calls.&lt;/li&gt;
&lt;li&gt;Generate test methods with names computed according to the context they’re in and insert calls to the corresponding setup methods to configure the state before running the validations.&lt;/li&gt;
&lt;li&gt;Format the output using &lt;code class=&quot;language-text&quot;&gt;clang-format&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To take care of these requirements, I created multiple parsers for processing the JSON and outputting different parts of the &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; test. The resulting tool is a Ruby gem that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executes the parser based on LibTooling that generates the JSON file.&lt;/li&gt;
&lt;li&gt;Calls all different parsers to generate the &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class and merges their results together.&lt;/li&gt;
&lt;li&gt;Formats the resulting &lt;code class=&quot;language-text&quot;&gt;XCTestCase&lt;/code&gt; class using &lt;code class=&quot;language-text&quot;&gt;clang-format&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;Once the tool was created, transforming a spec into &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; went from taking hours to taking only a couple of minutes. This allowed us to transform our 900+ specs with confidence and ease. The last spec was recently transformed and we were consequently able to remove Specta from our codebase and free ourselves from this third-party coupling, making the project a total success! 🎉&lt;/p&gt;
&lt;p&gt;But as is the case with everything, it wasn’t all perfect. So here are some points to keep in mind if you are considering building a refactoring tool with LibTooling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Poor documentation — The documentation on how to create these tools is limited, and on top of that, the documentation available is often outdated since the C++ API offers no API stability guarantees.&lt;/li&gt;
&lt;li&gt;Edge cases — Even after having created the &lt;code class=&quot;language-text&quot;&gt;ASTMatcher&lt;/code&gt;s to match the specs, we had to go back to and expand them to support edge cases we hadn’t detected before. This is natural when refactoring so many different sources — especially when they’re written in a language that allows for equivalent expressions to be written in several different ways.&lt;/li&gt;
&lt;li&gt;Missing matchers — Despite the large number of matchers &lt;a href=&quot;https://clang.llvm.org/docs/LibASTMatchersReference.html&quot;&gt;available&lt;/a&gt;, sometimes you might want to match something a matcher is not available for, forcing you to write your own custom matcher.&lt;/li&gt;
&lt;li&gt;Working on C++ — It depends on your level of writing and reading C++, but if, like me, you’re used to working with more high-level languages, using C++ to write a refactoring tool might feel like adding complexity to a task that can, by itself, be quite daunting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Creating your own refactoring tools requires an upfront work investment, and whether or not this investment is justified depends on each case. For the right situations, building a refactoring tool that is tailored to your problem can prove to be a high-return investment. For us, we went from needing hours of tedious work to transform a spec to being able to just run a command and get the output instantly! This type of automation results in freed-up resources for other tasks like building new features, which is a huge benefit for companies dealing with large amounts of legacy code.&lt;/p&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://clang.llvm.org/docs/LibASTMatchersTutorial.html&quot;&gt;Tutorial for building tools using LibTooling and LibASTMatchers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://clang.llvm.org/docs/LibASTMatchers.html&quot;&gt;Matching the Clang AST&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/2014/05/01/modern-source-to-source-transformation-with-clang-and-libtooling&quot;&gt;Modern source-to-source transformation with Clang and libTooling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[DeveloperBridge: SoundCloud’s Program for Training People from Diverse Backgrounds to Become Engineers]]></title><description><![CDATA[DeveloperBridge is a year-long, full-time, paid traineeship program where participants learn from and work with engineering teams at…]]></description><link>https://developers.soundcloud.com/blog/developerbridge-training-program</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/developerbridge-training-program</guid><pubDate>Fri, 17 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;DeveloperBridge is a year-long, full-time, paid traineeship program where participants learn from and work with engineering teams at SoundCloud. &lt;a href=&quot;https://developers.soundcloud.com/blog/insights-from-the-developerbridge-programme&quot;&gt;The DeveloperBridge program&lt;/a&gt; was launched in 2017 with the goal of further supporting diversity and inclusion at SoundCloud and giving back to the tech community. Now it’s 2020, and we just kicked off the second edition of the initiative.&lt;/p&gt;
&lt;p&gt;The program is designed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Give back to the community and support the Berlin tech scene by creating junior engineering roles that require no previous experience.&lt;/li&gt;
&lt;li&gt;Provide an opportunity to people passionate about engineering and who haven’t yet had a chance to kickstart their careers.&lt;/li&gt;
&lt;li&gt;Support underrepresented groups and people from marginalized communities.&lt;/li&gt;
&lt;li&gt;Promote junior talent.&lt;/li&gt;
&lt;li&gt;Improve diversity in the organization and create a more inclusive environment.&lt;/li&gt;
&lt;li&gt;Benefit from fresh perspectives and be reflective of our diverse user base.&lt;/li&gt;
&lt;li&gt;Give our engineers an opportunity to mentor and coach.&lt;/li&gt;
&lt;li&gt;Hire trainees to full-time positions at the end of the program.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Organizing such a program is a logistical challenge that requires cross-team effort and collaboration. We started recruitment in January 2020, and in the job description, we were transparent about our offering, process, and timeline. To create a diverse pipeline and attract our target audience, we promoted the program on different platforms and at various organizations, such as &lt;a href=&quot;https://www.redi-school.org/&quot;&gt;ReDI School of Digital Integration&lt;/a&gt;, &lt;a href=&quot;https://www.womentech.net/en-de&quot;&gt;WomenTech&lt;/a&gt;, &lt;a href=&quot;https://www.womenwhocode.com/&quot;&gt;Women Who Code&lt;/a&gt;, and the &lt;a href=&quot;https://www.meetup.com/women-who-go-berlin/&quot;&gt;Women Who Go&lt;/a&gt; course, which SoundCloud hosted in the past.&lt;/p&gt;
&lt;p&gt;After one month, we received an astonishing number of applications — 400 to be exact. Together with volunteers from Engineering, we screened the CVs and filtered out applicants who were already professional or full-time developers. The next step was a technical one, consisting of two take-home programming tasks. Since our participants are not experienced engineers, we didn’t require them to actually complete the tasks. Instead, we focused on their thought processes and problem-solving skills.&lt;/p&gt;
&lt;p&gt;At the end of this, we invited the 21 highest scorers to the final onsite day. During the day, the applicants learned about SoundCloud, took part in introduction sessions with engineering leadership and DeveloperBridge 2017 alumni, and met members of the SoundCloud team for informal lunches. They also had two short interview sessions: pair programming and barkeeper (barkeepers are senior engineers who represent the technical excellency, culture, and mindset of SoundCloud).&lt;/p&gt;
&lt;p&gt;Organizing the onsite day was a huge logistical task that required support from volunteers across the whole organization. In the end, 37 people supported the onsite day by interviewing, conducting introductory sessions, chaperoning, and hanging out with the participants during lunch. It was great to receive this support from the engineering team.&lt;/p&gt;
&lt;p&gt;Best of all, the onsite day was successful: We made eight offers instead of the seven we had initially planned for, and all of them were accepted!&lt;/p&gt;
&lt;p&gt;Here is what one of the finalists, Kaylee, thought of the recruitment process: “From the beginning until now, it has been an exciting, challenging, fair, and rewarding experience. The programming challenges brought an extensive range of brain stimulation, while the personal interview session at SoundCloud was welcoming and relaxing. Live coding with the senior developer was also fun and interesting.”&lt;/p&gt;
&lt;p&gt;The next step was finding projects and teams for the trainees. We wanted this process to be as inclusive and interactive as possible, in order to cater to the interests of the trainees. We came up with the idea of a “Pitch Day,” which is where engineering managers would pitch their teams and projects to the trainees. That way, the new joiners could also meet their future colleagues and learn more about the actual tasks and projects. Neslihan, one of the participants said: “I am impressed with the genuine interest in finding us the project that we would be passionate about.”&lt;/p&gt;
&lt;p&gt;The preparations didn’t stop there. Volunteers from Engineering started working on the training program to onboard the new joiners to all things SoundCloud: tech stack and systems, ways of working, internal policies and processes, and career progressions. In addition to the training program, the new joiners will have mentors and buddies who are also volunteers from the engineering team.&lt;/p&gt;
&lt;p&gt;In addition to the benefits for the new joiners, DeveloperBridge has already had huge positive effects on the company. For example, the training coordination group is planning on scaling its training program into a regular onboarding, junior colleagues had the chance to participate in a recruiting process, and colleagues were excited about running the program — this is something they drew additional motivation from in their day-to-day work.&lt;/p&gt;
&lt;p&gt;Organizing and executing DeveloperBridge has been a great learning experience for the recruitment team. It is also extremely rewarding to give back to the community by supporting junior talent and giving opportunities to people passionate about programming. We so excited for our eight trainees — Ekaterina, Kaylee, Margaret, Tristan, Oscar, Matan, Neslihan, and Evgenija — to start their SoundCloud journeys.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Changing the Interview Process during Remote Working]]></title><description><![CDATA[Please also see Part 1: Rethinking the Backend Engineering Interview Take-Home Challenge and Part 2: The Recruiting Perspective and Results…]]></description><link>https://developers.soundcloud.com/blog/changing-interview-process-during-remote-working</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/changing-interview-process-during-remote-working</guid><pubDate>Tue, 30 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Please also see &lt;a href=&quot;https://developers.soundcloud.com/blog/rethinking-the-backend-code-challenge&quot;&gt;Part 1: Rethinking the Backend Engineering Interview Take-Home Challenge&lt;/a&gt; and &lt;a href=&quot;https://developers.soundcloud.com/blog/backend-code-challenge-recruiting-perspective-and-results&quot;&gt;Part 2: The Recruiting Perspective and Results&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the last two blog posts, we talked about how we rethought our backend engineering challenge as a way of improving numbers for recruitment. In the same vein, this post talks about how we changed our hiring strategies to adapt to hiring remotely during COVID-19.&lt;/p&gt;
&lt;p&gt;Onsite engineering interviews at SoundCloud include whiteboarding, pair programming, barkeeper,&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; product manager, and engineering manager interviews. Because of COVID-19, SoundCloud switched to working completely remotely in March of 2020. However, we didn’t let this new setup put a stop to our recruiting efforts.&lt;/p&gt;
&lt;h2&gt;Scheduling Interviews&lt;/h2&gt;
&lt;p&gt;In the past, we had all of a candidate’s interviews scheduled onsite for the same day at our SoundCloud office space. As we moved to a full remote work environment, we made all the interviews virtual. Virtual interviews made us more flexible, as we could spread the interviews across multiple days to better accommodate the availability of the candidates and interviewers.&lt;/p&gt;
&lt;h2&gt;Video Calls for Interviews&lt;/h2&gt;
&lt;p&gt;Out of the five aforementioned interview types, the latter three types were the simplest to make work in a remote setup because they mostly consist of a verbal dialogue. To carry out these interviews, we set up video calls between the interviewers and the candidates. The candidates were given details about this in advance so that they could try out the setup and make sure they had good audio and video connectivity.&lt;/p&gt;
&lt;p&gt;When doing this, we discovered we could get similar signals about a candidate’s proficiency through a video call as we would in an in-person interview. The interviewers also found it was easier to take notes during the call without it being awkward. And the candidates seemed more comfortable taking part in the interview in spaces they were familiar with.&lt;/p&gt;
&lt;p&gt;We used Slack to coordinate with one another internally when someone finished an interview to make sure we allowed enough time for the candidate to take a quick break and refresh before the next interview began. This was also a helpful tool for communicating with recruiters — for example, if the candidate couldn’t join on time or had to drop off the call because of any connectivity issues. There was even an instance where we sent the incorrect VC link to the candidate, and having a running conversation on Slack helped us quickly communicate with the recruiter and thereby the candidate.&lt;/p&gt;
&lt;h2&gt;Tools for Whiteboard and Pairing Interviews&lt;/h2&gt;
&lt;p&gt;Whiteboard and pairing interviews were a bit more challenging to adapt to a remote setting, as a big portion of our technical assessment happens over these sessions. Running these interviews in a remote context forced us to invest in better tools, familiarize the interviewers with them, and send out material to candidates in advance to allow them to also understand the tools.&lt;/p&gt;
&lt;p&gt;For the system design interview, we used Miro as a whiteboard tool. The candidates could use it to sketch out the system design and walk through their ideas. The first time, we ran into unanticipated permission issues that had to be resolved just before the call. We quickly learned from this and started setting up the Miro board and sharing it with candidates in advance. These virtual boards also made it easier for interviewers to go back and review the work if necessary before filling out the feedback for candidates.&lt;/p&gt;
&lt;p&gt;For the code pairing session, we opted to use video conferencing along with screen sharing. This allowed candidates to use the IDE of their choice. However, the interviewer missed out on being able to contribute or point to specific lines of code. It was also difficult when candidates paused and explained before or while making their code changes. As a result, the interviewers needed to be explicit about asking probing questions and clarifying things to be able to keep up with a candidate’s train of thought.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;In addition to us learning directly through this experience, we tailored our post-interview feedback forms from the candidates to get their feedback on the interviewing experience. We also collected feedback from the interviewers to make possible improvements and reevaluate the tools.&lt;/p&gt;
&lt;h2&gt;Ongoing Challenges&lt;/h2&gt;
&lt;p&gt;The entire experience of running interviews has been an ongoing learning process for us and we are still trying to find optimal solutions to some of the challenges. For example, we haven’t found a good replacement for the casual lunch session we had in our onsite interview day. We tried having a casual video call as an alternative, but we haven’t found it to be as equally informal as having lunch together.&lt;/p&gt;
&lt;p&gt;Another challenge has been to find a best practice for sharing access to our remote interview tools with the candidates within a reasonable timeframe and in a way that makes it clear how to set up and use them.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;When initially faced with the challenges of moving our interviews to a remote setting, it seemed like the overhead of setting up tools and video conferencing in advance would be a lot of work on our part. However, through feedback and iteration, this became a seamless part of our hiring flow. As a result, we were able to continue to hit our hiring goals, even in the midst of a global pandemic.&lt;/p&gt;
&lt;p&gt;Overall, running our full onsite interview days in a completely remote setup has been a huge learning experience. It allowed us to be more flexible, empathetic, and inclusive, and we will continue to use these learnings to improve our hiring moving forward.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;Barkeepers bring an outside perspective to the hiring team. They represent the technical excellency, culture, and mindset of SoundCloud. They have a history of hiring effective engineers, experience running interviews, and technical depth in multiple areas.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Technical Interview Reform, Part 2: The Recruiting Perspective and Results]]></title><description><![CDATA[Please also see Part 1: Rethinking the Backend Engineering Interview Take-Home Challenge Among the engineering groups at SoundCloud, backend…]]></description><link>https://developers.soundcloud.com/blog/backend-code-challenge-recruiting-perspective-and-results</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/backend-code-challenge-recruiting-perspective-and-results</guid><pubDate>Fri, 26 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Please also see &lt;a href=&quot;https://developers.soundcloud.com/blog/rethinking-the-backend-code-challenge&quot;&gt;Part 1: Rethinking the Backend Engineering Interview Take-Home Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Among the engineering groups at SoundCloud, backend engineering has always had the highest number of open roles — at times reaching 10 positions. To fulfill headcount plans in the current fast-paced market, recruitment needs to move quickly and put efficient processes in place.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we have designed a three-step process to enable us to move candidates through the pipeline quickly: recruitment screening, challenge, and final onsite round. Each step has to provide a clear signal as to if the candidate is a good match for the role we’re trying to fill.&lt;/p&gt;
&lt;p&gt;However, in the past, we’d experienced challenges with high dropout and time-to-hire rates, as well as with the low ratio of challenges passed to offers made. Having analyzed the data, we identified the code challenge step as the bottleneck from the recruitment perspective.&lt;/p&gt;
&lt;p&gt;According to the candidate feedback, it took anywhere from 10 to 20 hours to complete the backend code challenge task. The turnaround between a candidate receiving the test and sending it back was three weeks on average, and it took around a week for engineers to review a submission.&lt;/p&gt;
&lt;p&gt;This is a big time investment to ask from candidates, and it is hard for them to commit to such a time-consuming task while having a full-time job. It is even less inclusive of people with families and kids or more junior candidates. As a result, the dropout rates during the challenge steps were high. All these factors contributed to the lengthy process and not always the best candidate experience.&lt;/p&gt;
&lt;p&gt;On a positive note, people who invested the time to complete the challenge were definitely motivated to join SoundCloud. :)&lt;/p&gt;
&lt;h2&gt;Process Changes&lt;/h2&gt;
&lt;p&gt;To initiate the change in the process, the recruitment team worked closely with the members of the leadership and engineering teams. We analyzed the data, proposed a possible solution, and issued an internal RFC (“request for comments”). Recruitment and engineering collaborated on developing an efficient process and constantly iterating based on the candidate feedback.&lt;/p&gt;
&lt;p&gt;To onboard challenge reviewers and interviewers, we introduced challenge review squads. A squad consists of more experienced and less experienced reviewers who pair and learn from each other. The squads change every 6–8 weeks so that everyone can participate and get onboarded to become reviewers of the new challenge.&lt;/p&gt;
&lt;p&gt;In addition, engineers are able to better manage their time with this process in place: they commit for 6–8 weeks, and when that time has passed, they can be free of the challenge reviews for the rest of the year if they want to.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The new backend challenge was implemented in October 2019, and the candidate feedback has been great: The time commitment is now 5–6 hours. The new PR review tasks received a positive response as well: Candidates often find this part of the challenge interesting, and they consider it a refreshing change from the norms they encounter in technical interviewing.&lt;/p&gt;
&lt;p&gt;The turnaround time between the candidate receiving the challenge and sending back the solution went down to an average of seven days (a decrease of 60 percent). The new challenge also provides stronger signals: Conversion rates (challenge completed/offer made) increased from 35 percent to 50 percent.&lt;/p&gt;
&lt;p&gt;As an interesting side effect, the new backend code challenge positively contributed to the company’s focus on diversity in hiring: Since implementing it in October 2019, 30 percent of the offers we’ve made have been to non-male engineers.&lt;/p&gt;
&lt;p&gt;After seeing such significant positive results of the new backend challenge process, we are now working on making similar changes in other engineering coding interviews.&lt;/p&gt;
&lt;h2&gt;What about COVID-19?&lt;/h2&gt;
&lt;p&gt;An upcoming bonus post will outline changes we’ve had to make and challenges we’ve faced adjusting our technical interviewing process in the COVID-19 era.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Technical Interview Reform, Part 1: Rethinking the Backend Engineering Interview Take-Home Challenge]]></title><description><![CDATA[Most SoundCloud backend engineers have good feelings about the old backend engineering take-home challenge. It’s commonly been characterized…]]></description><link>https://developers.soundcloud.com/blog/rethinking-the-backend-code-challenge</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/rethinking-the-backend-code-challenge</guid><pubDate>Thu, 25 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most SoundCloud backend engineers have good feelings about the old backend engineering take-home challenge. It’s commonly been characterized as a fun, thought-provoking coding problem.&lt;/p&gt;
&lt;p&gt;However, most of us took a significant amount of time to complete it. In fact, reports of having worked on it for 12 hours or more, over a timespan of two or more weeks, are not uncommon to hear. Candidates coming through the hiring process reported in feedback to their recruiters that they were straining under the time burden, and we clearly saw this reflected in the form of a significant dropoff at this stage in our hiring pipeline data.&lt;/p&gt;
&lt;p&gt;This makes intuitive sense: Many great candidates have good jobs and busy personal lives. We want to talk to as many qualified candidates as possible, but to do that, we need to minimize the chances that our interview process itself gets in the way.&lt;/p&gt;
&lt;p&gt;As a result, engineering management challenged engineers and recruiters to improve the situation. We set out to patch the obvious problems, but we ended up comprehensively rethinking the backend engineering take-home challenge.&lt;/p&gt;
&lt;h2&gt;The Tryout&lt;/h2&gt;
&lt;p&gt;The ability to compose, change, and critique programs is only one dimension of an engineer’s job — but it’s an essential one. Similar to an orchestra or acting audition, we use the take-home test to get an initial sense of how a candidate performs. We judge the performance with respect to what we believe it takes to be successful as an engineer in this organization.&lt;/p&gt;
&lt;p&gt;The form a “code tryout” should take is a hotly debated topic, in our view, partly because contextual factors heavily constrain the format possibilities. It’s clear to us that there’s no one ideal answer that fits all engineering organizations.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;take-home challenge format&lt;/strong&gt; has historically been a natural fit for SoundCloud for a couple of key reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We hire engineers from all over the world, so the decision to bring a candidate onsite means there’s a significant time investment to be made by both the candidate and SoundCloud. While the onsite is certainly no guarantee of an offer, a key goal is to maximize the odds that candidates brought onsite will excel and get an offer.&lt;/li&gt;
&lt;li&gt;This format presents a unique opportunity — and in the very first stage of a candidacy — to eliminate unwanted bias in evaluating candidate performance. We treat the take-home challenge as a kind of &lt;a href=&quot;https://en.wikipedia.org/wiki/Blind_audition&quot;&gt;anonymous audition&lt;/a&gt;, and we do our best to ensure reviewers do not have any knowledge of irrelevant personal attributes of a candidate when judging submissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We want our initial evaluation of a candidate to be based on what they can do given more ideal conditions than short onsite coding interviews allow for.&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; This means having enough time to really concentrate and produce work they’re proud of. Candidates often report that this is a refreshing change when compared to industry norms.&lt;/li&gt;
&lt;li&gt;SoundCloud engineers who review submissions are more confident in their ability to be fair and consistent if they are able to compare and contrast work that is technically consistent from candidate to candidate over time. In part because the programming problems are consistent, experienced reviewers can productively “train up” those who are new to the review process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Same Coding Problem, but Faster to Complete and Review&lt;/h2&gt;
&lt;p&gt;After much discussion, we guessed we could take the same problem, change the format a little, put some modest constraints on how the solution could be solved, and still get as good of an indication of a candidate’s likelihood to succeed in the onsite interview as we did with the old challenge.&lt;/p&gt;
&lt;p&gt;Here’s what we changed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of building from scratch, it’s now a refactoring challenge plus a small feature extension. It comes complete with a tester program that passes “out of the box.” The candidate’s first goal is to keep that tester passing while making the code a lot better.&lt;/li&gt;
&lt;li&gt;Partly as a consequence, we offer the choice of selecting from six languages to solve the problem, with an emphasis on languages that are popular in the industry and academia. In the past, candidates could choose any language they wanted, but with a refactoring exercise, this is no longer possible.&lt;/li&gt;
&lt;li&gt;We decided to restrict solutions to using only the standard library for the chosen language.&lt;/li&gt;
&lt;li&gt;We revised instructions and READMEs to be more thorough and clearer in what our expectations are.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This plan was motivated by a goal to reduce the burden on the candidate. But we quickly realized that reducing the burden on reviewers is almost as important. Efficient and consistent review means we can get back to candidates with a response more quickly — but it also opens up opportunities for us to be fairer.&lt;/p&gt;
&lt;p&gt;Each submission is evaluated by two people independently. They then meet up and file a “consensus review” signed off by both of them. But code review is a human activity, and reviewers will often end up in disagreement over a submission. When reviews are quick, it’s easy to pull in tiebreaker reviewers and still promptly get back to the candidate with a response.&lt;/p&gt;
&lt;h2&gt;Adding Pull Request Review&lt;/h2&gt;
&lt;p&gt;Inspired by Slack engineering’s &lt;a href=&quot;https://slack.engineering/refactoring-backend-engineering-hiring-at-slack-b53b1e0e7a3c&quot;&gt;pull request-based challenge&lt;/a&gt;, we decided to add a small PR challenge of our own to the backend engineering take-home challenge.&lt;/p&gt;
&lt;p&gt;PR review allows us to get some insight into how candidates give critique, and the additional code problem allows us to probe a couple other technical areas. In exchange for a modest amount of extra time to complete the code challenge, candidates get an important new channel through which they can show reviewers how they approach these important aspects of the day-to-day work in engineering.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;Please see &lt;a href=&quot;https://developers.soundcloud.com/blog/backend-code-challenge-recruiting-perspective-and-results&quot;&gt;part 2&lt;/a&gt; of this series for the recruiting take on the coding challenge reform, along with a report on both quantitative and qualitative results.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;h3&gt;General&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.lihaoyi.com/post/HowtoconductagoodProgrammingInterview.html&quot;&gt;How to Conduct a Good Programming Interview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.joelonsoftware.com/2006/10/25/the-guerrilla-guide-to-interviewing-version-30/&quot;&gt;The Guerrilla Guide to Interviewing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nytimes.com/2013/06/20/business/in-head-hunting-big-data-may-not-be-such-a-big-deal.html&quot;&gt;In Head-Hunting, Big Data May Not Be Such a Big Deal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Slack Engineering&lt;/h3&gt;
&lt;p&gt;We found the series of posts on technical interviewing from Slack Engineering to be particularly helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://slack.engineering/a-walkthrough-guide-to-finding-an-engineering-job-at-slack-dc07dd7b0144?gi=b349f6001b69&quot;&gt;A Walkthrough Guide to Finding an Engineering Job at Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://slack.engineering/refactoring-backend-engineering-hiring-at-slack-b53b1e0e7a3c&quot;&gt;Refactoring Backend Engineering Hiring at Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://slack.engineering/how-about-code-reviews-2695fb10d034&quot;&gt;How About Code Reviews?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These prompted us to create our own form of a PR-based challenge.&lt;/p&gt;
&lt;h3&gt;SoundCloud History&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Phil Calçado’s post, &lt;a href=&quot;https://philcalcado.com/2016/03/15/on_asking_job_candidates_to_code.html&quot;&gt;On Asking Job Candidates to Code&lt;/a&gt;, contains an excellent history of the SoundCloud coding challenge.&lt;/li&gt;
&lt;li&gt;Peter Vida described our &lt;a href=&quot;https://developers.soundcloud.com/blog/engineering-levels&quot;&gt;engineering level system&lt;/a&gt; in a past Backstage Blog post.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Thank You&lt;/h2&gt;
&lt;p&gt;Much of SoundCloud engineering, recruiting, and management contributed to this in some way. Big thanks go out to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anya Voronova&lt;/li&gt;
&lt;li&gt;Ben Stahl&lt;/li&gt;
&lt;li&gt;Matthias Käppler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We would also like to thank Slack Engineering for their time and advice as we built our version of the PR challenge, in particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chase Rutherford-Jenkins&lt;/li&gt;
&lt;li&gt;Maude Lemaire&lt;/li&gt;
&lt;li&gt;Will Kimeria&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;You know the drill: You have 60 minutes, a web browser coding environment or whiteboard, the problem is straight out of an Algorithms and Data Structures class, and the interviewer mostly sits there in awkward silence. Some companies see such high-pressure conditions as a good thing. We don’t.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Announcing Twinagle: Twirp and Protobuf for Finagle]]></title><description><![CDATA[A previous post on this blog ended with the following paragraph: “We might also replace JSON with a more efficient serialization protocol…]]></description><link>https://developers.soundcloud.com/blog/announcing-twinagle</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/announcing-twinagle</guid><pubDate>Fri, 12 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;a href=&quot;https://developers.soundcloud.com/blog/synchronous-communication-for-microservices-current-status-and-learnings&quot;&gt;previous post&lt;/a&gt; on this blog ended with the following paragraph:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We might also replace JSON with a more efficient serialization protocol like &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;protocol buffers&lt;/a&gt;. This would further improve performance and should result in less handwritten serialization and deserialization code but this still needs more experimentation and investigation.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To follow up on the above, this article announces the release of Twinagle, an open source implementation of the Twirp protocol for Scala/Finagle.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The backend of SoundCloud is designed around a set of &lt;a href=&quot;https://developers.soundcloud.com/blog/category/microservices&quot;&gt;microservices&lt;/a&gt;. As &lt;a href=&quot;https://developers.soundcloud.com/blog/synchronous-communication-for-microservices-current-status-and-learnings&quot;&gt;detailed previously&lt;/a&gt;, services expose a REST-like interface and use HTTP and JSON to communicate. Our services are usually implemented in Scala, making use of Twitter’s Finagle library, but we have a fair amount of Ruby and Go powering production as well.&lt;/p&gt;
&lt;p&gt;This choice has worked well for us, because tooling around HTTP and JSON is widespread and allows for easy debugging by humans. However, the very flexibility offered by the stack has, in significant ways, slowed down development. This is because, for each endpoint, developers need to decide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which HTTP method to use&lt;/li&gt;
&lt;li&gt;Whether to expose parameters in the path or as query parameters&lt;/li&gt;
&lt;li&gt;How to structure the response&lt;/li&gt;
&lt;li&gt;How to name fields in JSON objects&lt;/li&gt;
&lt;li&gt;How to represent common data types, such as time stamps and durations&lt;/li&gt;
&lt;li&gt;How to represent error conditions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Internal guidelines and recommendations have only helped so much — variations tend to creep in between teams and over time. Small inconsistencies between server and client implementations have caused outages (e.g. a client mistakenly made use of camelCase for field names but the server expected snake_case).&lt;/p&gt;
&lt;p&gt;The “artisanal” approach to HTTP API development and consumption can be frustrating: Even where guidelines are followed perfectly, an engineer still needs to forensically examine application code in order to get comprehensive information about how any given endpoint works.&lt;/p&gt;
&lt;p&gt;As a result of discussion in our weekly engineering forum, a subgroup of engineers decided to explore alternatives. The group honed in on &lt;a href=&quot;https://blog.twitch.tv/en/2018/01/16/twirp-a-sweet-new-rpc-framework-for-go-5f2febbf35f/&quot;&gt;Twirp&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why Twirp?&lt;/h2&gt;
&lt;p&gt;Twitch engineering created the &lt;a href=&quot;https://blog.twitch.tv/en/2018/01/16/twirp-a-sweet-new-rpc-framework-for-go-5f2febbf35f/&quot;&gt;Twirp&lt;/a&gt; project, which is a Go-based server implementation of the Twirp &lt;a href=&quot;https://twitchtv.github.io/twirp/docs/spec_v5.html&quot;&gt;wire protocol&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The Twirp wire protocol is a simple RPC protocol based on HTTP and Protocol Buffers (proto). The protocol uses HTTP URLs to specify the RPC endpoints, and sends/receives proto messages as HTTP request/response bodies.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Engineers compose API contracts in the protobuf Interface Description Language (IDL). They then use those contracts to generate server stubs and clients using protobuf tooling. The generated code takes care of HTTP request/response parsing and serialization to either JSON or the binary protobuf format. All requests are simple HTTP POST. And the wire protocol itself is simple and well-thought-out (thank you, Twirp engineering).&lt;/p&gt;
&lt;p&gt;Twirp seemed like a great fit: It enabled us to start introducing IDLs to simplify microservice development in a way that incrementally “slotted into” a complex backend. We looked at gRPC but decided against it, and our reasons for this decision match very well with those stated in the original &lt;a href=&quot;https://blog.twitch.tv/en/2018/01/16/twirp-a-sweet-new-rpc-framework-for-go-5f2febbf35f/&quot;&gt;Twirp blog post&lt;/a&gt;). We already run some Go services, and Twirp has a Go implementation we could just drop in and use.&lt;/p&gt;
&lt;p&gt;However, more than 70 percent of microservices we deploy are based on Scala + Finagle…&lt;/p&gt;
&lt;h2&gt;Twinagle = Twirp + Finagle&lt;/h2&gt;
&lt;p&gt;Due to the simplicity of the Twirp protocol, we decided to implement it ourselves. And we are pleased to announce the release of &lt;a href=&quot;https://soundcloud.github.io/twinagle/&quot;&gt;Twinagle&lt;/a&gt;, our implementation of Twirp for Finagle.&lt;/p&gt;
&lt;p&gt;The project is split into two components, outlined below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An SBT plugin that takes a protobuf interface and uses it to generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a trait that defines the service’s interface&lt;/li&gt;
&lt;li&gt;code that implements both the server-side and client-side of this interface.
This includes (de)serialization of requests and response bodies, error handling,
and transmitting data over the wire.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A runtime library that implements the logic to route requests,
performs (de)serialization of messages, and integrates with Finagle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For instructions and code examples, please see the &lt;a href=&quot;https://soundcloud.github.io/twinagle/&quot;&gt;Twinagle project page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Migrating a Service&lt;/h2&gt;
&lt;h3&gt;About the Track Authorization Service&lt;/h3&gt;
&lt;p&gt;The first high-impact production service at SoundCloud to adopt Twinagle was our track authorization service. This service is responsible for deciding how users can access tracks on SoundCloud based on the user’s location, client application, and subscription status. Should they hear an ad? Should they be able to see the track exists in other countries? Every time the SoundCloud backend handles a track identifier, this service gets a request. Needless to say, this service receives a large number of requests.&lt;/p&gt;
&lt;p&gt;In addition, JSON response bodies for track lookups can be large: Up to 100 tracks may be requested at a time, and in practice, the average request is for 25 tracks. However, the structure of these responses is not complex; it consists of a long list of relatively simple objects.&lt;/p&gt;
&lt;p&gt;The track authorization service is only used by one client application, meaning we only needed to perform one client migration. A combination of systems-integration simplicity and handling of significant request load made this service a natural early candidate for conversion to Twinagle.&lt;/p&gt;
&lt;h3&gt;Code Conversion&lt;/h3&gt;
&lt;p&gt;First we implemented a new Twinagle-based endpoint in the authorization service. We defined messages in the protobuf file that are based on existing domain model classes found in the service’s codebase, and we implemented a thin layer to convert between both. In exchange for this small amount of extra boilerplate code, we were able to reuse the existing business logic code, untouched. As a result, we could limit potential bugs to serialization and deserialization code.&lt;/p&gt;
&lt;p&gt;Once we deployed the new endpoint, we brought the Twirp library and protobuf file into the client application. We took a similar approach to the client side as we did on the server, building in a thin (de)serialization layer that isolates the existing business logic.&lt;/p&gt;
&lt;h3&gt;Rollout&lt;/h3&gt;
&lt;p&gt;Then we had the option of using both the REST and Twirp versions of the endpoint from the client app. We redirected client traffic using a percentage-based rollout flag in two phases. In the first phase, we made duplicate requests and compared the results. This meant that with a rollout percentage of 1 percent, the client made a request to both the REST and Twirp endpoints 1 percent of the time and reported discrepancies in the responses, but returned only the REST endpoint’s response. We caught a few critical bugs this way. We then moved on to use the Twirp endpoint only — and to stop duplicating requests. We rolled out to 100 percent with no outages and no bugs on the first attempt as a result of this process.&lt;/p&gt;
&lt;h3&gt;Findings&lt;/h3&gt;
&lt;p&gt;The results of this migration were positive but produced some mild surprises. We expected a drop in response time, but we only saw marginal benefits in the 90th percentile, and no benefits in the 50th or 99th percentiles. This was likely because most of the response time comes from accessing the database, and not from request/response serialization and deserialization. Additionally, the variability of 90th percentile latency dropped significantly.&lt;/p&gt;
&lt;p&gt;We saw a huge drop in bandwidth between the two services; network usage decreased by more than 40 percent when using binary protobuf instead of JSON! We also discovered that many fields in the response were being ignored by the client application, and we were able to confidently clean things up in the new Twirp endpoint. Finally, we deleted all of the custom JSON serialization and deserialization code in both services, which is always a satisfying developer experience!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This initial service conversion confirmed our hopes for Twinagle: Protobuf contracts make services easier to understand, allow us to get rid of an entire class of low-value code, and even result in some nice efficiency benefits. We are actively converting old services to use Twinagle, and all new SoundCloud services are now Twinagle-based. To try it out for yourself, check out the &lt;a href=&quot;https://soundcloud.github.io/twinagle/&quot;&gt;project page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Thank You&lt;/h2&gt;
&lt;p&gt;The implementation and rollout of Twinagle has been a great cross-team effort. Special thanks to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bernd Louis&lt;/li&gt;
&lt;li&gt;Frank Calma&lt;/li&gt;
&lt;li&gt;Jorge Creixell&lt;/li&gt;
&lt;li&gt;Niko Dziemba&lt;/li&gt;
&lt;li&gt;Sean Treadway&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And extra special thanks to Twitch engineering, for Twirp!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Stretch Opportunities for Engineers]]></title><description><![CDATA[Stretch opportunities are tasks or projects that are a bit beyond your current skill or knowledge level and that allow you to improve and…]]></description><link>https://developers.soundcloud.com/blog/stretch-opportunities-for-engineers</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/stretch-opportunities-for-engineers</guid><pubDate>Wed, 20 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://blog.iawomen.com/how-to-identify-stretch-opportunities-for-professional-growth/&quot;&gt;Stretch opportunities&lt;/a&gt; are tasks or projects that are a bit beyond your current skill or knowledge level and that allow you to improve and learn new things. They are also essential for growth. At SoundCloud, some teams introduced a process so engineers can take on stretch opportunities. In this blog post, I’ll explain how this works for the Content Team.&lt;/p&gt;
&lt;p&gt;The need for stretch opportunities within the Content Team was raised in a retrospective. An engineer stated that they wanted to broaden their role and be exposed to requirement gathering, meetings with stakeholders, and driving new projects. Other engineers also showed interest, so we began discussing as a team what that would mean and how we could accomplish this. Based on these discussions, we ended up introducing the Project Lead role.&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The Project Lead role is a voluntary non-permanent role that a team member gets assigned for the duration of a project. We assign the role for a new project when the current project is coming to completion and the new project will become the next main focus for the team.&lt;/p&gt;
&lt;h2&gt;Role Responsibilities&lt;/h2&gt;
&lt;p&gt;The team member in the Project Lead role takes over some of the responsibilities previously held by the Engineering Manager (EM) while still being supported by them. The responsibilities of a Project Lead are to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be the technical representative for the project within the team but also externally. We expect that they answer questions about the implementation and participate in meetings with stakeholders.&lt;/li&gt;
&lt;li&gt;Write the Request for Comments (&lt;a href=&quot;https://philcalcado.com/2018/11/19/a_structured_rfc_process.html&quot;&gt;RFC&lt;/a&gt;) for the project and request and respond to feedback from the team and external stakeholders.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Be the driver for the execution and delivery of the project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Surface the work that is left and break it up in smaller chunks together with the team.&lt;/li&gt;
&lt;li&gt;Identify what work can happen in parallel and encourage team members to help out.&lt;/li&gt;
&lt;li&gt;Estimate and communicate how many iterations are still needed to complete the project.&lt;/li&gt;
&lt;li&gt;Identify blockers and resolve them or ask for help.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make sure a retrospective happens when the project comes to an end, and find a facilitator for it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Role Restrictions&lt;/h2&gt;
&lt;p&gt;In addition to the responsibilities of the Project Lead, we also came up with a list of restrictions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Project Lead can’t unilaterally make decisions. In case of unresolved conflict, the EM and/or Product Manager (PM) will make the final decision (for example, for different opinions about technical implementations or product/prioritization or anything else).&lt;/li&gt;
&lt;li&gt;The Project Lead shouldn’t work alone. There can be some preparation work that happens in isolation for some time — for example, when writing the RFC — but we want to work as a team on all projects, as we think that getting different perspectives and leveraging the diversity of the team leads to better outcomes.&lt;/li&gt;
&lt;li&gt;We stick to one Project Lead per project. In case of unplanned absence, the EM and PM should be able to cover all the expectations and responsibilities. In case of planned absence (vacation), the Project Lead should find a replacement or alternative solution before leaving, and they should do a handover.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Learnings&lt;/h2&gt;
&lt;p&gt;After using the Project Lead role for several years, we’ve seen many advantages.&lt;/p&gt;
&lt;p&gt;Because the role is temporary and there is support from the EM and PM, it is a safe way for team members to take on additional responsibilities, and it’s an opportunity for growth and getting feedback.&lt;/p&gt;
&lt;p&gt;The voluntary nature of it means that team members can step into the role for a project they feel comfortable with. We have no restrictions on the experience level needed to step into the role, however, we noticed less experienced engineers will typically volunteer for smaller projects, while more experienced engineers are challenged by taking on larger projects with potentially more stakeholders and sometimes dependencies on other teams.&lt;/p&gt;
&lt;p&gt;We see that engineers who took on the Project Lead role, especially for larger projects, are in a better place for getting promoted because of the extra responsibilities they took on and the additional exposure they got outside the team. In fact, some of the Project Lead role responsibilities and expectations match well with some of &lt;a href=&quot;https://developers.soundcloud.com/blog/engineering-levels&quot;&gt;our Level 3 Engineering Level expectations&lt;/a&gt;, in particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drives larger projects reliably by delegating and coordinating&lt;/li&gt;
&lt;li&gt;Coordinates work across team boundaries&lt;/li&gt;
&lt;li&gt;Keeps the team honest and accountable to commitments&lt;/li&gt;
&lt;li&gt;Constantly puts effort into understanding the business/product big picture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Furthermore, delegating some of the responsibilities previously taken on by the EM means that the EM will end up with more time for other things, which can be welcome, especially when leading larger teams.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Project Lead role allows engineers to take on stretch opportunities. It’s also a way of acknowledging all the extra work that might not always be visible otherwise (what Tanya Reilly calls &lt;a href=&quot;https://noidea.dog/glue&quot;&gt;glue work&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The fact that the role is voluntary and on a per-project basis is great, because it means that everyone who wants to take on the role will have a chance to sooner or later. Furthermore, it also works for people who prefer to focus on contributing but not leading.&lt;/p&gt;
&lt;p&gt;The Project Lead role concept may or may not work in your environment, but I believe it is important to create stretch opportunities for your engineers so they can get exposed to new skills, remain engaged, and grow.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;When talking about a project in the context of this blog post, I mean a software development project that has a purpose of reaching a certain outcome. A project can be part of a larger program consisting of multiple projects focused around a common theme.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Open Sourcing Intervene]]></title><description><![CDATA[A little while back, the web team at SoundCloud got an urgent report that our upload page looked weird in the US. Web engineering is based…]]></description><link>https://developers.soundcloud.com/blog/open-sourcing-intervene</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/open-sourcing-intervene</guid><pubDate>Tue, 05 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little while back, the web team at SoundCloud got an urgent report that our upload page looked weird in the US. Web engineering is based in Berlin, and it looked fine outside the US. However, we quickly identified that a change we’d made recently was conflicting with some custom visuals that were only being run for US visitors.&lt;/p&gt;
&lt;p&gt;Rolling back the change was the easy part, but we needed to emulate the API response in Berlin in order to replicate and fix the issue properly. We develop against a single HTTPS test instance of our &lt;a href=&quot;https://www.thoughtworks.com/de/insights/blog/bff-soundcloud&quot;&gt;BFF&lt;/a&gt;, and it’s not trivial to add extra mocking directly into that BFF code (it’s the same code that gets deployed to production). We ended up adding a breakpoint in the browser dev tools where the response was received, and we were able to manually inject the US version of the response. It wasn’t pretty and we weren’t proud, but we were able to replicate and fix the issue.&lt;/p&gt;
&lt;p&gt;Although we’d resolved the issue, I still thought: “There has to be an easier way. After all, we just need to override one endpoint and proxy the rest to the real server, so how hard can it be?”&lt;/p&gt;
&lt;h2&gt;Finding a Solution&lt;/h2&gt;
&lt;p&gt;I looked into a few existing solutions, but none were as all-in-one as I’d have liked. I wanted to be able to start a command locally and override specific endpoints and have the HTTPS certificates generated and trusted automatically. I also wanted overriding and editing responses to be possible in languages frontend engineers are familiar with, and I wanted that this configuration could be checked in to the repository to be shared with other engineers.&lt;/p&gt;
&lt;p&gt;And so &lt;a href=&quot;https://intervene.dev&quot;&gt;intervene&lt;/a&gt; was born.&lt;/p&gt;
&lt;h2&gt;Intervene&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; is a node.js process that works like a man-in-the-middle proxy (except it’s not an HTTP proxy, as it only proxies to a single host and appears to be a single HTTP server). It enables the altering of requests and responses for mocking purposes. The configuration is written in &lt;a href=&quot;https://www.typescriptlang.org&quot;&gt;TypeScript&lt;/a&gt;, so that frontend engineers are using a familiar language (as TypeScript is a superset of JavaScript, using plain JavaScript is fine too).&lt;/p&gt;
&lt;p&gt;It automatically generates and trusts self-signed certificates for HTTPS, so in every scenario, only a single command is necessary to create and start a proxy, with no extra steps to perform manually.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we used it to build the frontend for a major creator feature before the backend was ready. We added full typing support to &lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; in order to use it to discuss the APIs as they were being built. Our mock became the documentation around which discussions were had on what the API should look like. We were able to slowly turn the mocking off as the backend endpoints started to work, and we were even able to patch up responses when the backend implementation wasn’t finished and was only partially working. It’s now become a key piece of our development tooling, with many mocks checked in to frontend repos so that engineers can quickly turn on mocking for common scenarios.&lt;/p&gt;
&lt;h2&gt;Open Source&lt;/h2&gt;
&lt;p&gt;We’re really excited to be able to open source this tool today. It’s been invaluable not only in development, but also in recreating edge cases and a whole bunch of other scenarios where we just need to alter a header, request, or response to quickly try something out.&lt;/p&gt;
&lt;p&gt;To install globally (so you can run &lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; from anywhere):&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;npm install -g intervene&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can also install it as a devDependency of your project:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;npm install --save-dev intervene&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;A configuration can be automatically generated using &lt;code class=&quot;language-text&quot;&gt;intervene create https://api.mycompany.com&lt;/code&gt; (or whatever host you want to create a configuration for). In this example, it generates a file called &lt;code class=&quot;language-text&quot;&gt;api.mycompany.com.ts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So what does a minimal configuration look like?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ProxyConfig &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;intervene&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ProxyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  target&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.mycompany.com&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s add a static JSON response to an endpoint:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ProxyConfig &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;intervene&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ProxyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  target&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.mycompany.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  routes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;GET /cats&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      collection&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Charlie&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Whiskers&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Saving the file automatically reloads the configuration, so there’s no need to restart after saving. &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; in the route name is the default, so it can be excluded for the sake of brevity.&lt;/p&gt;
&lt;p&gt;Now if we make a &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; request to &lt;a href=&quot;https://api.mycompany.com/cats&quot;&gt;https://api.mycompany.com/cats&lt;/a&gt;, we’ll get some static JSON back:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ curl -k https://api.mycompany.com/cats
{&amp;quot;collection&amp;quot;:[{&amp;quot;name&amp;quot;:&amp;quot;Charlie&amp;quot;},{&amp;quot;name&amp;quot;:&amp;quot;Whiskers&amp;quot;}]}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;-k&lt;/code&gt; is needed in the curl request, because otherwise curl will complain that the certificate is self-signed and not trusted. However, if the URL is opened in Chrome, it will Just Work(™), because &lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; has already added the certificate to the operating system’s list of trusted certificates (this list isn’t used by &lt;code class=&quot;language-text&quot;&gt;curl&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Note that it doesn’t matter if there was an existing &lt;code class=&quot;language-text&quot;&gt;/cats&lt;/code&gt; endpoint — either way, the JSON specified in the configuration is what is returned and the real server is not called. CORS is automatically enabled for all endpoints from all origins. This can be configured if more specific CORS rules are required.&lt;/p&gt;
&lt;h3&gt;Altering Responses&lt;/h3&gt;
&lt;p&gt;So &lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; can easily return static JSON, but what about if we want to modify the response from the server? Let’s imagine there’s a &lt;code class=&quot;language-text&quot;&gt;GET /dogs&lt;/code&gt; endpoint that returns the names of some dogs in the same format as the &lt;code class=&quot;language-text&quot;&gt;/cats&lt;/code&gt; endpoint we mocked earlier:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ProxyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  target&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.mycompany.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  routes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;/dogs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Get the response from the real server.&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Mutate the response (add an `age` property to each dog).&lt;/span&gt;
      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dog&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        dog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Return the response.&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If a function is used as a route handler, it gets called with the request details, an instance of the &lt;a href=&quot;https://hapi.dev/api/?v=18.4.1#response-toolkit&quot;&gt;hapi.js response toolkit&lt;/a&gt;, and a function that returns a promise of the response from the server.&lt;/p&gt;
&lt;p&gt;Changing the &lt;code class=&quot;language-text&quot;&gt;req&lt;/code&gt; object before calling the &lt;code class=&quot;language-text&quot;&gt;proxy()&lt;/code&gt; function will change the request that gets made to the server. For instance, changing &lt;code class=&quot;language-text&quot;&gt;req.url.pathname&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;/cats&lt;/code&gt; before calling the function will mean that the &lt;code class=&quot;language-text&quot;&gt;proxy()&lt;/code&gt; function will actually call the &lt;code class=&quot;language-text&quot;&gt;/cats&lt;/code&gt; endpoint on the real server when a request is made to &lt;code class=&quot;language-text&quot;&gt;/dogs&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Custom Responses&lt;/h3&gt;
&lt;p&gt;Using the &lt;a href=&quot;https://hapi.dev/api/?v=18.4.1#response-toolkit&quot;&gt;hapi.js response toolkit&lt;/a&gt;, it’s possible to create custom responses. We have used this extensively at SoundCloud to develop error handling routines by mocking an endpoint to return an error.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ProxyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  target&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.mycompany.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  routes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;/whoops&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Create a response.&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; “whoops” &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Set the status code.&lt;/span&gt;
      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Add a custom header.&lt;/span&gt;
      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;‘x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;id’&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ‘&lt;span class=&quot;token number&quot;&gt;123456&lt;/span&gt;’&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Return the response.&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;How Does It Work?&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; initially writes an entry to &lt;code class=&quot;language-text&quot;&gt;/etc/hosts&lt;/code&gt;, adding the address of the server to mock as 127.0.0.1 (localhost). This means requests to that server now get routed to &lt;code class=&quot;language-text&quot;&gt;127.0.0.1&lt;/code&gt;. If the server is HTTPS, it generates a self-signed certificate and calls operating system commands to add it to the set of trusted certificates. It then starts an HTTP(s) server (based on the fantastic &lt;a href=&quot;https://hapi.dev&quot;&gt;hapi&lt;/a&gt; framework). Routes from the configuration file are added, and then default routes are added to proxy all remaining requests to the real server.&lt;/p&gt;
&lt;p&gt;In order to proxy requests, when a request comes in, &lt;code class=&quot;language-text&quot;&gt;intervene&lt;/code&gt; performs a real DNS lookup to ensure it ignores the entry in &lt;code class=&quot;language-text&quot;&gt;/etc/hosts&lt;/code&gt;. These lookups are cached and use the TTLs returned in the DNS response.&lt;/p&gt;
&lt;h2&gt;Where to Find out More&lt;/h2&gt;
&lt;p&gt;There’s a lot more documentation on the &lt;a href=&quot;https://intervene.dev&quot;&gt;intervene.dev&lt;/a&gt; site. Additionally, feel free to raise an issue or send a PR to the &lt;a href=&quot;https://github.com/soundcloud/intervene&quot;&gt;GitHub project&lt;/a&gt; if you have questions or suggestions.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Periskop: Exception Monitoring Service]]></title><description><![CDATA[Periskop is an exception monitoring service that we built here at SoundCloud. It was designed with microservice environments in mind, but it…]]></description><link>https://developers.soundcloud.com/blog/periskop-exception-monitoring-service</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/periskop-exception-monitoring-service</guid><pubDate>Mon, 16 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/81436/periskop_logo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.644171779141104%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAhUlEQVQI1y3OzwpBURAH4LvARi4l/yKuIgtZSDZKEUtJCQtZWOAF5BHIE9v6bp1T35maZn5N9PvENzYkPChzCr0GPY40mbImw5IzLSa8KEa+O11GXMnzZEsnDC8YcmFHIfTaIXjOnmwamESeWmVFTD9cXApXzhhQY5wucuBLncrvHefSnD8nTWWCnpZaigAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Periskop&quot;
        title=&quot;Periskop&quot;
        src=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/8ff1e/periskop_logo.png&quot;
        srcset=&quot;/blog/static/3add9dc6c52ee89743575e673459c0a8/9ec3c/periskop_logo.png 200w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/c7805/periskop_logo.png 400w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/8ff1e/periskop_logo.png 800w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/6ff5e/periskop_logo.png 1200w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/2f950/periskop_logo.png 1600w,
/blog/static/3add9dc6c52ee89743575e673459c0a8/81436/periskop_logo.png 1630w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/soundcloud/periskop&quot;&gt;Periskop&lt;/a&gt; is an exception monitoring service that we built here at SoundCloud. It was designed with microservice environments in mind, but it’s also useful for long-running services in general. It has been architected for scale and is heavily influenced by &lt;a href=&quot;https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud&quot;&gt;what we’ve learned&lt;/a&gt; with &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;. It uses a pull-based model to efficiently aggregate exceptions in clients, after which point a server component collects exceptions and further aggregates them across instances.&lt;/p&gt;
&lt;h2&gt;Why Yet Another Exception Aggregation System?&lt;/h2&gt;
&lt;p&gt;In the past, SoundCloud relied on &lt;a href=&quot;https://airbrake.io/&quot;&gt;Airbrake&lt;/a&gt; to help troubleshoot and identify problems happening in production. As traffic increased and our services started receiving requests in the tens of thousands per second, small hiccups would send huge bursts of exception reports, in turn depleting our entire monthly budget in just a few minutes, and causing loss of information. &lt;code class=&quot;language-text&quot;&gt;grep&lt;/code&gt;ping or indexing the logs was not an option given the huge volume generated by our services.&lt;/p&gt;
&lt;p&gt;Considering the increasing amount of services SoundCloud runs, we needed a solution that would easily discover and scrape services with the minimum amount of manual effort.&lt;/p&gt;
&lt;p&gt;Since SoundCloud employees &lt;a href=&quot;https://developers.soundcloud.com/blog/a-happy-new-employee&quot;&gt;are given the opportunity to use up to 20 percent of time to experiment&lt;/a&gt; with new ideas (known here as self-allocated time), this was a perfect opportunity for a &lt;a href=&quot;https://github.com/soundcloud/periskop/blob/master/AUTHORS&quot;&gt;group of engineers&lt;/a&gt; to get together and build something fun and useful to improve tooling at SoundCloud, and hopefully, for the entire open source community.&lt;/p&gt;
&lt;h2&gt;How Does It Work?&lt;/h2&gt;
&lt;p&gt;Periskop is composed of both a &lt;strong&gt;client&lt;/strong&gt; library component and a &lt;strong&gt;server&lt;/strong&gt; component.&lt;/p&gt;
&lt;p&gt;The client library provides an exception collector. Every time an exception is handled, it can be added to the collector. Unhandled exceptions can be added to the collector by a top-level exception handler. Once an exception is added:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The collector builds a unique key with the exception’s message and a hash of the stack trace. This key represents a concrete exception type.&lt;/li&gt;
&lt;li&gt;The collector aggregates the exception using the unique key and stores the aggregate using an in-memory data structure.&lt;/li&gt;
&lt;li&gt;The concrete exception occurrence is added to the aggregate’s list of recent exceptions for inspection. A queue data structure is used to implement a moving window of the most recent exceptions with a configurable size.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Collected exception aggregates are exported via an HTTP endpoint using a &lt;a href=&quot;https://github.com/soundcloud/periskop/blob/master/representation/errors.proto&quot;&gt;well-known schema&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 721px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/245a03d81fec8969a61e4ca6d88ce189/b13b6/periskop_design.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.66019417475728%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAACIElEQVQoz2PYdfqW3rEbL/NO3nmbeejKk6jHP/4zMQDBsq2HGaCAUVeCiZEBD1AXBctD1AANaQIa8v/m2///j9548d/NJ0gaJP7//38Gbk52xs2dsSzdL/4zvu9VEf7cLqTxpZVX5XMrn+SXFh75z52iil/6FRQv/f/PeHhDN7OYMB8jw64zt412n7lTBsQF+y8+SNTQ0RcAmsdnZGyM4ap/xQz83zqFpb61cAl+7xKW/pXLIHYE5jIY2Hz4ktKBSw8DDl996rvn7B0nkMuAgBmI+W2N1DgfbOsJfLyzL+bpnkkJt3bOUkHW++DhSdUPz4/Ffnp2MObl3d1BEztLWEFebnz07f//66///D94+fF/G0c3BaBaDiDm0lGR1nu9f/L/53sm/v9/a/n/K2tbekEGpYU7i4LoW+fXdfz/e/X//y/n/r+4u+v3g6ub+Rj2nLtrfeDSo24gbgW6tHjhhr0sUAfwGmnKC77cN6n18pqWKY929M19tnuCE0ji7bmlYDXPbu1w/vDkwOz3j/dPeXVvd/OW1RM4GLYeuyp54tZrm7MPPloCDTVUVFEHuY6fk4ODDVuMTq6IAaeCiqIEbDHPyAD0ZiMslg9eevTfysFVHiYrIsDDuG1KEfOCphSW7VOLWIA02DAZCWGw/JwptUxnDi5mObZnHsveLdOZzU10GBj2nb/vsffc3dV7zt5dAjS8d82eU6wgxYlZRQzkAADhzgS6+14byAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Periskop Design&quot;
        title=&quot;Periskop Design&quot;
        src=&quot;/blog/static/245a03d81fec8969a61e4ca6d88ce189/b13b6/periskop_design.png&quot;
        srcset=&quot;/blog/static/245a03d81fec8969a61e4ca6d88ce189/9ec3c/periskop_design.png 200w,
/blog/static/245a03d81fec8969a61e4ca6d88ce189/c7805/periskop_design.png 400w,
/blog/static/245a03d81fec8969a61e4ca6d88ce189/b13b6/periskop_design.png 721w&quot;
        sizes=&quot;(max-width: 721px) 100vw, 721px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The server component uses service discovery to find all instances of a service. It then scrapes and further aggregates exported exceptions across instances. A simple UI allows navigating and inspecting exceptions as they occur.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/a987b/periskop_ui.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.21875%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABFElEQVQoz52Re0vDMBTF+30FmQMn/jHcpHS06DcUW5Y225pCH8n6oI9j7tqKulV0gR8nuWkP9+QazsMMzuIW1v0c5t0NnuYzLB6XWK5MPFs2VuYG61+ge3PjwLJfsLZfYSj2jqP3hjpw0Wpy5iJNU1RVhWuWsQ04tnw3sIe/2yOKIpRlhaZp/kzbtqjrGobv++hh8BkDDwIIbSilvK7DQBuMkDGp63rwPA+HQ4gsy05P8BWqjYxnUiFEbzgakXLO9Z4jDAWUOuroJYqi+AbVLqGUumzYv2H577hd100bJkmio0hkcuAUsdc8z8+6Jqh+bqg1jmOk+keaGk3vc5rDnmrUDekIneluskPqhD6aivYTWmT8ASlL6E7lhNsXAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Periskop UI&quot;
        title=&quot;Periskop UI&quot;
        src=&quot;/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/8ff1e/periskop_ui.png&quot;
        srcset=&quot;/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/9ec3c/periskop_ui.png 200w,
/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/c7805/periskop_ui.png 400w,
/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/8ff1e/periskop_ui.png 800w,
/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/6ff5e/periskop_ui.png 1200w,
/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/2f950/periskop_ui.png 1600w,
/blog/static/114e4dc8230cc7c7d2fddbaec478bbf5/a987b/periskop_ui.png 1920w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Design Tradeoffs&lt;/h2&gt;
&lt;p&gt;Every design decision comes with a set of tradeoffs that are important to be aware of. In the case of Periskop, we opted for a pull model for collecting exceptions as opposed to the usual push model of similar tools like &lt;a href=&quot;https://sentry.io/welcome/&quot;&gt;Sentry&lt;/a&gt;. This is advantageous because the pull model scales well with the number of exceptions and instances, and it provides interesting capabilities with little effort, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Memory used by the client library is bounded by the number of different types of exceptions occurring instead of the total number of exceptions overall. Memory on the server side is similarly bounded, as it uses the same aggregation logic.&lt;/li&gt;
&lt;li&gt;If the server component needs to scrape a large amount of service instances, it might end up skipping scrape cycles, thereby reducing the freshness of the data, but it does not require additional resources. In practice, this means that Periskop can handle large numbers of services and instances with few resources.&lt;/li&gt;
&lt;li&gt;Creating hierarchies of Periskop instances (known as &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/federation/&quot;&gt;federation&lt;/a&gt;) becomes trivial. A primary Periskop instance could aggregate exceptions collected by secondary, data center-specific instances, thus providing a global view of the entire service stack across data centers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This model also has some disadvantages when compared to the push model, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a fatal exception occurs and the process dies, the exception won’t be collected. However, this can be mitigated by the reporting capabilities of orchestration services like Kubernetes or other forms of logging.&lt;/li&gt;
&lt;li&gt;The pull model is not well suited for short-lived processes like scheduled jobs. This could be solved by the use of a push-based event gateway, although we haven’t yet had the need at SoundCloud, as it is usually more convenient to use logs to inspect failed jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What Is Coming Next&lt;/h2&gt;
&lt;p&gt;Periskop is in its early stages and lacks some capabilities. Some of the ideas for improving and extending it that are on the roadmap include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Server-side persistence of exception occurrences over time&lt;/li&gt;
&lt;li&gt;UI filtering and sorting&lt;/li&gt;
&lt;li&gt;Federation (hierarchical Periskop servers)&lt;/li&gt;
&lt;li&gt;Pluggable service discovery mechanisms&lt;/li&gt;
&lt;li&gt;Connectors to other error monitoring services like Sentry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, high-level libraries for widely used web frameworks and client libraries for more programming languages need to be developed for Periskop to realize its full potential.&lt;/p&gt;
&lt;p&gt;Periskop is &lt;a href=&quot;https://github.com/soundcloud/periskop&quot;&gt;open source&lt;/a&gt; and we are happy to accept external contributions! If you find this project useful, we would love to hear from you. Please drop us a line at &lt;a href=&quot;mailto:periskop-maintainers@soundcloud.com&quot;&gt;periskop-maintainers@soundcloud.com&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How (Not) to Build Datasets and Consume Data at Your Company]]></title><description><![CDATA[The topic of datasets is not new, and they have been successfully used at SoundCloud for analytics purposes for a long time. In this blog…]]></description><link>https://developers.soundcloud.com/blog/how-not-to-build-datasets-and-consume-data-at-your-company</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-not-to-build-datasets-and-consume-data-at-your-company</guid><pubDate>Tue, 03 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The topic of datasets is not new, and they have been successfully used at SoundCloud for analytics purposes for a long time. In this blog post, however, we’ll delve into how the culture of building and providing datasets is being adopted in more areas of engineering beyond the domain of analytics.&lt;/p&gt;
&lt;p&gt;When we talk about datasets, we’re talking about &lt;strong&gt;published datasets&lt;/strong&gt;. In this case, a team owning the data in question would also own the published dataset and be responsible for delivering it in a particular shape, as agreed upon with the internal consumers, i.e. the end users of the dataset. As a result, the dataset &lt;strong&gt;encapsulates any domain intricacies&lt;/strong&gt; that should not leak into the consumer’s domain.&lt;/p&gt;
&lt;p&gt;This post shares our experience of adopting “&lt;a href=&quot;a-better-model-of-data-ownership&quot;&gt;A Better Model of Data Ownership&lt;/a&gt;,” which we blogged about more than two years ago. It discusses our learnings and digs deeper into the specifics and technical details.&lt;/p&gt;
&lt;p&gt;In particular, we’ll focus on exploring three questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How &lt;strong&gt;not&lt;/strong&gt; to consume data&lt;/li&gt;
&lt;li&gt;How to consume data and who should provide it&lt;/li&gt;
&lt;li&gt;How to build datasets and what to consider before you start&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process of data management in a data privacy-compliant organization is beyond the scope of this blog post. Whenever the access to or use of personally identifiable information is not justified by the business case, further treatment of data is required, but this is not discussed here.&lt;/p&gt;
&lt;h2&gt;Examples of Datasets at SoundCloud&lt;/h2&gt;
&lt;p&gt;Before going any further, here are a couple of examples of datasets we will refer to in the following sections. We deal with artists, tracks, and plays here at SoundCloud, so we’ll draw examples from that domain&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Track Metadata Dataset&lt;/strong&gt; &lt;em&gt;(holds information about a given track)&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;track_id&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;artist&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;is_current&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Longer, Better, Stronger Passwords&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Adept Surfer&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2060-01-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Think Before You Click&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Cautious Mouse&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-05-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Think Before You Click (On My Heart)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Cautious Mouse&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-05-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2060-01-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Track Rightsholders Dataset&lt;/strong&gt; &lt;em&gt;(records the label or individual who owns rights to a track in a given territory)&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;track_id&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;territory&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;rightsholder_id&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;is_current&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;DE&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;182&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2060-01-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;FR&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;182&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2060-01-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;DE&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;42&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2060-01-01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The particular representation shown above is called a &lt;strong&gt;slowly changing dimension table&lt;/strong&gt;, since it captures the historical changes of an entity, e.g. a track, over time. For many datasets, SoundCloud has settled on the following shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Primary keys&lt;/strong&gt; — one or more columns that uniquely identify an entity (e.g, &lt;code class=&quot;language-text&quot;&gt;track_id&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;territory&lt;/code&gt;). However, there may be multiple entries for the same entity as it changes over time.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt;: &lt;code class=&quot;language-text&quot;&gt;datetime&lt;/code&gt; — the start of the validity period for the given row (technically also part of the primary key)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;: &lt;code class=&quot;language-text&quot;&gt;datetime&lt;/code&gt; — the end of the validity period for the given row (or a time in the far future)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;is_current&lt;/code&gt;: &lt;code class=&quot;language-text&quot;&gt;boolean&lt;/code&gt; — &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; if the given row is the valid one at the time of dataset creation, and &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; otherwise.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The dataset may or may not contain gaps in the time dimension for a single entity. This depends on whether or not it makes sense in the domain.&lt;/p&gt;
&lt;p&gt;One word on representing entities that have been deleted: Deleted records appear as rows, with the latest &lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt; in the past and &lt;code class=&quot;language-text&quot;&gt;is_current = false&lt;/code&gt;. In that sense, these rows look similar to old versions of an entity with no &lt;code class=&quot;language-text&quot;&gt;is_current&lt;/code&gt; row. This makes it harder to retrieve all entities, regardless of whether or not they have been deleted. If retrieval of deleted entries is required, it should also be possible to introduce an &lt;code class=&quot;language-text&quot;&gt;is_deleted&lt;/code&gt; column in the dataset.&lt;/p&gt;
&lt;p&gt;Naturally, not all datasets need to have or can have a time dimension. A plays dataset is an example of a simple &lt;code class=&quot;language-text&quot;&gt;fact&lt;/code&gt; table:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plays Dataset&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;timestamp&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;listener_id&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;track_id&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;territory&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-15 11:20:00 UTC&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1349&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;DE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-15 11:30:00 UTC&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1349&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;DE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2019-02-15 12:05:00 UTC&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1066&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now that we’ve seen what the datasets actually look like, we will now consider all the various ways in which the company exchanged and consumed data before adopting the idea of published datasets.&lt;/p&gt;
&lt;h2&gt;How Not to Consume Data&lt;/h2&gt;
&lt;p&gt;The shift toward using datasets at SoundCloud arrived together with the shift toward batch-oriented processing where it makes sense. This means replacing online services with batch jobs for tasks such as: royalties calculation, report generation for labels and artists, processing content delivered by labels, some aspects of search and recommender systems, etc. All of these examples rely on data owned by other teams in the company, which brings up questions of contracts, SLAs, domain encapsulation, and team interdependency.&lt;/p&gt;
&lt;h3&gt;Data Consumption: A Story of One Team&lt;/h3&gt;
&lt;p&gt;This post will now highlight the experience of the Royalties &amp;#x26; Reporting team here at SoundCloud, a team that has always been a heavy consumer of data and has undergone a shift toward the use of published datasets. The responsibilities of the team include calculating royalties and building reports for artists and labels.&lt;/p&gt;
&lt;p&gt;An example of a report is a CSV file that shows the breakdown of tracks, track metadata, play counts, and associated royalty earnings. In relational database terms, you could think of it as a join between a plays table, a track metadata table, a track rightsholder table, and a royalties table. Usually, these reports are generated on a schedule (as opposed to on demand), so they fit the batch-processing model quite well.&lt;/p&gt;
&lt;p&gt;With reference to the following figure, consider the various ways in which these reporting jobs used to consume data in the past. We will discuss the pros and cons of consuming events, snapshotting databases, and consuming live service APIs, all of which we have tried but eventually moved away from.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/148913331dae75abf2c0bfba78673908/undesirable_scenarios.svg&quot; alt=&quot;Undesirable Scenarios&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Consuming Events&lt;/h3&gt;
&lt;p&gt;Events are great. They are a solid approach for storing the changes of state of a system without imposing a way of how such events should later be consumed or interpreted. Many SoundCloud systems already publish events.&lt;/p&gt;
&lt;p&gt;Events can be consumed in batch jobs directly, and were, in fact, the principle way of consuming data in our reporting jobs until very recently. But this approach carries with it a handful of tradeoffs, which are outlined below.&lt;/p&gt;
&lt;h4&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flexibility:&lt;/strong&gt; Events allow for ultimate flexibility, as they capture the most amount of detail and the entire history. This can also be a bad thing, as the details may expose domain complexity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Excellent time resolution:&lt;/strong&gt; Each event is timestamped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing:&lt;/strong&gt; Investigating and debugging historical changes is free, and nothing is ever deleted. As such, it is possible to recreate system state at any point in time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Expensive infrastructure:&lt;/strong&gt; Each service must support event infrastructure, and this cost is not justified for all uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficult to consume:&lt;/strong&gt; Consumers must read all events from the beginning of time. It is impossible to get the current state without reading everything. This does not scale well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heavyweight:&lt;/strong&gt; At SoundCloud, events are encoded as &lt;a href=&quot;https://developers.google.com/protocol-buffers&quot;&gt;Google Protobuf&lt;/a&gt; messages and stored in Hadoop Sequence Files. This makes them opaque, with no ability to selectively consume individual fields without decoding complete events. This limitation is implementation-specific.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposes domain complexity:&lt;/strong&gt; Events tend to include all of the information available for interpretation later on. This interpretation is better handled by the data owner than by every consumer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Potentially lower quality data:&lt;/strong&gt; This occurs in cases where the data owner does not consume their own events and instead relies on their databases and APIs. The same may be true of datasets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Events seemed very promising at first. However, as time went on, we realized they were difficult to work with and the jobs did not scale well after a year or two. The limited ad hoc querying abilities and increasingly slow jobs made development and operations frustrating and costly.&lt;/p&gt;
&lt;h3&gt;Snapshotting Databases&lt;/h3&gt;
&lt;p&gt;We did this. We performed full dumps of the database table backing the live service we don’t own.&lt;/p&gt;
&lt;h4&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accurate:&lt;/strong&gt; The data owner and data consumer consume the same data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to consume:&lt;/strong&gt; Structured data that can be consumed cheaply and easily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to develop with:&lt;/strong&gt; Can be queried in an ad hoc manner for development and debugging purposes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bad coupling:&lt;/strong&gt; The database model becomes an implicit contract and the consumer needs to be consulted regarding any changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No SLA:&lt;/strong&gt; No explicit contract with the data owner around the quality and availability of data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bad domain encapsulation:&lt;/strong&gt; This exposes internal domain complexity that may be misinterpreted by the consumer. It bypasses any domain processing that happens in the service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No time dimension:&lt;/strong&gt; There is no visibility of changes of data over time or deleted entities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s quite clear how snapshotting databases breaks many principles of encapsulation. Additionally, we were spending a lot of time building and maintaining all the datasets we needed ourselves. They were not the responsibility of the data owner, and they were not available to other consumers.&lt;/p&gt;
&lt;p&gt;To be clear, snapshotting is a viable option for cheaply and easily building datasets. However, it’s a completely different story when the data owner, and not the consumer, is doing it with full knowledge and understanding of their databases.&lt;/p&gt;
&lt;h3&gt;Consuming Live Services through APIs&lt;/h3&gt;
&lt;p&gt;Yes, we did this too. We would make API calls to a live service from inside a batch job.&lt;/p&gt;
&lt;h4&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accuracy:&lt;/strong&gt; This was the most accurate and up-to-date data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bad fit for batch-processing paradigm:&lt;/strong&gt; Depending on the API design, it would not normally be possible to work with the data in bulk with all the flexibility you’d expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor performance:&lt;/strong&gt; It does not scale well and has the possibility of introducing unnecessary strain on the consumed API and its database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard to develop against:&lt;/strong&gt; Ad hoc querying is easy, but it may not be possible to do in bulk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No time dimension:&lt;/strong&gt; There is no visibility of changes of data over time or deleted entities unless the service keeps all the history.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Enter Published Datasets&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/e501cbf007aa4075fe8a7c6272b932a5/good_scenarios.svg&quot; alt=&quot;Good Scenarios&quot;&gt;&lt;/p&gt;
&lt;p&gt;Published datasets combine some of the positive aspects of the above approaches, but they practically eliminate all the negative ones. To reiterate, &lt;em&gt;published datasets are produced by the data owner following an agreement with the consumer or consumers&lt;/em&gt;. We’ll discuss the pros and cons of these below.&lt;/p&gt;
&lt;h4&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain encapsulation:&lt;/strong&gt; The dataset producer can preprocess the data to hide domain complexities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract and SLA:&lt;/strong&gt; Follows a well-defined contract between the producer and the consumer of what the shape, correctness, and availability should be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;May be easy to build:&lt;/strong&gt; Internally built tooling can help build datasets for many use cases with low effort.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to consume:&lt;/strong&gt; Structured data that can be cheaply and easily consumed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to develop with:&lt;/strong&gt; Can be queried in an ad hoc manner for development and debugging purposes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear ownership:&lt;/strong&gt; The producer assumes the responsibility for maintaining the dataset and fixing issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decoupling:&lt;/strong&gt; The producer is free to evolve the schema of their databases and events, so long as they maintain the dataset interface the same, when it makes sense.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Potentially lower quality data:&lt;/strong&gt; The producer doesn’t usually consume their own datasets — data problems are still possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Takes effort to build:&lt;/strong&gt; Datasets go beyond what can be built using standard tooling (more on that in the next section), and they may still take effort to build. In the cases where a need for a dataset is blocking the consumer but the producer has other competing priorities, the consumer may take on the responsibility of building the dataset for the producer to own and maintain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How We Build Datasets&lt;/h2&gt;
&lt;p&gt;There are two primary ways in which we build our datasets: &lt;strong&gt;from events&lt;/strong&gt;, and &lt;strong&gt;from database snapshots&lt;/strong&gt;. As discussed in the previous section, we have experience consuming both events and snapshots in our batch jobs. What we wanted to do differently was to (a) establish a coherent way of building these datasets, (b) create standard tooling to cover the most common cases, and (c) make it the responsibility of the data owner to provide a dataset that was clean, consistent, and timely.&lt;/p&gt;
&lt;p&gt;The two methods of building datasets (from events and from snapshots) are not as dissimilar as they may seem. In fact, they share many of the same steps, which becomes apparent when looking at the figure below. Most datasets we produce at SoundCloud take the shape of dimension tables, and they capture the historical changes of an entity over time, as shown in the introduction section. We will assume we’re building such a dataset in the discussion below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/2bf414b0fa6ca0acc8e9a247935e0c3d/building_datasets.svg&quot; alt=&quot;Building Datasets&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Building a Dataset from Events&lt;/h3&gt;
&lt;p&gt;In case of events, the workflow is a single step, outlined below.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Roll up events on top of the previous dataset:&lt;/strong&gt; This step reads the previous version of the published dataset, as well as the events emitted since the last dataset was published. It applies the new events on top of the old dataset, performing any necessary transformations, and writes a new dataset. This is a &lt;strong&gt;release candidate for the published dataset&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you have a very small number of events, you may choose to roll them up from the beginning of time every time you produce the dataset. This gives you the most flexibility and ensures the most recent code is used for rollup.&lt;/p&gt;
&lt;h3&gt;Building a Dataset from Database Snapshots&lt;/h3&gt;
&lt;p&gt;The workflow to produce a release candidate is covered below.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dump a table to a snapshot:&lt;/strong&gt; The tool reads from the database and writes this snapshot to a temporary table. This is a direct copy of the table, with no further processing. This is not the published dataset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transform the snapshot (optional):&lt;/strong&gt; This is where you’d implement steps such as filtering/removing or renaming columns, or transforming values in the dataset. This is also not the published dataset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roll up snapshot on top of the previous dataset (optional):&lt;/strong&gt; This step reads the transformed snapshot and the previous version of the published dataset and writes a new dataset. This step is required when building &lt;em&gt;slowly changing dimension tables&lt;/em&gt; or when the entities deleted from the database must be preserved in the dataset. This is a release candidate for the published dataset.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This multistep procedure allows you to decouple your published dataset from the source table. In the past, we’ve had problems caused by coupling schemas in the database with schemas in the data warehouse, because changing the source becomes impossible without coordinating changes far downstream.&lt;/p&gt;
&lt;p&gt;Whether the dataset is produced from snapshots or from events, the shape of the release candidate is the same, so the validation and publishing/promoting steps are the same.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Validate dataset:&lt;/strong&gt; This is a &lt;strong&gt;very important step&lt;/strong&gt; that’s often overlooked. Testing the &lt;strong&gt;code&lt;/strong&gt;, e.g. through unit testing, lets us gain confidence in the &lt;strong&gt;logic&lt;/strong&gt; of the various steps in dataset production. However, it is usually done using a contrived set of toy data. But when dealing with large amounts of data and moving parts, it is important to also test the data that is actually produced:&lt;/li&gt;
&lt;li&gt;Does it contain the expected values in each field?&lt;/li&gt;
&lt;li&gt;Does it satisfy the dimension table rules (see the Examples of Datasets at SoundCloud section above)?&lt;/li&gt;
&lt;li&gt;Is the dataset a truthful representation of the upstream data? Even a simple comparison of the number of rows in the input and the output can go a long way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish dataset:&lt;/strong&gt; Usually the dataset is published to the “latest location” (thereby overwriting the previous version), as well as to a location corresponding to the time of publishing (for safekeeping).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At SoundCloud, we have standard tooling for building datasets from snapshots of MySQL databases. This greatly simplifies the creation of new datasets, and as a side effect, it promotes a consistent and common shape of datasets.&lt;/p&gt;
&lt;h2&gt;Considerations for Building a Dataset&lt;/h2&gt;
&lt;p&gt;Now that we’ve looked at the two ways of building a dataset, let’s look at the considerations that the dataset producer and consumers need to agree on that would inform the approach taken.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do you need to record the &lt;strong&gt;previous versions&lt;/strong&gt; of an entity?&lt;/li&gt;
&lt;li&gt;Do you need information about &lt;strong&gt;deleted entities&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Can entities be &lt;strong&gt;deleted and reinstated&lt;/strong&gt;? How would you like to handle &lt;strong&gt;gaps&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How often do you need the dataset updated with the newest data?&lt;/li&gt;
&lt;li&gt;Is there a specific time by which an updated version of the dataset needs to be available?&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For a dataset with a time dimension, what is the &lt;strong&gt;time resolution&lt;/strong&gt; required? Hourly, every six hours, daily? Highest precision possible?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generally, the datasets based on snapshots suffer from the loss of precision.&lt;/li&gt;
&lt;li&gt;In some cases, it may be possible to keep the complete history of changes with exact timestamps in the database, though it makes the service more complex.&lt;/li&gt;
&lt;li&gt;Events can provide high time accuracy, though there may still be a time difference between the event timestamp and the time that the change became effective in the other systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For a dataset with a time dimension, how far back is the &lt;strong&gt;historical information&lt;/strong&gt; required? Where do you get this historical data from (e.g. from events, from a different dataset, or inferred from the current value)?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, for some datasets, it may be important to distinguish between two types of changes to an entity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It may be a &lt;strong&gt;retrospective fix&lt;/strong&gt; or a &lt;strong&gt;correction&lt;/strong&gt;, in which case the change should be considered effective &lt;strong&gt;before&lt;/strong&gt; it is made in the service/database; or&lt;/li&gt;
&lt;li&gt;It may be a real brand-new change, effective from the time the change is made.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As more datasets were built at SoundCloud, common patterns led to standard semantics and standard tooling for the most common cases. Even in those cases where standard tooling did not apply, there was already an established pattern for what the dataset could look like and how it could be published.&lt;/p&gt;
&lt;p&gt;For heavy consumers of datasets, that also meant that the burden of building and maintaining the datasets was now shared with those who knew most about the data, albeit not always those who had the most experience with building the datasets. But even that was changing quickly. The additional effort and the initial learning curve on the part of data owners led to the organization-wide benefits of shared, well-specified, and well-maintained datasets.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Magic of Generating an Xcode Project]]></title><description><![CDATA[Planet Earth vs. Jupiter. There is the same order of magnitude between the ratio of these two planets and the amount of code that defined…]]></description><link>https://developers.soundcloud.com/blog/tuist-project-generation</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/tuist-project-generation</guid><pubDate>Thu, 20 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 640px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/695b2812efc27f2a4da8c7a84aa7ac21/e49a9/jupiter_earth.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAs6AAALOgFkf1cNAAADKklEQVQ4y4WUaUzTdxzG24IFnFU25AoapAi1FOXoJMR4JJuLTAfTScALxDMKOEAUsYhKpLi0tSairkymUrACTpBDSFRUDkWtaCLGxSnTqEQFER17oy/8+Ldu8QgNL54339/vefJ9vpdIJBIxFBwcHPhy1Eh8vD1tGCkbYYv7jBlLkFJJYGAgKpUKLy+vd3H7QmKxmLHeHnwTGUpCzLekJcxl08p4NiTFsiBqBkp/X2SjXHFzc8Pd3R2ZTGZf8F1WqoBxxM2aSmbij+jSkzDlJmMuWE+5PosK3Ub2Z68mUhXwOXdwwQB/OYuiZ6JZHU9h1gos2jQaCnM4+1sebb/v4OIhLa0HtdQYNYQp5DaORHAk+t+aDRKJ7cHVbTSz53xPTmYKh/fspKmymBunK7l3oY6ejlP0dTTSd7WR3isNPGytxrxbyzCp1H6G8kAFi+Pmod+STvUBA9cbynhqbeDV3TbePOmE/r/ghYBnt3h9z0q3tYnpkRHv+Z6enqiCVfj5+aEKCsJ5+BdMDAkhITaavJ+TKMnP4HxxPtYjem7X7Odpazl97ZX0tpTR01xG95kSHjX/wfIFse8FJYJNhUJJaIgaFxdnnFyGM1kdSnL8bPZkLuWkPo0rpk10lmzj79q9/HvtBP+0H2VAQH9bKQ/q9/GoyULmqsQPln195YSHR9oCjsOkhIVOYtX8KHTJcVTkLuOCMYXbRWk8Nm+gvyKHgeo8Buq0DNQW8Px4Pt1VBtbGR9uroRhl8ESWLfxJGJWlVO5IodGQQUthFh1FGv4UMu06qqXLks8dSwE3S7VcMu9iytdh9pvi4e3DvJg55Aojc2TLcs78uo12s47OciMPThbRe7qYnlPF3K/Zx50aE6WG7UilTvYFxRIHwtVqUoW67Naso9a0k8tVB+g6W0HPxeO8uFzFy6t1dJ+zYD1mYlqE+tM5HAxOzs5MmxJB+oqFGDenUvJLNvWmApoO6mg+pKfFbKS+SMcPM2f8t6ZDbIqtQY6OBE8Yz6KY79i8ZgmG7BT2bs3AqEklPTEWhdx3MN7Q18ZJ2IIxwpVRjvdjgv84PL5y/eSAfPz3LUb4MwV/NlnqAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Earth vs Jupiter&quot;
        title=&quot;Earth vs Jupiter&quot;
        src=&quot;/blog/static/695b2812efc27f2a4da8c7a84aa7ac21/e49a9/jupiter_earth.png&quot;
        srcset=&quot;/blog/static/695b2812efc27f2a4da8c7a84aa7ac21/9ec3c/jupiter_earth.png 200w,
/blog/static/695b2812efc27f2a4da8c7a84aa7ac21/c7805/jupiter_earth.png 400w,
/blog/static/695b2812efc27f2a4da8c7a84aa7ac21/e49a9/jupiter_earth.png 640w&quot;
        sizes=&quot;(max-width: 640px) 100vw, 640px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Planet Earth vs. Jupiter. There is the same order of magnitude between the ratio of these two planets and the amount of code that defined the SoundCloud iOS project before — Jupiter — and after — Earth — generating it using &lt;a href=&quot;https://www.tuist.io/&quot;&gt;Tuist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this blog post, we will discuss the problems of maintaining a complex iOS project, how we managed to simplify the process by writing less and &lt;em&gt;more consistent&lt;/em&gt; code, and how we ended up with lower build times as an unexpected result.&lt;/p&gt;
&lt;h2&gt;What’s the Problem with Xcode Projects?&lt;/h2&gt;
&lt;p&gt;Xcode is the official tool for developing apps on Apple platforms. Every time you make a change to an Xcode project, such as adding a new source file or changing a setting, the IDE generates some code into a file called &lt;code class=&quot;language-text&quot;&gt;project.pbxproj&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, here is an extract of changes that are automatically generated by Xcode when creating a new file:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/da2f0/new-file-xcode.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.62857142857142%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACXElEQVQ4y52Sy08TURTGL6++6MsW26Gdvl8znRk701ALWghIFSVREf8BFy7cmEhcqiuDW90qojEaXsaolWJioih/VzOf596xoAlsXPxybk7mfvf7zhm28/Wgt9n92dvo7Pe2dn/8H9393s7eLwF71/mO23fuY2bpOmrtZVgXbsD8C2ues3wi9fZNaDNXsXTrLrZ298Fe7XRtpXnRZsGkzcaKNovk/1Cw2Smq4ZzNAhmb+dNHBNJOjxPK2swVtzPWrP3mw57N1jY/Q59eRDhrQNanIBst4jyipQb8uQaCOQvRooVIwRQ1WjThS+kYSWhwEe6kDhYpotS8hPXtDthLElSmFsBOlxHI1jGaseBNm/CkzsAt1zAsKfAmVXgIn1wVDEsVDMYUDEkqhsdVsHAexbNtrG+R4NrGJ+itKwhldUhKA5LaREKbRDhfhzdVowfqcHHhhIGBmIoBSSMRzREiE4NxBRQbhUZf8NBhBXG9jbixAEmfQ9aaxSg59qS5oEluTYppwJ1yEoioJwteBosW4UvXxIfuRBVemc9Hw1C8Qs7KdLnkMNanTP3KMYIUWaPIfOhJ/RwtZFrUcI7EZXKUNGj4upgXF+CueBqnHuPwcCnREnzZCUTUOcQqTcTKJoK0eR93mlCdBUi8KgJ+5mL8zEK5o6VwwfIkCQYzwgGP6E9XCU0QyOhiw85lp/bP/Hvhmu7mJ+ad32Z9+wtqs9fEi/5MjTZr0PwMMcuR8aro8+pKHI9H1sVMq61FvH6/C/b24zesPnuBlUdPcO/BqmDl4b/0+yfB7z5++hxc6zf2AcH9It1rWgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;New Xcode file&quot;
        title=&quot;New Xcode file&quot;
        src=&quot;/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/8ff1e/new-file-xcode.png&quot;
        srcset=&quot;/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/9ec3c/new-file-xcode.png 200w,
/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/c7805/new-file-xcode.png 400w,
/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/8ff1e/new-file-xcode.png 800w,
/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/6ff5e/new-file-xcode.png 1200w,
/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/2f950/new-file-xcode.png 1600w,
/blog/static/88b6c0f9d785d4c8807a4d4305c561f5/da2f0/new-file-xcode.png 1750w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The first thing you may notice is that the generated code is not easy to read or change, as it uses unique random identifiers to refer to every entry of the project. Furthermore, a single file addition creates changes in different sections of the project file, which needs to respect a specific format and particular conventions, or else it would result in an invalid project that can’t be opened.&lt;/p&gt;
&lt;h3&gt;Project Files Are for Computers, Not for Humans&lt;/h3&gt;
&lt;p&gt;Now let’s try to imagine how large this file would be in a project of the size of the SoundCloud iOS app, which is composed of thousands of source files and split across dozens of frameworks with multiple targets, hundreds of build settings, and complex linking rules. To give you a better understanding of this, in December 2019, we had more than &lt;strong&gt;80,000&lt;/strong&gt; lines of code representing our project. That’s Jupiter!&lt;/p&gt;
&lt;p&gt;Maintaining project files has always been a problem for iOS developers, especially as the number of contributors working on the same codebase grows. As an example of one possible issue, if multiple engineers are working in parallel and trigger changes on the same project file, there is a high chance that this will result in painful git conflicts.&lt;/p&gt;
&lt;h2&gt;Project Generation&lt;/h2&gt;
&lt;p&gt;To solve most of these problems, some engineers in the iOS community came up with the idea of automatically generating the project files. In this way, the projects can be redefined using a “user friendly” format composed of more concise and readable rules. A tool will then run to create a &lt;code class=&quot;language-text&quot;&gt;pbxproj&lt;/code&gt; file that adheres to Apple’s standards.&lt;/p&gt;
&lt;p&gt;The main benefits of this process are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt; — one rule applies to all modules&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt; — it’s easier to understand how your project is defined&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microprojects&lt;/strong&gt; — you can generate a subset of your project on demand, in order to work on a feature in isolation and get lower build times&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No conflicts&lt;/strong&gt; — you’ll avoid having to solve difficult conflicts on project files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensibility&lt;/strong&gt; — you can easily add new modules&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introducing Tuist&lt;/h2&gt;
&lt;p&gt;When we decided to find a new way of generating projects, we opted to use &lt;a href=&quot;https://www.tuist.io/&quot;&gt;Tuist&lt;/a&gt;, an open source project started by one of our former employees, &lt;a href=&quot;https://ppinera.es/&quot;&gt;Pedro Piñera&lt;/a&gt;. Tuist generates Xcode projects based on some rules expressed on a Swift manifest file named &lt;code class=&quot;language-text&quot;&gt;Project.swift&lt;/code&gt; and on the current state of the filesystem.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 658px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3bfe07ea07ca9ad4d4f8ab25cd2447ae/6164f/tuist-generate.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.13373860182371%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACoUlEQVQ4y5XSX0hTURwH8HO13HTOtAzFHprWg6ZBmDKn985w/6QgDCnrboL0Er0EPRgS/VEcJYE6ys39MbuTYdNslkGYsGQpGmgJBol/alNTQ9EeEjRb99e5082ZGXTgyzn3d8/5cDn3hxLTKOQf0XzESxCiXWjrCMXh4YRxDwCw+SYF7fZlY1xCM5ySGnw4HieKW7COQD2WIIgkHBFeh/vRy8jDj0fkoQQkFV1FwKtEawEYqbRzRy7o5l0lhiVniWHxTVH1bKfwKfBIScY5uVw+plAoBnAGZXny0YwTaWIPBpg4b09LEozbD8JwcxI72xgJD4JBSWHNN9DU4Ri+Q0HVzKqoHoQS8fE7NK0GtVoNNE1DsaYYMqlj54cQ8I1ha4uPowGsOG17AMwh7LNgkDxb5wXa+BM0ZoDT9xZA2geCbHF6mVKlAplM9ouLSqmCrLz0k4CAqEfeaSYS4FEMrNoiAIyIbQ2Acu3SfknZtCbrmkcjLnUXS2/OFaUUGqPSjx6OI0kqPycnR0GSpJLKzlUKzqDwZgTRJoKdahICMDHsWrOAA+FJAKxtuM9nXKPl1p5Puqbez3rLqwE1+sfQox/7TAgmdwRvNzqT7UPL4PjohZduAEPXxAhX7x2bR/L8U6EURYVwoVOu+1oKH8YguwHCdrCccSbb+r96WwcX2PYPy2Dp9rz1t4fJ6Q7sw4cIbq73gbAzeKuh84Cpe3LK4vqyhLNi6Bpv4+pjGKx2vPs/cGSj828YO+K0Ta9FlVZnYqmuRcjVatrfb7m7P0A3sw6u2NbBzb/8fAK2XXzwl/0FjMXgQksUgG0vgCPSB74IbEzNzEVam4uosvcTd+19RAXj9NV1HcPoShUTDPpnAQYrLDzQP4xgay1hrBn34UXu3W9ZCnQ8iiBJ+gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tuist generate command&quot;
        title=&quot;Tuist generate command&quot;
        src=&quot;/blog/static/3bfe07ea07ca9ad4d4f8ab25cd2447ae/6164f/tuist-generate.png&quot;
        srcset=&quot;/blog/static/3bfe07ea07ca9ad4d4f8ab25cd2447ae/9ec3c/tuist-generate.png 200w,
/blog/static/3bfe07ea07ca9ad4d4f8ab25cd2447ae/c7805/tuist-generate.png 400w,
/blog/static/3bfe07ea07ca9ad4d4f8ab25cd2447ae/6164f/tuist-generate.png 658w&quot;
        sizes=&quot;(max-width: 658px) 100vw, 658px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Before using Tuist, creating a framework for a new μFeature&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; at SoundCloud was manual, error-prone work. Those days of work are now behind us, as it takes just one line of code and a couple of seconds to run the &lt;code class=&quot;language-text&quot;&gt;tuist generate&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;For example, this is the project definition of our μFeature Search:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3ef42090d8683220360c3c37c2bae663/0a9c2/search.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.893296853625166%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABp0lEQVQoz53O20vTYRzH8cfTdA5/c8rWnIdtP506NTFhZHiALA9EF7UpUuE2p3gTzIqoC11eeBPRTYEhJInosoNz4sXGPEBl4R/1g9/bZ4cbCS/q4sXn4cuH7/MVu5lTLXF0piUP/2jJo/+zf3ymJQ5/a19TPzXxdm2b6MtXzL6IEV5YIvwkbyYnVrB0qcjTGKHoIivv1vme/oV4NP9cdzQ26UXuFl0obl1UF5glxZlndl3OourCYNdvTczo31I/dBGYXcBY56NW7cfcOoTFO4ziGZCGMDp9lDq6EdbWvxTZstoovuJFLmbYH2J7P4MIRKKY6ntQmq9T6fJRpfZhkqo9fVi9g9S03UBRr1HlzlPUXkzOHiqaeqlouIri6qbY6uHm/SBbe2nEveBjhLERYetE1HgQFqm2Xf7cjqG+k9I6LwZHNjty7/KGLkrs8ipbl+y3UGaX/UoHA3cfEs9euPj6PXcezDE+GWJsIsRoTpCRQJDb/ulcjmTTf3E2GpgudMOMT0V4tvyGraRcuJlIs/45yYeNL6x+2mF14999jCfY3E0RPzjhHAEERaEZiDJAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Search project definition&quot;
        title=&quot;Search project definition&quot;
        src=&quot;/blog/static/3ef42090d8683220360c3c37c2bae663/8ff1e/search.png&quot;
        srcset=&quot;/blog/static/3ef42090d8683220360c3c37c2bae663/9ec3c/search.png 200w,
/blog/static/3ef42090d8683220360c3c37c2bae663/c7805/search.png 400w,
/blog/static/3ef42090d8683220360c3c37c2bae663/8ff1e/search.png 800w,
/blog/static/3ef42090d8683220360c3c37c2bae663/6ff5e/search.png 1200w,
/blog/static/3ef42090d8683220360c3c37c2bae663/0a9c2/search.png 1462w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The result of running Tuist is the creation of a non-trivial project all set up with our common dependencies, build settings, multiple targets, an example app, etc. Everything is generated by a single line of code:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0de7544370839f8d8fe966b24f23dd30/34106/search-project.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.50978564771668%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABrUlEQVQoz3VSa2/jIBD0//9t7fe2Uk9pr6ksx2BMMRgWPLesH4lVHcrYhMd4dmYbbT38HAUmAJQLStmRoacFkQqwLPxbUfdijALnHOY4y9x7j6btDfw0IeUFT58J1gWkFBkJcQ54/puhXAYzIud8gChjKQuIz2WeVxARmq+PV7y/v8H9OIyqg+p7UbCrqaMkB+9G8P1jjVKA0lfEQphzFERGE2yH769PllsPDNBai7q9tMwsmRJ//b5W34kienODp4ApTQwvaLYPyrjdbmjbVvw4Llc1sSo07C+XuVRCVp0TUrAyl4e8UQnvpXVdh8vlAqXUSU1hoqr6HtYKGZsNO06ExhiEENZzsv6gcBql/HUdbMMMrb7hOeFUWG1ecSIcx1Ha4ERYL/PBGMMprJIJ1mpE7oi8cML8nzigE2H1b/dwvbwrnODswEHQUWrilP98vKDVHcbAQjiYH67kRFhDuV6vorT21L6XNw9pI1xTTmhVi94qjN5K0pX0V8nDMHApVkhkb1MYpjXRRw+Nabnc2lr/KbmGUgkPdWXzkHttDo7VPYTCvlrbcxC0BUKCf+s4qwAewYTuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Search generated project&quot;
        title=&quot;Search generated project&quot;
        src=&quot;/blog/static/0de7544370839f8d8fe966b24f23dd30/8ff1e/search-project.png&quot;
        srcset=&quot;/blog/static/0de7544370839f8d8fe966b24f23dd30/9ec3c/search-project.png 200w,
/blog/static/0de7544370839f8d8fe966b24f23dd30/c7805/search-project.png 400w,
/blog/static/0de7544370839f8d8fe966b24f23dd30/8ff1e/search-project.png 800w,
/blog/static/0de7544370839f8d8fe966b24f23dd30/34106/search-project.png 1073w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;With Tuist, We Finally Got Control of Our Project&lt;/h3&gt;
&lt;p&gt;By using Tuist, we didn’t just improve our productivity, but we also upped the consistency of our projects. All the modules are now generated using the same rules, expressed in Swift, using &lt;a href=&quot;https://tuist.io/docs/usage/helpers/&quot;&gt;Tuist project helpers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If we want to change a build setting or a linking rule across all our frameworks, we can simply do it in a single place and regenerate all our projects. It is a faster and more robust approach.&lt;/p&gt;
&lt;p&gt;This is an example of a project helper function that defines the generation of the main target of our μFeatures:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/244c11997e85fd73d949a0252e476a7f/40805/tuist-helpers.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.46254458977407%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACo0lEQVQ4y42Ty08TURSHLxbb0oelj5mWlmlrC5136QOk0JbyaHiDCyK48D8wJi4NxqWJG4lbFxCRmBSQxCgpiRvBv4qE5P48M22CGnwsvpw7i/vld86Zy47OLq5aZ9+vWqcXV0ftc+Kiy/l/c0yOY3JYLvbh9BuePN1Gc3MDo8tbuLfwoMOixSbGCevbquNLW1R/pbL8EKXmBh49foYWudi7kzYvNdY4C2V4j6ByJhqcRU3OIhpnYfoOKZwJOmeBu5z5k0SqW7sE0pzdjnK1usTfn5xxtnf4Gfnp+/BmpiBoswhrTQTlBkK5CgR1AlFtAn3JEdyKqmCijB5RsbkVVeCIKegd0MCCGeQb69g7/AK2S0Jzag2ebB398hSCygxCahMhbQ5RkuaKdaTNMp0L8CU1woA7oZFUBhNytphSkmMVlqsrXIUjXiRhg5JW4EmVEcjVICrTEIeqiCljkMxJxNRxBNKGLXXE1JuFVky9vgKHoJFgFkF1Dl5q0Z8y0Z8pwCPpcEklukitCXQ5MkzkbJmV8o8J2Z00QtRuWJ+nOgVnIg8WyoKFhwiqwnBHJlzL/iFM2S0HVUopT9vLcUkFuJNlO6EzrtNySpS+M0MmWMLc34X+7CT8NDMfVYtOm7nuZmV7044oVTHXSfzTDI2bhBFjHgPFdQjGAkRzEaIxi3h+BlKhgUS+jphehag3EFFpYXoN/swYdVBCT1Sn32YNu62OkBv1Vc58Se4cLHJvtsrdyVHukso8MFzlQbnGvekSdyUM7iR64yZ3DFAlKDk9BJnT/LleW+GWi+2ftFGhZ9VLv4E3mbfn0zeo27jobOEeNGz6LKRrPJIJX2qE7ir209z/2AY7+PT1cuftweWLV28un798Tex0+f3cYfsGrLuWw3L9AM0O6gr+6uXfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tuist Helpers&quot;
        title=&quot;Tuist Helpers&quot;
        src=&quot;/blog/static/244c11997e85fd73d949a0252e476a7f/8ff1e/tuist-helpers.png&quot;
        srcset=&quot;/blog/static/244c11997e85fd73d949a0252e476a7f/9ec3c/tuist-helpers.png 200w,
/blog/static/244c11997e85fd73d949a0252e476a7f/c7805/tuist-helpers.png 400w,
/blog/static/244c11997e85fd73d949a0252e476a7f/8ff1e/tuist-helpers.png 800w,
/blog/static/244c11997e85fd73d949a0252e476a7f/6ff5e/tuist-helpers.png 1200w,
/blog/static/244c11997e85fd73d949a0252e476a7f/2f950/tuist-helpers.png 1600w,
/blog/static/244c11997e85fd73d949a0252e476a7f/40805/tuist-helpers.png 1682w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There are also other popular open source tools for generating Xcode projects, such as &lt;a href=&quot;https://github.com/yonaskolb/XcodeGen&quot;&gt;XcodeGen&lt;/a&gt;. But we decided to use Tuist because of its ability to express rules in a powerful language like Swift, as opposed to the more verbose approach of using a data interchange format like YAML or JSON. In addition, our developers prefer to edit projects in Swift on Xcode to benefit from code completion and all the features the IDE provides.&lt;/p&gt;
&lt;h2&gt;Serendipity&lt;/h2&gt;
&lt;p&gt;On our way to generate the SoundCloud project, two unplanned yet fortunate discoveries occurred. The first one is that we got better compilation times for our μFeatures. This is because, when using the &lt;code class=&quot;language-text&quot;&gt;tuist generate&lt;/code&gt; command, the tool also generates a separate workspace on which developers can work in isolation.&lt;/p&gt;
&lt;p&gt;So for example, when we want to make some changes on the Search feature, we can quickly generate a workspace that contains only the Search framework and all the modules needed to compile it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ebc02e9d0638daf49208d70e87cbdf62/sc-tuist-focus2.gif&quot; alt=&quot;Tuist focus&quot;&gt;&lt;/p&gt;
&lt;p&gt;Surprisingly, a smaller workspace resulted in faster compilation times, probably because Xcode and its compiler have an easier job understanding which source files should be indexed and recompiled and which ones don’t need to be.&lt;/p&gt;
&lt;p&gt;The second unplanned advantage was that we cleaned up our project. While writing the rules to generate SoundCloud, we got rid of: files that were not part of any targets, duplicate resources, and dependencies that should not have been linked. Also, the generated project looks tidier: for example, all of our dependencies are now ordered both by type and alphabetically.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Generating the project was not an easy journey, as the process of writing the manifest files can be tedious and error-prone. Debugging problems at the project level is not always easy, nor is it easy to validate if the generated project is semantically equal to the previously existing one, even with the support of a great open source tool like &lt;a href=&quot;https://github.com/bloomberg/xcdiff&quot;&gt;xcdiff&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, the benefits clearly overcame the costs for us. Being able to quickly change the setup of all our projects is a huge step toward further modularization of the SoundCloud app. It enables our iOS engineers to quickly create more modules while we are still able to keep the architecture under control.&lt;/p&gt;
&lt;p&gt;Looking to the future, we are planning on using Tuist to continue our process of extracting code from the main application to feature modules, since it’s increasing the productivity of our engineers. We are proud to contribute to open source projects like Tuist, and we are very interested in the further development of the tool that has already proven to be a game changer!&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;A μFeature is an &lt;a href=&quot;https://github.com/tuist/microfeatures-guidelines&quot;&gt;architectural approach&lt;/a&gt; to structuring iOS applications to enable scalability, optimize build and testing cycles, and ensure good practices on your team.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[A Happy New Employee]]></title><description><![CDATA[My first six months at SoundCloud as an iOS engineer on the Recommendations team have just finished. In that time, I’ve already contributed…]]></description><link>https://developers.soundcloud.com/blog/a-happy-new-employee</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/a-happy-new-employee</guid><pubDate>Fri, 07 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My first six months at SoundCloud as an iOS engineer on the Recommendations team have just finished. In that time, I’ve already contributed to around 15-20 repositories. I have made a couple of changes to our frontend and backend, ranging from a minor improvement in tooling to a newly baked feature. I feel I contribute to the team and grow every day. And I’m happy.&lt;/p&gt;
&lt;p&gt;Is this because I’m smart and hardworking? Well, this is just one part of it. 😉 But there’s something about the culture and practices here that enable me to succeed. So I decided to take some time to articulate exactly what these things are.&lt;/p&gt;
&lt;h2&gt;Engineering Practices That Make the Onboarding Experience Better&lt;/h2&gt;
&lt;p&gt;If you are new to a company, it likely means you have a myriad of random questions every day, perhaps every hour. But I’m an introvert — maybe you can relate. I don’t want to bother people, especially for things I don’t necessarily &lt;em&gt;need&lt;/em&gt; to know. And I don’t want to ask questions without knowing how to ask them. Would I go to someone and say “I’m curious about this. I have no idea how it works or what it is for. Could you explain this to me?” No, never.&lt;/p&gt;
&lt;p&gt;Even so, I’ve found that something like this is possible at SoundCloud because people here are very supportive. However, what really helped me were the engineering practices in place that allowed me to explore the code base, documentation, and infrastructure on my own, following my curiosity and learning what I could until I needed someone’s help.&lt;/p&gt;
&lt;p&gt;So, here’s a collection of random things that I found useful for quickly onboarding. Some of them may be widely known practices in the context of SRE and microservices. Some of them may be so common that no one bothers to mention them. Nevertheless, I’d like to share them while I still maintain an outsider’s view.&lt;/p&gt;
&lt;h3&gt;Standardized Technology Stack&lt;/h3&gt;
&lt;p&gt;SoundCloud is a distributed system built on top of a gazillion microservices, as explained in &lt;a href=&quot;https://developers.soundcloud.com/blog/category/microservices&quot;&gt;many previous posts&lt;/a&gt;. These microservices enable product teams to work autonomously, but doesn’t it mean that individual services grow so differently that I need to start over whenever I look into a new project? Well that’s not what’s happening at SoundCloud. Instead, I see common patterns appear in many places, again and again.&lt;/p&gt;
&lt;p&gt;What do I mean by this? Well, we have shared frameworks, with which one can get many things for free. We have shared scripts that package and deploy applications. Most importantly, we have a dedicated team that continuously applies the latest practices to different services so that none of them falls behind too much and ends up confusing engineers with outdated information.&lt;/p&gt;
&lt;p&gt;This means that once I learn one thing, I can transfer it to other projects, and the learning curve is not just linear.&lt;/p&gt;
&lt;h3&gt;Example Applications as a Showcase of Best Practices&lt;/h3&gt;
&lt;p&gt;In addition to the above, we have internal example applications that capture our best practices. These allow us to understand the essentials of how individual apps are configured, deployed, and monitored without being distracted by subtleties.&lt;/p&gt;
&lt;p&gt;We can also quickly and easily start a new project using the example applications as templates. For the simplest application, we could start it in a day just by copying an example application and making the relevant tweaks to it.&lt;/p&gt;
&lt;h3&gt;Practical Hands-On Working&lt;/h3&gt;
&lt;p&gt;No matter how good the documentation is, we sometimes still need to mess up things in order to know what we don’t know. But how can we do that without causing havoc?&lt;/p&gt;
&lt;p&gt;Here at SoundCloud, we have a hands-on program that everyone can try. This program allows developers to provision a new machine, deploy the aforementioned example application to it, and set up monitoring for it in the same way our production services do.&lt;/p&gt;
&lt;h3&gt;First Responder&lt;/h3&gt;
&lt;p&gt;As a new hire, you may have questions, some of which seem too basic that you are almost ashamed of asking. In fact, you might not even know where to ask them. So you finally work up the courage to post the question in a chat room that looks relevant. You keep coming back to the thread, hoping your question made sense and someone eventually replies to you, but no one responds in the end. Everyone knows this feeling. Understandably, people are often too busy to spend time clarifying what you want to ask, or maybe they expect someone else who knows the subject better will answer it.&lt;/p&gt;
&lt;p&gt;At SoundCloud, many teams have a “first responder.” This is a person designated on a rotational basis to answer all the questions asked while they are in charge. Having someone like this greatly reduces the mental cost of asking questions because we know who we can bother, and there’s at least one person responsible for answering any questions.&lt;/p&gt;
&lt;h3&gt;Content Management Tool Open to Everyone&lt;/h3&gt;
&lt;p&gt;If your company operates with a fairly big number of users, you probably have an internal admin tool where you can manage users and their contents. What I experienced in the past was that it was open only to operational teams.&lt;/p&gt;
&lt;p&gt;At SoundCloud, everyone can access this tool, though its capabilities are limited by the permissions set for the individual user. When selecting an account, one can quickly tell which experimental features it is getting, what its contents are, when it was created, which program it subscribes to, and so on. This information often explains why a problem happens only to specific users, and it helps facilitate communication with other teams while debugging.&lt;/p&gt;
&lt;h3&gt;Infrastructure as Code and a Code Search Tool&lt;/h3&gt;
&lt;p&gt;It’s common for new people to face permission issues, and if you need to talk to different people whenever you have these problems in order to gain access to something and solve the problem, it will likely discourage you from exploring new things.&lt;/p&gt;
&lt;p&gt;Here at SoundCloud, in most cases, the way systems are provisioned can be found as code in a single place. Along with an internal code search tool, which enables fast cross-repository search, we can easily identify what we’re looking for by searching with an error message or our account name and fixing it ourselves by updating the relevant configuration.&lt;/p&gt;
&lt;h3&gt;Monitoring with User-Friendly Metadata&lt;/h3&gt;
&lt;p&gt;When you start in a new company, it’s a great opportunity to find issues that many people have gotten used to and ignore. However, if you can’t narrow them down, your report probably won’t help. Imagine that you are a mobile engineer and find a 500 error that happens intermittently. If you report it ambiguously like “I got a 500 error when I opened screen X and it never happens again,” it probably just adds another ticket that no one ever looks into.&lt;/p&gt;
&lt;p&gt;We have been making a rigorous effort to make debugging information useful and user friendly. For example, you can find an improvement we made to the tracing metadata in the &lt;a href=&quot;https://developers.soundcloud.com/blog/using-kubernetes-pod-metadata-to-improve-zipkin-traces&quot;&gt;Using Kubernetes Pod Metadata to Improve Zipkin Traces&lt;/a&gt; blog post. In a scenario like this, I can check the trace and tell exactly where things go wrong and who is in charge of the issue. With this information, I can rewrite the occasional 500 error report with more specific information, which can be like “An upstream service X timeouts when requesting information of user Y.”&lt;/p&gt;
&lt;h3&gt;SAT (Self-Allocated Time)&lt;/h3&gt;
&lt;p&gt;Allowing people to work on side projects in working hours has been a popular concept since Google shared more about its 20-percent time policy, but it easily becomes a window dressing, and people don’t do it for many reasons. For example, they can’t find a suitable project or people to work with, or they can’t find time for it because project deadlines are always tight. And once the majority of people stop doing it, it’s hard for other people to justify continuing.&lt;/p&gt;
&lt;p&gt;We have this same concept here at SoundCloud, but we refer to it as SAT, or self-allocated time. And I’m happy to report that I see many people actually taking time on Fridays for this. I myself use this time to work on the things I know the least about. It also gives me a chance to collaborate with people whom I don’t usually work with. It often pays off because it adds a new tool to one’s toolbox, which, sooner or later, becomes useful in regular projects too.&lt;/p&gt;
&lt;h3&gt;Documentation&lt;/h3&gt;
&lt;p&gt;I put this at the end of the list because this looks fundamental, but it works as collective knowledge only if the subject can be accessed and tested by everyone, and if it’s supported by the things I mentioned above.&lt;/p&gt;
&lt;p&gt;What I like about how we update the documentation at SoundCloud is that it’s not authoritative. Although people ask other’s reviews for things they are not too sure about or when they think that other people should be aware of a change, the documentation can always be updated straight away. This removes the mental cost of updating documents and encourages more people to do so, which, in the end, also allows for minor errors which might be introduced by this non-authoritative model to be quickly fixed.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As engineers, we’re all familiar with the idea of being driven by our curiosity so profoundly that we forget to sleep or feel like we can work forever (which of course is untrue). And while smarter people often perform well, there are many high performers who do so because they’re the ones most excited about learning and putting that learning into practice.&lt;/p&gt;
&lt;p&gt;What I’d like to emphasize with this post is that you and your company can leverage those people by having good engineering practices in place and allowing people to explore things freely without barriers to learning.&lt;/p&gt;
&lt;p&gt;Or, more simply put, let good people do their jobs.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Speeding Up Builds with Dagger Reflect]]></title><description><![CDATA[A large portion of an Android app’s build time can consist of Dagger annotation processing, and most developers agree that productivity is…]]></description><link>https://developers.soundcloud.com/blog/dagger-reflect</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/dagger-reflect</guid><pubDate>Thu, 30 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A large portion of an Android app’s build time can consist of Dagger&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; annotation processing,&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and most developers agree that productivity is important, so we decided to experiment to see if we could save time when compiling the SoundCloud Android app. This blog post covers how we used Dagger Reflect to save developer time with minimal changes to our codebase. Before we dive deep into how we saved Dagger annotation processing time, let’s make sure we’re on the same page in understanding what dependency injection is.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.droidcon.com/media-detail?video=380844287&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAACbUlEQVQozwFiAp39AEy2wkuSm0aSm1udpFWdpmWWmrWUhrqPfbKeko7X34zk95ze7ce2rNO4rMHSzejexriaksdLSpZmQKN4LQBav8qcx8yUytF7v8elztOizNGSw8qRw8mZzdWS2t/A0JXC1KCy38bAyrLmtJapm5QoKSFJQ0GvlmKBVhsAN7rKQrbERbrGRrvIUb3KV77LYMTPZsPNbMbSccvViczAndK2rtasttah6Ni4r6OhiFg8WD83wqVt4o0kACeotyKKlyeLly2Qmy+OmDSRm0Ons0iptUmep0iUnVCgrYC6wai/vbSuqeK5p/vx6b6GbbCDbdLB0cRHnQAfo7Meg48kj5wmiJMri5cui5Y1l6E6maQ8kZw8i5VUoqm0pZKoq6SjxbXMwIny1LrFjXGyinK7oJu1Y5UAFae4EY2bF5GgGY6cH5WjKJekMaCtMpijOJynPpymQ6SwXM/eldCussZ9jMizwJiI0Z9xzJZvx6p8udCqACi8zEbI10HG1U7K2VHL2jbC0jC+zjfA0T7E0ju/0kWrvKW5dIu/nay8bbS3lZ6bv5GLk6SIf6XFvsXRiwAbrL46q7c0p7Q+rro/qrcmrb0mucssuss1u8gmmLA2f6uFyJ1rtanUpUX/uIC0ioefVVqQSGLEoaCi0sMAA6G3AIeZBIqaBouaC5qpF7PEHrXHIbnLLKm4l0tP15Fpo7ldOpGUkmiV9ZV71m5Aun+HrX+I0aCmroCJAAWrwwWnvgipvwypvhCwwhO1xhi0xRq4ySemtrxQTv+cWqquSi+Wp4tpuvCOgdRwRK1cc7tUaOBziKxVWoirWfsyf3I1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;droidconsf&quot;
        title=&quot;droidconsf&quot;
        src=&quot;/blog/static/68ddbda0aacdf5ed4b8dc29f35f7da6a/8ff1e/droidcon-sf-speaker-image.png&quot;
        srcset=&quot;/blog/static/68ddbda0aacdf5ed4b8dc29f35f7da6a/9ec3c/droidcon-sf-speaker-image.png 200w,
/blog/static/68ddbda0aacdf5ed4b8dc29f35f7da6a/c7805/droidcon-sf-speaker-image.png 400w,
/blog/static/68ddbda0aacdf5ed4b8dc29f35f7da6a/8ff1e/droidcon-sf-speaker-image.png 800w,
/blog/static/68ddbda0aacdf5ed4b8dc29f35f7da6a/6ff5e/droidcon-sf-speaker-image.png 1200w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Basics of Dependency Injection&lt;/h2&gt;
&lt;p&gt;Dependency injection is a great way to write maintainable, testable code by using the dependency inversion principle. For more information on the basics of dependency injection, take a look at this &lt;a href=&quot;https://www.vogella.com/tutorials/DependencyInjection/article.html&quot;&gt;introduction tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;History of Dependency Injection&lt;/h2&gt;
&lt;p&gt;Dependency injection frameworks in the Android world started by bringing practices from the Java server side to Android. At the time, these frameworks, e.g. &lt;a href=&quot;https://github.com/google/guice&quot;&gt;Guice&lt;/a&gt;, were reflection based, meaning they inspected the program’s code during runtime. &lt;a href=&quot;https://blog.nimbledroid.com/2016/03/07/performance-of-dependency-injection-libraries.html&quot;&gt;This approach&lt;/a&gt; was especially slow on Android. Then &lt;a href=&quot;https://github.com/google/dagger&quot;&gt;Dagger&lt;/a&gt; came along and alleviated this problem by generating code at compile time instead of using reflection at runtime. However, the tradeoff to the faster app startup time was a longer time spent compiling, which was especially noticeable as apps grew in size.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/487afaffa2f2da376dad0cae945959e5/05a72/annotation-42-secs.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.69517884914463%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9ElEQVQ4y3VUa5OiMBDk//+zvav7eOuqiyAISi1vEkggoXcmQKnnXld1hRCm7XlET+sRTdehqpuFTetYVDVK2iulMU0TxvFO3mutIYTAMAxP556ig7YTLjgvSuRl5dbsq0CWFxSgKMC4oGU1q8BIgvJVECsyErnmJSw9GzyDAzrKwhgDa5dTpZRzyOsjvHmeAWJPqUdVi1vX49JKGGvhzgjspixLNE3jHDFYvK5rJ8rgb5ke1geGHA2iRiImqunukwWrqnoSZLRt+yq4iWljkZK7hNxFDaVi7oLcABZkbilKKX92OD84rNWIoBaOPRUYDymzWFEUrp78PdeU37Hwjw55zcTgXBa9JqqnGm7pmdW5pRqzw/8KCj0hJGdp2+PxRxhcN3Z0xxLMgtv7F8HRzriQu4rS/hfskLkJbSuLbU26dxlLqYQc0NCg1q2A6MmR7DGs4oYaxjdmnTBsMcstMqvndQ6hEkwyxsfuDbv3N+w/fuHz+AeH/W/U+ZGmOoPtLxi7M7X7SnZvpJRiEjGK6w5d4bv9PCSO3jxR220PKUq87//idD7iFB4RRj784IBz7COgd8ww+sQlCaCH2sUMFFPlKXRfuT1reZtVRU3ZBxHO6Q1+lMCPE5yIUZrhGMY4hJHb3+h6GrvOLo1WSX8immK3tL8B+rs+eeBsvG0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;42 seconds&quot;
        title=&quot;42 seconds&quot;
        src=&quot;/blog/static/487afaffa2f2da376dad0cae945959e5/8ff1e/annotation-42-secs.png&quot;
        srcset=&quot;/blog/static/487afaffa2f2da376dad0cae945959e5/9ec3c/annotation-42-secs.png 200w,
/blog/static/487afaffa2f2da376dad0cae945959e5/c7805/annotation-42-secs.png 400w,
/blog/static/487afaffa2f2da376dad0cae945959e5/8ff1e/annotation-42-secs.png 800w,
/blog/static/487afaffa2f2da376dad0cae945959e5/6ff5e/annotation-42-secs.png 1200w,
/blog/static/487afaffa2f2da376dad0cae945959e5/05a72/annotation-42-secs.png 1286w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the above example from our app, a 59-second build spent 42 seconds processing annotations. More specifically, the 42-second processing time is spent checking the code, analyzing the object graph, and generating the code. To put this in perspective, in the SoundCloud Android app, Dagger generates a very large component class with more than 10,000 lines of code.&lt;/p&gt;
&lt;p&gt;So this is how we as developers using Dagger started to suffer. What this meant for us here at SoundCloud is that we needed to find a solution that saved time. This sparked looking into Dagger Reflect. We did this because it offered the possibility of the best app startup time for users and the best compile time for developers, with little changes to the production code. Another option would have been to migrate our entire codebase to a runtime- or reflection-based dependency injection framework. However, we decided against it because the cost of migration and the slower startup times were not worth it.&lt;/p&gt;
&lt;h2&gt;How Dagger Reflect Works&lt;/h2&gt;
&lt;p&gt;Dagger Reflect is an open source project by Jake Wharton that mimics the API of Dagger.&lt;sup id=&quot;fnref-3&quot;&gt;&lt;a href=&quot;#fn-3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; However, instead of generating code at compile time, it parses injectable objects and the component dependencies at runtime. This skips generating and regenerating the same code for every build. It instead uses a &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html&quot;&gt;dynamic proxy class&lt;/a&gt;, which implements an interface specified at runtime. Dagger Reflect proxies an application’s Dagger Component to the Dagger Reflect runtime.&lt;/p&gt;
&lt;h2&gt;How to Use Dagger Reflect in Your Project&lt;/h2&gt;
&lt;p&gt;There are a few ways to use Dagger Reflect in your project. One approach is full reflection, and the other approach is partial reflection, as outlined in the &lt;a href=&quot;https://github.com/JakeWharton/dagger-reflect/blob/master/README.md&quot;&gt;Dagger Reflect README&lt;/a&gt;. You can also swap the dependencies manually for every module. The simplest approach is to use the Delect Gradle Plugin, something that was created here at SoundCloud to make integrating Dagger Reflect simple and painless. For this blog post, we are going to cover the partial reflection approach using Delect.&lt;/p&gt;
&lt;p&gt;The partial reflection approach requires the least amount of changes to your existing codebase, and almost no downside — it takes only 60 ms of code generation to glue your code to Dagger Reflect. Here’s how to start using Dagger Reflect in your project.&lt;/p&gt;
&lt;h3&gt;Step 1&lt;/h3&gt;
&lt;p&gt;Make sure to use the Dagger Component Factory or Component Module annotations:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component
  public interface ApplicationComponent {
    @Component.Factory
    interface Builder { }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Step 2&lt;/h3&gt;
&lt;p&gt;Ensure that all Qualifier annotations have &lt;code class=&quot;language-text&quot;&gt;AnnotationRetention.RUNTIME&lt;/code&gt;. Additionally, all &lt;code class=&quot;language-text&quot;&gt;MapKey&lt;/code&gt; annotations must have runtime retention:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class LibraryStorage&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Step 3&lt;/h3&gt;
&lt;p&gt;Apply the Delect plugin to your root build script:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;buildscript {
  classpath &amp;#39;com.soundcloud.delect:delect-plugin:&amp;lt;latest_version&amp;gt;&amp;#39;
}
apply plugin: &amp;#39;com.soundcloud.delect&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will automatically swap Dagger for Dagger Reflect when the Gradle property &lt;code class=&quot;language-text&quot;&gt;dagger.reflect&lt;/code&gt; is set to &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Delect — the Dagger Reflect Gradle Plugin&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/soundcloud/delect&quot;&gt;Delect is our open source Gradle plugin&lt;/a&gt; that makes it much easier to switch out all the regular Dagger dependencies for the Dagger Reflect versions in your project. Here is an example of the following dependency swap you would need to perform in each Gradle module:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;implementation “com.google.dagger:dagger:&amp;lt;latest-version&amp;gt;”
implementation “com.google.dagger:dagger-android-support:&amp;lt;latest-version&amp;gt;”
lintChecks “com.jakewharton.dagger:dagger-reflect-lint:&amp;lt;latest-version&amp;gt;”
if (useDaggerReflect()) {
  implementation “com.jakewharton.dagger:dagger-reflect:&amp;lt;latest-version&amp;gt;”
  kapt “com.jakewharton.dagger:dagger-reflect-compiler:&amp;lt;latest-version&amp;gt;”
} else {
  kapt “com.google.dagger:dagger-compiler:&amp;lt;latest-version&amp;gt;”
  kapt “com.google.dagger:dagger-android-processor:&amp;lt;latest-version&amp;gt;”
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The plugin works by using the &lt;a href=&quot;https://docs.gradle.org/current/userguide/resolution_rules.html&quot;&gt;Gradle Dependency Substitution API&lt;/a&gt;. When it detects that there is a dependency on the Dagger runtime, it adds the Dagger Reflect runtime. When it detects the Dagger compiler or the Dagger Android compiler, it swaps them out for the Dagger Reflect compiler. There is no need for a separate runtime or compiler component in order to use the Dagger Android processor.&lt;/p&gt;
&lt;p&gt;To give you an idea of how it works, here’s a small snippet of code &lt;a href=&quot;https://github.com/soundcloud/delect/blob/master/buildSrc/src/main/java/com/soundcloud/reflect/DaggerReflectPlugin.kt#L58&quot;&gt;from the plugin&lt;/a&gt; where the Dagger annotation processor is substituted for the Dagger Reflect annotation processor:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;substitute(
  module(&amp;quot;$com.google.dagger:dagger-compiler&amp;quot;)
).apply {
  with(module(&amp;quot;$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}&amp;quot;))
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In some initial trials, we noticed that developers’ workflows switched from the IDE to the command line while working on the same task. Only enabling Dagger Reflect for IDE builds triggered a full rebuild when switching and vice versa. Because of this painful rebuild, we chose not to enable Dagger Reflect in the Gradle Plugin only in IDE builds. Instead, Delect is enabled or disabled via a user controlled Gradle property, &lt;code class=&quot;language-text&quot;&gt;(dagger.reflect)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A quick way to enable Dagger Reflect when the plugin is applied is the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;echo &amp;quot;dagger.reflect=true&amp;quot; &amp;gt; ~/.gradle/gradle.properties&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We also seed our Gradle build cache with Dagger Reflect enabled; otherwise, building with Dagger Reflect would always be the slower option.&lt;/p&gt;
&lt;h2&gt;Performance Benchmark&lt;/h2&gt;
&lt;p&gt;Before we dig into the numbers, we want to note that we’ve spent a lot of time optimizing our builds by using incremental annotation processors and enabling caching, and as a result, have solved &lt;a href=&quot;https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses&quot;&gt;as many build cache misses as we possibly could&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Though on average, local developer builds are under 60 seconds, some builds can take as long as 3-5 minutes here at SoundCloud. As an example, the following build scan shows a build that took 5 minutes and 44 seconds.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/cb8c4a1333877c8e197c4ee9530641c5/e4566/annotation-3-mins.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.4247491638796%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAB10lEQVQ4y42TC3OiMBSF/f8/b2d2qraioggqlkd4BERO77kSFmpnupkJCUnuyXcfWUCabVpc4htyY2CKAkmWobaWW3g8Hjr2fY+6rrW7tZ/agp+mbRHeEhjbwnb/Dreynok4x67rYOUSI5dy7i753hf8sHUyRqbCtRACtyaGeZ6riGuFeHC/30fBF0K3mNsGfmJwyspRkGRxHCNJEhUpyxJpmur6r4Ldo0csdCRsB7cpQkJSkbaqqnH+q+CttAiyAmVzF8GnQdM06q5LAkfS/hdhUrf4rJrZJg1H40GAxNMYTvuYZRqluUEu7hiJU1FWg2Q/uufOMdPTUpo2zbJmTkSWGw9vHx7W3g6ef0BVW5QSszTLtSYpxvixjKYxpDiJ3SWjy2fJ5iGMEF1jnM4XBNEZh1Oonf+1bV5o9FEMtcl4zwR9MfzztlLS5WaLlbfF+3av/3/XH9jsDyIeieE8ISRjKbEC5kkRV47hWcn84El1FGIS+sEJ4eWq8WUovjfWJ5/kTJCukozx8/yjEu2Ogf6vtzulpXAsZcOn2k7iRpdfBEmwlKRs9v7YKfy+26v7K0kW56S+3j4RyXlH+6MgN+l2bp6lwxIqhhJiN8VzXlb1S3JI6nS+AObQjPUktKaFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;3 mins annotation processing&quot;
        title=&quot;3 mins annotation processing&quot;
        src=&quot;/blog/static/cb8c4a1333877c8e197c4ee9530641c5/8ff1e/annotation-3-mins.png&quot;
        srcset=&quot;/blog/static/cb8c4a1333877c8e197c4ee9530641c5/9ec3c/annotation-3-mins.png 200w,
/blog/static/cb8c4a1333877c8e197c4ee9530641c5/c7805/annotation-3-mins.png 400w,
/blog/static/cb8c4a1333877c8e197c4ee9530641c5/8ff1e/annotation-3-mins.png 800w,
/blog/static/cb8c4a1333877c8e197c4ee9530641c5/e4566/annotation-3-mins.png 1196w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Out of all the annotation processors in use here, Dagger takes the most amount of time by far. It took a total of 3m 9s in the above example.&lt;sup id=&quot;fnref-4&quot;&gt;&lt;a href=&quot;#fn-4&quot; class=&quot;footnote-ref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;When using Dagger Reflect, the compiler never spends more than 80 ms processing annotations — a huge decrease in build times.&lt;/p&gt;
&lt;p&gt;Our Dagger Reflect benchmark in our Android project led to a decrease in build times from 62 seconds to 31 seconds for an ABI change to a module. Dagger Reflect makes builds take half as long!&lt;/p&gt;
&lt;p&gt;But what about overall build speeds for all our developers? In a four-week period, our average build time for all local builds without Dagger Reflect was 81.13 seconds. The build time with Dagger Reflect enabled was 55.33 seconds. This is, on average, a 26 second and &lt;em&gt;32% decrease&lt;/em&gt; in build speeds!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;No Dagger Reflect&lt;/th&gt;
&lt;th&gt;With Dagger Reflect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 398px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/32781ec968f6aaa35ce71ae8b96d0246/2b173/without-reflect.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 149.2462311557789%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAABYlAAAWJQFJUiTwAAAD0ElEQVRIx61VZ3PbRhDFt0x6T+z09s/yMRmnjNPzNzKTalUXxXKScRx7rIydUSwWUJJtFYpiAUmxN4kgQVIkCPBld8mjRFmORx7d6AnAEXi3u7f3ntZq7aLd7sC2u0N0Orag23XkmX/f3W3/L5iHrxq/XKlsI5VKIZncgmHEkc1mCTlEozGZq1ZNuebzBVmIPz4IJrOsBjS+qdXqKBSKyOXyhBzK5QqKxRLS6YwQm2ZNnpm40xmNVkWviDX+Z9s2HjRcx5USOI4D1+2h1+vJPX/P80zMEEKHJvKlMuYWfFhe38DC8l347qxicS0oCKyuY6daQzpXgNVoDhdpNpuSUavVklIMCTlCDjuSTGGb0tqhFMs7VdSpJozS9g66tDkM9CDR8eAILcuiCLujEfIEkxipDBLprERSpI0KxeKEBEVfQZQWCxkJxLbSaFCUBaozB6EGkw1ryClzRJ7bK7g678ESpc3kK5sR3A5uClE4sYW7oTCWgyGJmjNRhBzxCCHnf6QxSFmR3UOodln9+CAcNkYI+cF1XSryw8IZ9qK2vymPA0PCxjGh3zZt2q2ufS/so0Pr0dnUc1W8P2/gg1txfLgQx6kFAx95DHxM+MRr4FPCaZ+Bzwif+w184Y/hSz2GrwhfB6L4hrEYxbcEDY6N8VAB2g86HjmzSAjgsbEAnhgP4KmJAJ6Z1PHclI4XpnW8dFbHiXN+nDzvx6sX/Hh9xoc3Zrx461cv3r7oxbuzXiKk1CaF0I9HiYjxOJE9OaHjaSJ7lsieJ7IXz/rxMpMRXjnvw2sXvEL25oDsnVkP3rvkgeZQypGKhe/Wcvgx2MdPhJ83+viFcCaUxdgA45tZTAwwGc5iKpzBVCSD6QE0Vo8OqQVs2pjOQ6K9B2ob6p/do7ZH6z6gtsExD214PumPj5+o84GrvOP2Ffow8HsMfk9TH8ZJrthkcsUyTPIY1sBa3UKxvC1qXSW9bDYPP27sSayRzKPR2nLjJck3SDznPH4R0dm5m7h8819MXb4qdnDur+v448a8WIVSFnVs2eDYwPopc0qUDhNWzToy+aJEZFJ0/Kz0kqNtNlt9ZSKF4XtFquZN0+wTcoT6yjqyhRIZUnDochzNraU7ouRmzRIlXw1H5ZnH/rR50VKpNJpyirxk7Pcr4iNcy3A8iek/r2Hm2t9YC8dw8foN/KMv4fuZ36QsXOt2e0/+JGW1KcFoXFLcIGOKkH+Uyel4ASZif2GCSCIl3rJpJKXe9XpjxORZbDXVFn3FdvaMnVvmPnKvBpsbO6aCtM1Bj3D3+YYsdERv+Q+akbwF7q42KAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;81.13 secs average&quot;
        title=&quot;81.13 secs average&quot;
        src=&quot;/blog/static/32781ec968f6aaa35ce71ae8b96d0246/2b173/without-reflect.png&quot;
        srcset=&quot;/blog/static/32781ec968f6aaa35ce71ae8b96d0246/9ec3c/without-reflect.png 200w,
/blog/static/32781ec968f6aaa35ce71ae8b96d0246/2b173/without-reflect.png 398w&quot;
        sizes=&quot;(max-width: 398px) 100vw, 398px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 384px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0d3dcbb4e7fae32e898feb5bf44864a6/45483/with-reflect.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 153.12500000000003%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAfCAYAAADnTu3OAAAACXBIWXMAABYlAAAWJQFJUiTwAAAD90lEQVRIx62W6XIbRRDH9yNFcV+pUJyheAWqeAc+Q4UU9/UOvATfgIQjiQ2JCQkkpMiBFeTYjqSV40PHateWJcu2ZCXGuu9j909378HKZQOGTNVPM7Mz899Wz0z3Ku12B81mC51OF61WW+BnjNvmsWq1ikajKW332W54vsI/mUwWKytpJJMaNC2FXG4dhrGMWCyOVEpHqVSWfrF4B71e33uhn263h0qlCsV9mzvA7a6v3263ZbJtXQd9EuQ+w+L9/kBq10qFfwaDIQ30qR6gxSIs6izq+BbyC3iOW3hNrVaTWua6gsPhEJGlBC4GphCNJXEjHEVoIYbwYhzhpThmbi+iXm8it1lAk1zkllarRW4o0h40PSs9QZ64U67AHJqe+fycJ7LVlmkBli00pDl2PSTLujBNc9RCNllfXSOyWNAMrK5vIpffwuz8EhLLaWQ38ogbaRjZHFbW1mU3N7aK6NE6t7Agu8Tx4YBENhBajOH6bBjp3AbWSFBLZ6Bn1uQF2c28CBrUlx2t1sT3XCzLGhVkkw9SWMDf3lOQHsOkAdO0vEn7sVfxBN2jwKLsbK4PytDZSE/wXiG7zIe4SZ1Gi+70XrQPhviQ7hMdKjoCg/+Pwvf2wuo2Plsq4PPEFlHAF8SXSZvjxAmtgK+Ir5lUAd8Q3zJ6HieJU4yRx2lCfPjKRQ3KcRX3nZwjorifeOBUFA+djuKRMRWPjat44jsVT32v4tCZCA4TT5+N4JmJMJ77IYznz4Xw4o8hHDkfovBFgq9eIsETKonMidCDJPTwWBSPjkfxOIk9SWKHSOzwGdURiuBZEQrjBeIIib1EYi9fCNk+/DSSw2vXdLwRWMbrAYNqA0dvGHiTOPa7gbeCBt4O6nhnSse7xHs3U3h/OoUPiA9nUviI+HhWwye3NCc4dG0G/5meh8KBs95oEM19aPjqf0ax5LqZ9wxl96W3dl16F/Nv7rG/iCDHNT4+JgVMvtcS5nmtg+kEVG5zKrDzSW8ktzAccEVwa/sPxCmQclB1A+r03IIE21uUCjj4cjrgulypjSQx3tSdnZJkRo6PIpi/cxfRhCYBdT6pI0OCAcorTFC9LdH6cnAaN6Pz2KbFbip1U0WZUgenUM+HLMjW8ETOK5wq+W9z7QL2oZM79kq9vJYzoAhuFu8iSdZNhlRMXPkN479cRalcxdlfr+NnyoSXp2bEhQ36wvDEOn9ZaAt3JW+L4DolnEV9Wf4uC567FkAklsDE1Un8NBnE2KUrMib+81noh595m1Kp1ZEnK2v1hiwqkT92yMJtcjS7QChVJNt1Rr4qRmE3Kd7Z8ucMF2Dk+HCoZyv2g9cq/kPsX2+3Ladv/euM+CdSMARLfyS6xwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;55.33 secs average&quot;
        title=&quot;55.33 secs average&quot;
        src=&quot;/blog/static/0d3dcbb4e7fae32e898feb5bf44864a6/45483/with-reflect.png&quot;
        srcset=&quot;/blog/static/0d3dcbb4e7fae32e898feb5bf44864a6/9ec3c/with-reflect.png 200w,
/blog/static/0d3dcbb4e7fae32e898feb5bf44864a6/45483/with-reflect.png 384w&quot;
        sizes=&quot;(max-width: 384px) 100vw, 384px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;However, we see the biggest wins on the longest builds because those are the ones that spend the most amount of time in annotation processing. For simply applying a Gradle plugin in the root of a project, this is probably the easiest build speed win!&lt;/p&gt;
&lt;p&gt;To sum it up, we saw that Dagger was taking a large portion of our build times. We saved up to 50 percent of our local build times with little maintenance cost by swapping regular Dagger for Dagger Reflect. Try it out in your project!&lt;/p&gt;
&lt;h2&gt;Additional Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For more information about Dagger Reflect, &lt;a href=&quot;https://github.com/jakewharton/dagger-reflect&quot;&gt;see here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For more information about Delect, &lt;a href=&quot;https://github.com/soundcloud/delect&quot;&gt;check out the GitHub repository&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click for recorded version of this &lt;a href=&quot;https://www.droidcon.com/media-detail?video=380844287&quot;&gt;talk at Droidcon SF&lt;/a&gt; as well as corresponding &lt;a href=&quot;https://speakerdeck.com/runningcode/dagger-reflect-the-circle-from-runtime-to-compile-time-and-back-again-af6dae83-4030-4e23-8598-12dc0dd0afa2&quot;&gt;slides&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/google/dagger&quot;&gt;Dagger&lt;/a&gt; is a popular &lt;a href=&quot;https://en.wikipedia.org/wiki/Dependency_injection&quot;&gt;dependency injection framework&lt;/a&gt; in the Android and JVM world that leverages annotation processing.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;Annotation processing is a way of hooking in to the compilation to analyze source code and generate code.&lt;/p&gt;
&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;
&lt;p&gt;Dagger Reflect is designed to be used for development builds only.&lt;/p&gt;
&lt;a href=&quot;#fnref-3&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-4&quot;&gt;
&lt;p&gt;This time is cumulative, not wall clock time. Multiple threads or Gradle tasks could be compiling using Dagger in parallel and the time spent in parallel might be double counted.&lt;/p&gt;
&lt;a href=&quot;#fnref-4&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[The Power of Types for Errors]]></title><description><![CDATA[At KotlinConf 2019, I talked about the power of types. In essence, I discussed limiting the number of primitives we use in our code in favor…]]></description><link>https://developers.soundcloud.com/blog/power-of-types-for-errors</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/power-of-types-for-errors</guid><pubDate>Mon, 20 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At &lt;a href=&quot;https://kotlinconf.com/&quot;&gt;KotlinConf 2019&lt;/a&gt;, I talked about the &lt;a href=&quot;https://www.youtube.com/watch?v=t3DBzaeid74&quot;&gt;power of types&lt;/a&gt;. In essence, I discussed limiting the number of primitives we use in our code in favor of using custom types instead. In this way, we as developers will not only reduce the possibility of bugs by using the compiler, but we will also achieve more readable and self-documented types.&lt;/p&gt;
&lt;p&gt;Today I ran into a situation which reminded me of the things I said in this talk.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=t3DBzaeid74&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAv/aAAwDAQACEAMQAAABw2LaQWSb/8QAGhAAAgIDAAAAAAAAAAAAAAAAAAIDMgEREv/aAAgBAQABBQLliKzW3khFRT//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEv/aAAgBAwEBPwFL/8QAFREBAQAAAAAAAAAAAAAAAAAAABL/2gAIAQIBAT8BU//EABgQAQADAQAAAAAAAAAAAAAAAAABETFR/9oACAEBAAY/Al8TetSx/8QAHBAAAwACAwEAAAAAAAAAAAAAAAERIUExYXGB/9oACAEBAAE/IUxWKfDm9CW/ZmGKVBs90Np3c//aAAwDAQACAAMAAAAQgO//xAAXEQEAAwAAAAAAAAAAAAAAAAAAAREh/9oACAEDAQE/EF51/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAERIf/aAAgBAgEBPxBSMf/EABsQAQEBAAMBAQAAAAAAAAAAAAERACExYUFR/9oACAEBAAE/EBAqBFemLAFgWS3KU02ol8fuPwx1eMwQwAfRQykC0crv/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;kotlinconf&quot;
        title=&quot;kotlinconf&quot;
        src=&quot;/blog/static/e5327b0c4755d511915d1650fcebe88b/a296c/kotlinconf.jpg&quot;
        srcset=&quot;/blog/static/e5327b0c4755d511915d1650fcebe88b/f544b/kotlinconf.jpg 200w,
/blog/static/e5327b0c4755d511915d1650fcebe88b/41689/kotlinconf.jpg 400w,
/blog/static/e5327b0c4755d511915d1650fcebe88b/a296c/kotlinconf.jpg 800w,
/blog/static/e5327b0c4755d511915d1650fcebe88b/c35de/kotlinconf.jpg 1200w,
/blog/static/e5327b0c4755d511915d1650fcebe88b/6c315/kotlinconf.jpg 1280w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Where It All Started&lt;/h2&gt;
&lt;p&gt;I was working on some tracking code that’s part of the signup flow of our Android app. The goal was to improve the insights from the error states that users might run into. These could be local UX flaws, but they could also be issues that arise when trying to log in via third parties.&lt;/p&gt;
&lt;p&gt;My initial code looked like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; details&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;ErrorType&lt;/code&gt; is an enum that specifies the exact error type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; ErrorType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   IO&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   PASSWORD_INVALID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   RECAPTCHA_FAILED
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The additional parameter details are for extra information some errors might need. But let’s take a closer look at the method signature we had:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; details&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I’d say these two parameters belong together, as the details describe the error. This means they have a very tight coupling, but they still don’t share any relationship! Rather, they are just two independent parameters.&lt;/p&gt;
&lt;h2&gt;Let’s Express This Relationship&lt;/h2&gt;
&lt;p&gt;The first idea for improving this situation is to wrap both parameters into one type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; details&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
Our tracking method now looks much nicer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once we do this, there will only be one parameter in our method, making it clear to the reader what to pass:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And they can’t pass in something wrong, as was possible when we had two independent parameters.&lt;/p&gt;
&lt;p&gt;This only confirms the quote I shared in my talk from the late Robin Milner, a British computer scientist who &lt;a href=&quot;https://www.sciencedirect.com/science/article/pii/0022000078900144?via%3Dihub&quot;&gt;once said&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Well-typed expressions cannot “go wrong.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;We Can Do Better&lt;/h2&gt;
&lt;p&gt;But we should not stop here! The method is now saved, but the two parameters have simply been moved to the &lt;code class=&quot;language-text&quot;&gt;Error&lt;/code&gt; class, so there is still a hidden relationship!&lt;/p&gt;
&lt;p&gt;So, we should express this. We should not let the developer guess when to pass what and when or what not to! &lt;/p&gt;
&lt;p&gt;An enum type could not hold these individual details, as enum values are simply constants, which are by definition immutable. However, with sealed classes, we can model this more precisely:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; InvalidPassword &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; RecaptchaFailed &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IOError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; details&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;The Next Step&lt;/h2&gt;
&lt;p&gt;Another thing that enums do not support is a hierarchy. Enum values are flat on one level of abstraction.&lt;/p&gt;
&lt;p&gt;To compensate, we normally express this via the name:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; ErrorType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   IO&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   EMAIL_DENIED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   EMAIL_EXISTING&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   EMAIL_INVALID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   PASSWORD_INVALID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   RECAPTCHA_FAILED
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But as I mentioned in my talk:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don’t put in a name what can be in a type.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, once we get rid of the enum, it’s easy to introduce a hierarchy:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; SignInError &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; EmailError &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SignInError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; Denied &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EmailError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; Invalid &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EmailError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; Existing &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EmailError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IOError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SignInError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you might have noticed, we could even restrict the email-related errors to sign-in use cases. This way, we could handle all errors related to one use case at one point simply by using types.&lt;/p&gt;
&lt;h2&gt;And Then I Saw…&lt;/h2&gt;
&lt;p&gt;There was another area of the code that wasn’t using the full power of types. When trying to log in via social networks, the code normally first handles third-party library calls and then proceeds with the login flow to your own system with those results. On the other hand, when you log in directly via an email, you won’t have this first step.&lt;/p&gt;
&lt;p&gt;The code had something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// An error on Facebook/Google appeared.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onAuthenticationError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class=&quot;language-text&quot;&gt;Method&lt;/code&gt; is another enum:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Method &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   FACEBOOK&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; GOOGLE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; EMAIL
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But as the code comment says, &lt;code class=&quot;language-text&quot;&gt;onAuthenticationError()&lt;/code&gt; is meant only for Google and Facebook. A developer should not even be able to call it with &lt;code class=&quot;language-text&quot;&gt;Method.EMAIL&lt;/code&gt;. But nothing prevents us from doing this. We could add an assertion and crash on invalid input, but both runtime crashes and hidden code comments are not ideal. The ideal scenario would be that the compiler could check this upfront.&lt;/p&gt;
&lt;p&gt;Thanks to our new error hierarchy, this can be solved very easily with types:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onAuthenticationError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SocialError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class=&quot;language-text&quot;&gt;SocialError&lt;/code&gt; is just another specialty in our sealed &lt;code class=&quot;language-text&quot;&gt;Error&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; SignInError &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SocialError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SignInError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GoogleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SocialError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FacebookError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SocialError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In summary, types are very powerful. With the changes mentioned above, our code became easier to read and more expressive, and a new developer will know how to use the tracking methods simply by the types they need as arguments.&lt;/p&gt;
&lt;p&gt;Here are a few key takeaways from this blog post to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you see parameters that belong together but are treated separately, check if there is a hidden type.&lt;/li&gt;
&lt;li&gt;If you see classes with fields that are only used in certain cases, maybe there is a hidden type hierarchy.&lt;/li&gt;
&lt;li&gt;If you see methods that only allow a certain combination, secure them with a type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And, of course, if you’re interested in learning more about this, you’ll find more examples in my &lt;a href=&quot;https://speakerdeck.com/dpreussler/the-power-of-types-kotlinconf-2019&quot;&gt;slides&lt;/a&gt; and by watching the &lt;a href=&quot;https://www.youtube.com/watch?v=t3DBzaeid74&quot;&gt;recording&lt;/a&gt; of the talk.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Implementing Dark Mode Using the Observer Pattern]]></title><description><![CDATA[Last week’s update to the SoundCloud iOS app includes support for Dark Mode. This took several months of work and collaboration between…]]></description><link>https://developers.soundcloud.com/blog/dark-mode-observer-pattern</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/dark-mode-observer-pattern</guid><pubDate>Fri, 08 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9f2bb119f1570b22f3d0f703746adc51/cc43b/banner.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAeUzsIwf/8QAGhABAAEFAAAAAAAAAAAAAAAAAQIAAxAiMf/aAAgBAQABBQJoiubG0nv/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAEBAAMBAAAAAAAAAAAAAAABAAIQIdH/2gAIAQEABj8Cvdpl0Cb/xAAbEAADAAIDAAAAAAAAAAAAAAAAASERMRBBof/aAAgBAQABPyFpB/In3GeHsVIGgTuKeo//2gAMAwEAAgADAAAAECDv/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQIBAT8QWf/EABwQAQACAgMBAAAAAAAAAAAAAAEAESExUWFxgf/aAAgBAQABPxDZtY54qAhBFFp4WLTNnkJBibToHUIAFAsfZ//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;banner&quot;
        title=&quot;banner&quot;
        src=&quot;/blog/static/9f2bb119f1570b22f3d0f703746adc51/a296c/banner.jpg&quot;
        srcset=&quot;/blog/static/9f2bb119f1570b22f3d0f703746adc51/f544b/banner.jpg 200w,
/blog/static/9f2bb119f1570b22f3d0f703746adc51/41689/banner.jpg 400w,
/blog/static/9f2bb119f1570b22f3d0f703746adc51/a296c/banner.jpg 800w,
/blog/static/9f2bb119f1570b22f3d0f703746adc51/c35de/banner.jpg 1200w,
/blog/static/9f2bb119f1570b22f3d0f703746adc51/8179c/banner.jpg 1600w,
/blog/static/9f2bb119f1570b22f3d0f703746adc51/cc43b/banner.jpg 4000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Last week’s update to the SoundCloud iOS app includes support for Dark Mode. This took several months of work and collaboration between design and engineering teams across the company, so we wanted to share our approach to implementing Dark Mode and some of the obstacles we encountered along the way.&lt;/p&gt;
&lt;p&gt;Apple did an excellent job implementing Dark Mode in iOS 13, but because it’s important for us to support compatibility with previous versions of iOS, we decided to build our own implementation of themes. To achieve this, we built a fast, type-safe, and powerful observer pattern in Swift.&lt;/p&gt;
&lt;h2&gt;Observer Pattern&lt;/h2&gt;
&lt;p&gt;The observer pattern is a one-to-many relationship between objects, meaning that when one object changes its state, all of its dependent objects are notified and updated with the new state. Looking at this design in another way, the observer pattern can be seen as a delegation pattern with one-to-many delegates. We can think of it as the foundation of reactive programming.&lt;/p&gt;
&lt;p&gt;The key objects in this pattern are the &lt;em&gt;subject&lt;/em&gt; and the &lt;em&gt;observers&lt;/em&gt;. A subject may have any number of dependent observers, and all observers are notified whenever the subject undergoes a change in state. It’s important to note that the observers do not access the state directly, because this can lead to dangerous race conditions.&lt;/p&gt;
&lt;p&gt;How? It could be that while an observer is accessing the subject’s state, the state is changed from another thread. But the possible combinations of things that can go wrong here are exponential, so it’s a requirement that the subject informs the observers about state changes and never the other way around.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 491px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/502e61b8f647e521bd358d44e2668cf5/3bb78/uml.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.973523421588595%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABN0lEQVQoz3WSy46CUAyGef93YuXOyAIXJioQRZEgVy8gKNDJ1+QQnMw0aZrTy9/277Fer5ecz2fZ7XZSVZU8n0+5Xq9S17W0bStGPp+PjOP4pcaHGGsNwyB5nito0zQKVBSFJtxuNwUty1Iej4fEcSxJkqhN01R9pukEuFqtxHEcwa7XawVEka7rtEkURWqPx6M2PhwOaue5E+BisRDbtmW5XOpEBOg8l/v9Lvv9XjzPm+z7/dY8k2tosOAMJ53gk2LsXCjOskzXhQ4Uvqnr+/4r15o/zNjwCihAFBheoYAYPuLwiOVNLsNZvy+HkEQQnk6nk/i+r6sCzFTwicUHn8TZYLvd/j0hhSRcLhcFRVkXOuAZJc6xiIVhqAO4rvs/IBc1B+CP8j1YDVC+URAEGmcqcpHNZiM/BbGv0I5sW8gAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;uml&quot;
        title=&quot;uml&quot;
        src=&quot;/blog/static/502e61b8f647e521bd358d44e2668cf5/3bb78/uml.png&quot;
        srcset=&quot;/blog/static/502e61b8f647e521bd358d44e2668cf5/9ec3c/uml.png 200w,
/blog/static/502e61b8f647e521bd358d44e2668cf5/c7805/uml.png 400w,
/blog/static/502e61b8f647e521bd358d44e2668cf5/3bb78/uml.png 491w&quot;
        sizes=&quot;(max-width: 491px) 100vw, 491px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;State to Be Notified&lt;/h2&gt;
&lt;p&gt;But first we need something to notify the observers of, which is the state that we want to spread across X number of observers. In our case: We want to notify the observers when a color theme changes across the app.&lt;/p&gt;
&lt;p&gt;Let’s start by defining an abstract &lt;code class=&quot;language-text&quot;&gt;Theme&lt;/code&gt; as our state:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; main&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIColor&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can define, for example, a concrete &lt;code class=&quot;language-text&quot;&gt;DarkTheme&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DarkTheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; main&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIColor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;black
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Observers&lt;/h2&gt;
&lt;p&gt;In our case, an observer is going to be a &lt;code class=&quot;language-text&quot;&gt;View&lt;/code&gt; or a &lt;code class=&quot;language-text&quot;&gt;ViewController&lt;/code&gt; that is interested in using colors. Following the “&lt;a href=&quot;https://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to-an-interface/384067#384067&quot;&gt;program to an interface&lt;/a&gt;” philosophy, let’s first define an observer as a protocol:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can implement a concrete observer — for example, in a &lt;code class=&quot;language-text&quot;&gt;LoginViewController&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoginViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        backgroundColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s great! Now our &lt;code class=&quot;language-text&quot;&gt;LoginView&lt;/code&gt; is an observer that can be notified about &lt;code class=&quot;language-text&quot;&gt;Theme&lt;/code&gt; updates.&lt;/p&gt;
&lt;h2&gt;Subject&lt;/h2&gt;
&lt;p&gt;Here is when things get really interesting. We need to define an object that will allow observers to subscribe to state changes by providing an interface for attaching and detaching observers. In addition, the subject will provide an interface to reach observer objects when its internal state changes. In response to a state change, the subject will broadcast the new state to all subscribed observers.&lt;/p&gt;
&lt;h3&gt;Abstract Subject&lt;/h3&gt;
&lt;p&gt;Following the interface segregation principle, let’s start defining the interface for a subject that allows observers to subscribe to and detach from it. This interface is going to be visible from any observer that wants to subscribe to notifications, however, the interface will make it impossible to access the state directly, thereby preventing race conditions or undesirable state updates from the clients:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Subject&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Themable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Themable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Concrete Subject&lt;/h3&gt;
&lt;h4&gt;Internal Data Structure&lt;/h4&gt;
&lt;p&gt;We want to be able to keep a reference to each observer, but without taking care of the memory management of these objects. In essence, we need to find a data structure that holds objects weakly, in order to prevent memory leaks.&lt;/p&gt;
&lt;p&gt;First and foremost, we want to keep one (and only one) reference to each object to prevent a double subscription of the same observer. The main reason is that multiple subscriptions of the same observer can result in multiple notifications of new states.&lt;/p&gt;
&lt;p&gt;In addition, we need to provide a fast and cheap way to subscribe new observers.&lt;/p&gt;
&lt;p&gt;If you’re thinking this sounds like a &lt;code class=&quot;language-text&quot;&gt;Set&lt;/code&gt; data structure, you’re correct! We needed to build a &lt;code class=&quot;language-text&quot;&gt;Set&lt;/code&gt; that can only hold objects weakly. So after some research, we found a Foundation class called &lt;code class=&quot;language-text&quot;&gt;NSHashTable&amp;lt;ObjectType&amp;gt;&lt;/code&gt; that can reference objects weakly and add and access objects in constant time, O(1):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; observers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSHashTable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;weakObjects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To make the data structure more useful, we can extend it and create a reusable generic &lt;code class=&quot;language-text&quot;&gt;WeakSet&lt;/code&gt; class:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Foundation&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WeakSet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AnyObject&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Sequence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ExpressibleByArrayLiteral&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; objects &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSHashTable&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;weakObjects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; objects&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; object &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; objects &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;convenience&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arrayLiteral elements&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;elements&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; allObjects&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allObjects
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Note that this count may be an overestimate, as entries are not necessarily purged from the `NSHashTable` immediately.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeIterator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AnyIterator&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iterator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;objectEnumerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AnyIterator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            iterator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; T
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Unit Test&lt;/h3&gt;
&lt;p&gt;And of course, &lt;a href=&quot;https://gist.github.com/matiasvillaverde/5d3b895fcc77bd9d6ef0749b56258427&quot;&gt;let’s unit test it&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Concrete Subject&lt;/h3&gt;
&lt;p&gt;Now that we have a solid data structure to work with, we can define a concrete subject:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeSubject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Subject&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; observers&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;WeakSet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        observers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        observers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Now Let’s Bring Everything Together&lt;/h2&gt;
&lt;p&gt;Sweet! So far, we have created a &lt;code class=&quot;language-text&quot;&gt;ThemeObserver&lt;/code&gt; and our &lt;code class=&quot;language-text&quot;&gt;ThemeSubject&lt;/code&gt;. Now let’s provide a default way to subscribe our observers to the subject. We can do this as a protocol extension of &lt;code class=&quot;language-text&quot;&gt;ThemeObserver&lt;/code&gt;. We will define a subscribe method that will access a shared instance of &lt;code class=&quot;language-text&quot;&gt;ThemeSubject&lt;/code&gt; to subscribe our view to the subject:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribeForThemeUpdates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;ThemeSubject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can add a state to our &lt;code class=&quot;language-text&quot;&gt;ThemeSubject&lt;/code&gt;. In our example, this will be &lt;code class=&quot;language-text&quot;&gt;Theme&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeSubject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Subject&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; observers&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;WeakSet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        observers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        observers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;didSet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        observers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;compactMap &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; observer &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
            observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s awesome! Now every time we set a new state to &lt;code class=&quot;language-text&quot;&gt;ThemeSubject&lt;/code&gt;, all our observers’ views will receive the notification with the new theme. The code below shows how we can use this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoginViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ThemeObserver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;subscribeForThemeUpdates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        backgroundColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Remember that adding new views as theme observers will be in constant time, and changing the theme across our app will be linear. Quite powerful, right?&lt;/p&gt;
&lt;h2&gt;Obstacles We Encountered along the Way&lt;/h2&gt;
&lt;p&gt;While implementing themes on the iOS app, the major problem we encountered was the inconsistency of &lt;code class=&quot;language-text&quot;&gt;UIColor&lt;/code&gt; use. This is because some parts of the app set colors on Storyboards or XIB files, while in other parts, the colors were used as global variables. This led to us putting a large amount of effort into refactoring the UI layers in order to conform to the &lt;code class=&quot;language-text&quot;&gt;ThemeObservable&lt;/code&gt; protocol.&lt;/p&gt;
&lt;p&gt;Another problem we discovered was the inconsistency and lack of UI components. For example, we found eight different implementations of the loading spinner. We thought this was a good opportunity to unify several views into common UI components.&lt;/p&gt;
&lt;h2&gt;When We Should Apply the Observer Pattern&lt;/h2&gt;
&lt;p&gt;The observer pattern is a powerful tool that we should all have in our toolset. So I’d like to share some tips on how to identify opportunities to apply it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When an abstraction has two aspects, one depends on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.&lt;/li&gt;
&lt;li&gt;When a change in one object requires changing others and we don’t know how many objects need to be changed.&lt;/li&gt;
&lt;li&gt;When an object should be able to notify other objects without making assumptions about what these objects are. Or, put another way, when you don’t want these objects tightly coupled.&lt;/li&gt;
&lt;li&gt;When there are a lot of layers between the subject and observer so that the delegate pattern is not practical.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;p&gt;There are a handful of benefits that come from making use of the observer pattern. They include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abstract coupling between the subject and the observers. All a subject knows is that it has a list of observers, each conforming to a simple interface of the abstract &lt;code class=&quot;language-text&quot;&gt;Observer&lt;/code&gt; protocol. The subject doesn’t know the concrete class of any observer. As a result, the coupling between a subject and observers is abstract and minimal. Consequently, the observers can belong to different layers of abstraction in a system.&lt;/li&gt;
&lt;li&gt;Support for broadcast communication. The notification is automatically broadcast to all interested objects that have subscribed to it.&lt;/li&gt;
&lt;li&gt;Benefits from reactive programming, without a huge third-party dependency.&lt;/li&gt;
&lt;li&gt;Compared to &lt;code class=&quot;language-text&quot;&gt;NotificationCenter&lt;/code&gt;, the type-safe approach that we propose can prevent runtime crashes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This post details how we created a dark mode for the SoundCloud iOS app using the observer pattern. But the pattern is not just limited to this kind of scenario. In fact, here are other real-world examples of where you can apply the observer pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A music player that wants to notify different architectural layers of the app that playback has started.&lt;/li&gt;
&lt;li&gt;When views need to observe and react to changes on a database.&lt;/li&gt;
&lt;li&gt;When sending notification about the app lifecycle, e.g. when the app is in the background, foreground, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do you have more ideas of other problems that can be solved with this pattern?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; question &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; questions &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    question&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Solving Remote Build Cache Misses by Annoying Your Colleagues]]></title><description><![CDATA[This is part two in a series about solving Gradle remote build cache misses. Solving build cache misses is important to both avoid work that…]]></description><link>https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses-part-2</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses-part-2</guid><pubDate>Tue, 01 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part two in a series about solving Gradle remote build cache misses. Solving build cache misses is important to both avoid work that has already been compiled and improve build speeds. For more information, check out part one &lt;a href=&quot;https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To understand how these cache misses happen, it is important to understand how we seed our build cache. &lt;a href=&quot;https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure_use_cases&quot;&gt;Gradle recommends&lt;/a&gt; that your CI system populates the build cache from clean builds and that developers only load from it. At SoundCloud, we also pull from the build cache on CI in order to make our CI builds faster. This is a diagram of what our remote build cache setup looks like.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5029c683c9dcd142b5276343646200ed/c70a2/image3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.05196182396607%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB60lEQVQoz41T22oTURTd50yS1okpvXorlsRSQ3qBjLe2Yh8qij73F/yAgih9FYQ+WBGlTdKSUGqspraIlmpEpKBQBJ/8AMEf8FEapsNkL/dctGJNcMNiz9nsvc46mzVEf8YKlJdiC7XTlIObqbA99oLt9DO2rQ225byrSgDl+b7fn2eDmsb0e+0TbiFJS8DJVWBiExheB8ZfAiPPmY2yR4hHfn8RTQlVmKPq6q0pWsReyxI7lzfZmRRc22IntYqakEAV+IHfOfM58i8i/dd5kLqS4zT3/UK0hGxfuW4NrbHV94StZNm1aBkWXbk5IX3xRspSguuCUcEpUjoj+UTz3chrlHFGcqsgIejaF2bEIqQjsZA4LTgn/aN0uKc3Kq/rvvdV967U9fHHrDvK0DS9/WtvLQKP9KxgUoSY+/dlp0wyO4cpcSRN8c6kVEYE/Y31GcG+j2XayOzI/q7f/hTsVK482kbUHRL9X6TGfFJ4rghUhrHgvKGHu9s0z68p536kAu769Ys3NAEHifJMNO8E3zl3Vtzwju58+0JzPz7I7CvPS2ivAJeqgF7zTbsTDippOEgotUSJfXWqgJ30W2BQPHpe5hOeR2WwZhbZ7X/KdvAXoBoONiSMF4M/igpcHdgAhir1vew6u4eKqP0E5R7LNTfN02kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Seeding cache setup&quot;
        title=&quot;Seeding cache setup&quot;
        src=&quot;/blog/static/5029c683c9dcd142b5276343646200ed/8ff1e/image3.png&quot;
        srcset=&quot;/blog/static/5029c683c9dcd142b5276343646200ed/9ec3c/image3.png 200w,
/blog/static/5029c683c9dcd142b5276343646200ed/c7805/image3.png 400w,
/blog/static/5029c683c9dcd142b5276343646200ed/8ff1e/image3.png 800w,
/blog/static/5029c683c9dcd142b5276343646200ed/c70a2/image3.png 943w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For the same task, differences in the list of inputs when the task is running on the CI system vs. on the local machine causes these build cache misses.&lt;/p&gt;
&lt;h2&gt;Blocking Slow Builds&lt;/h2&gt;
&lt;p&gt;So now on to the explanation of the title. Part of a build engineer’s role is to speed up builds. Improving build performance and avoiding work with caching is one way to achieve this, but another tool in the build engineer’s belt is that of disallowing slow builds. Sometimes, there are two paths that produce the same outcome. Let’s call them the slow path and the fast path. If we can always take the fast path, we can improve our build speed.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 419px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/70603488e2cd0a1203e737d7a5ddd23e/1b142/image1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 106.44391408114558%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsSAAALEgHS3X78AAAC80lEQVQ4y2NggIK95+6xgOjtJ66vvfD4y/9jN178BuJ/IHz85ss/5x5++r/z1M3jMPWrdp1gwAtO33vPDKJP3Hq96uTtN/9X7z75Y+PBC783HDj3e/m2Iz+Bhv4/fffdEZh6oMX4DTxy7RnYhfsu3N9+9+P//0euP/9/+t47kCH/j1x7/v/667//D15+fBmmfsPB8/gNXL//HCOIXrHjmCXQa5FnH3z02XX6dsje8/eCtxy9En7s5ssgoDedYOrnrNrOQBAsWL+HEYnrDcRMULYWEPuCGCJiEowMxIBNhy6C6S///zNmFFWxsXNwhHv4h3L+B/I9/EI4eXj5rIFYA6SGkZGRgY2dnYEUIAbEMlA2sotABqqTYhBMsyKSgehyIEPVsFiGF+gBMSseeV0gliPFUCMifGEKxPzEGCaI5CV8AJRuDbBJgGwRhRoEwvJQjM07PEAsBcTcQCwAdaUQuoFC0JgDuUoFiBWQDVSVkGAo9vaGGczLwsSkzc7CAgo/DW52dkNmJiYFsHOZmHCGJSh2ZUEMLnZ2FEXANMmErpaTjU0YxMhxc2PM9/BACWiYYmkkFzJICQpyzElLU12clQXO64GmprxdUVGgoGFoDAmZtKG4WJpQ7MlAvc1gpwHOGEyTExKcDjc0zL3a3V1+s7d36aMpU65f6+lZ/mrmzP+3+/vXH6yr6+2IjFQEWsCJzWAuINaBcUp9fMRPNDVV35806cPDyZP/n21r+3+xo+P/5a6u/4fq6v6+nzPn/7Np0/7vr6lZmO3mhtO1hsCAZ4Oy2Vbk5sYCDdhxo7f3zZOpU//f6uv793jKlP9AF/++M2HCDyC+u7WsLLYpNFQXl7cVYBETbmnJCYxlXhC7Jzo6flV+/vYHkyZtBLo0GWjBui2lpYr7amoyzJSV8eUsBnb03GKuogJKd2rxdnYSQEOVQGLWamrwMJsQF8cEjDy8OQFUQHCAGHycnCDvm3vo63PBJN/MmgWuMnZXVTG9nT0bnpwAgOMUC/vt0SQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Blocking slow paths&quot;
        title=&quot;Blocking slow paths&quot;
        src=&quot;/blog/static/70603488e2cd0a1203e737d7a5ddd23e/1b142/image1.png&quot;
        srcset=&quot;/blog/static/70603488e2cd0a1203e737d7a5ddd23e/9ec3c/image1.png 200w,
/blog/static/70603488e2cd0a1203e737d7a5ddd23e/c7805/image1.png 400w,
/blog/static/70603488e2cd0a1203e737d7a5ddd23e/1b142/image1.png 419w&quot;
        sizes=&quot;(max-width: 419px) 100vw, 419px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Often it isn’t obvious which path is the “slow path” and which path is the “fast path.” When confronted with two paths in real life, we usually ask Google Maps which path has the most traffic so that we can avoid it. With Android Builds, there may be limited guidance from Google. So, it is the build engineer’s role to investigate and disallow the slow paths in order to make engineers happy and productive.&lt;/p&gt;
&lt;h3&gt;Instant Run&lt;/h3&gt;
&lt;p&gt;One of the slow paths we discovered at SoundCloud was &lt;a href=&quot;https://developer.android.com/studio/run#instant-run&quot;&gt;Instant Run&lt;/a&gt;. At first, we didn’t realize that it was causing problems; we only saw that some of our build scans showed that some of our developers were building with the Gradle build cache disabled. Since we have &lt;code class=&quot;language-text&quot;&gt;org.gradle.caching=true&lt;/code&gt; checked in to our repository in the root &lt;code class=&quot;language-text&quot;&gt;gradle.properties&lt;/code&gt;, we were puzzled as to how it could happen. We paired with developers to troubleshoot these slow builds, and we quickly realized that they were being built with Instant Run.&lt;/p&gt;
&lt;p&gt;Instant Run was a tool designed to allow developers to deploy changes to devices faster, but it didn’t always work in large projects. One big limitation is that it disabled the build cache. This sometimes led to painfully slow builds, as everything had to be recompiled. Additionally, this meant that when building with Instant Run, both the local and remote build cache were disabled. We did a performance analysis of Instant Run in different scenarios, and it showed that in our codebase, Instant Run performed either equally as fast as a build without it, or slower — sometimes significantly slower.&lt;/p&gt;
&lt;p&gt;We already had a page in our internal wiki recommending our team disable Instant Run for faster builds, but nobody reads documentation! So how could we disable Instant Run for our team and save everyone from slow builds?&lt;/p&gt;
&lt;p&gt;Here is a Groovy snippet for Gradle which does exactly that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;def instantRun = project.getProperties().get(&amp;quot;android.optional.compilation&amp;quot;)?.contains(&amp;quot;INSTANT_DEV&amp;quot;)
if (instantRun) {
 throw new IllegalStateException(&amp;quot;Disable Instant Run: Android Studio -&amp;gt; Preferences -&amp;gt; Build, Execution, Deployment -&amp;gt; Instant Run&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The snippet checks the project for a specific property, which Android Studio injects into Gradle whenever Instant Run is enabled. When a build is run with Instant Run, the build fails and a helpful error message is displayed guiding the developer to disable Instant Run. This might be slightly annoying to colleagues, but it is all for the team’s benefit: A 10-second settings change is rewarded many times over with faster builds.&lt;/p&gt;
&lt;p&gt;Learn more about how &lt;a href=&quot;https://medium.com/@runningcode/is-building-from-the-terminal-faster-than-android-studio-175ead7dc2ca&quot;&gt;injected properties in Android Studio work here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Instant Run was removed in Android Gradle plugin 3.5.0.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Empty Directories&lt;/h3&gt;
&lt;p&gt;Another slow path we had at SoundCloud was that of empty directories. Empty directories in Gradle create a slow path compilation, which we should disallow. But how and why does this happen? Well, let’s suppose you have the following directory structure on your local machine:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;src/main/java/com/soundcloud/Player.java
src/main/java/com/soundcloud/Artwork.java
src/main/java/com/soundcloud/audio/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let’s assume you have the following on CI:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;src/main/java/com/soundcloud/Player.java
src/main/java/com/soundcloud/Artwork.java&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If audio is an empty folder, are the inputs to the task the same or not? The Java compiler itself ignores empty source directories. Gradle, however, does not. Empty directories are treated as different input properties. Well, that is, &lt;a href=&quot;https://github.com/gradle/gradle/issues/2463&quot;&gt;until this issue is resolved&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How can we push out a change to delete all empty directories on all our local project workspaces? If we take a cue from the previous section, we can fail the build if we find any empty source directories. This ensures that the directory structure, as Gradle sees it, is the same locally as it is on CI:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;allprojects {
 tasks.withType(SourceTask).configureEach { t -&amp;gt;
  t.doFirst {
    t.source.visit { FileVisitDetails d -&amp;gt;
      if (d.file.directory &amp;amp;&amp;amp; d.file.listFiles().size() == 0) {
        throw new IllegalStateException(&amp;quot;Found an empty source directory. Remove it: \nrmdir &amp;quot; + d.file.absolutePath)
      }
    }
  }
 }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above snippet, which should be placed in the root &lt;code class=&quot;language-text&quot;&gt;build.gradle&lt;/code&gt;, looks at all &lt;code class=&quot;language-text&quot;&gt;SourceTask&lt;/code&gt;s in the project. It traverses the source directory structure to find any empty directories. When it finds a source directory, it prints a helpful error message to the console telling you how to delete that directory. It performs this check before any source task is run.&lt;/p&gt;
&lt;p&gt;You can also remove all empty directories by running the following command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;find . -empty -type d -delete&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One thing to note is that if the root directory is empty, this does not fail the build.&lt;/p&gt;
&lt;p&gt;So, before adding this to our codebase, we needed to be sure this didn’t impact our build times. In this case, every single module in our project took less than 5 milliseconds to run this check, except one particularly large legacy module, which took 89 milliseconds to run.&lt;/p&gt;
&lt;p&gt;Is it worth it? We decided it was. Almost all of our developers were experiencing build cache misses while having a clean local Git state compared to master. After pushing the change, many developers started getting a little frustrated when they would have several build failures in a row because of nested empty directories. It is quite common to have these lying around after refactoring code or changing branches, since Git ignores empty directories. However, after the initial change, these failures became quite rare and we eventually increased our remote build cache hits.&lt;/p&gt;
&lt;h3&gt;CompileDebugAIDL&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/8d0ff/image5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.861111111111107%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA1klEQVQY012QDW7DIAyFe/+T7RZrp3VqkwD5A2NieDOOtKqL8uUFnm3ZvjBvCOEDYb5ji4RSyh/Mp4rIG8ch5pnPr9juXWoVuPCtOLsMy4qYElIiEJHeMf4/tTb18kl+aa1VC2pVNH1b1Y9g31ds22JkStoNI+ekkJ2Ze6Lm9KQ36llQeIbkEQdNqhN2Hd0PV/jxCjd8qt6wuC+s8w8oDig0Qthp/IiSBoPj085VWAtqh5P3uD+euscZXv+TjXxy6H5aazZB195N35lzzvy+lpSixfUOfwFBK4UFqtvPIgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CompileDebugAIDL&quot;
        title=&quot;CompileDebugAIDL&quot;
        src=&quot;/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/8ff1e/image5.png&quot;
        srcset=&quot;/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/9ec3c/image5.png 200w,
/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/c7805/image5.png 400w,
/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/8ff1e/image5.png 800w,
/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/6ff5e/image5.png 1200w,
/blog/static/12d0c29bd0abf362e4d3b65c8abca4dc/8d0ff/image5.png 1440w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Next, we saw a build cache miss for a generated class called &lt;code class=&quot;language-text&quot;&gt;InAppBillingService.java&lt;/code&gt;. This class is generated by the &lt;code class=&quot;language-text&quot;&gt;compileDebugAidl&lt;/code&gt; task that is part of the Android Gradle plugin. It looks for any source files ending in &lt;code class=&quot;language-text&quot;&gt;.aidl&lt;/code&gt;, and for each one, it generates a Java file before the &lt;code class=&quot;language-text&quot;&gt;JavaCompile&lt;/code&gt; task runs in your AIDL source directory. It then adds these generated Java files to the source directory of the &lt;code class=&quot;language-text&quot;&gt;JavaCompile&lt;/code&gt; task. We opened the generated Java source file to find this comment:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/**
 * This file is auto-generated. DO NOT MODIFY.
 * Original file /Users/no/workspace/soundcloud/android/libs/src/main/java/com/soundcloud/android/InAppBillingService.aidl
 */&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This file is always going to generate a remote cache miss, because the absolute path is different on every machine. We used the power of Gradle to solve this one as well:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;tasks.named(&amp;quot;compileDebugAidl&amp;quot;).configure {
  doLast {
    outputs.files.forEach { directory -&amp;gt;
      directory.traverse(type: FILES) { file -&amp;gt;
        file.setText((file as String[]).findAll {
          !it.contains(&amp;#39;Original file:&amp;#39;)
        }.join(‚\n&amp;#39;), &amp;#39;utf-8&amp;#39;)
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above code snippet finds the &lt;code class=&quot;language-text&quot;&gt;compileDebugAidl&lt;/code&gt; task and adds a &lt;code class=&quot;language-text&quot;&gt;doLast&lt;/code&gt; action, which iterates through each of its lines and rewrites it as is, skipping the line that contains “Original file.” The file is now the same on every machine. That solved the build cache miss for us, but we want to make the world a better place, so we &lt;a href=&quot;https://issuetracker.google.com/issues/121251997&quot;&gt;filed a bug against Google&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While examining this task, we also noticed that the &lt;code class=&quot;language-text&quot;&gt;CompileAidl&lt;/code&gt; task would take 1.3 seconds to perform an up-to-date check — something which should typically be done in less than 10 milliseconds. The reason it took longer is because the check filters the entire source tree. &lt;a href=&quot;https://github.com/gradle/gradle/issues/8559&quot;&gt;A Gradle engineer opened an issue about this for us&lt;/a&gt;, but in the meantime, as a workaround, we moved this AIDL file into its own module, reducing the run time to .003 seconds.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;There are, of course, a couple more build cache misses we fixed here and there that aren’t mentioned in this article. One fun thing to note: Make sure your annotation processors generate the same code locally as on CI. And when annotation processors generate lists, make sure they are ordered or sorted, as code may be processed in different orders on CI.&lt;/p&gt;
&lt;p&gt;The end result of all this work was quite noticeable when looking at build times because speed gains from modularization are multiplied by improving cache hits. In the week before this work started, our build times were 49.55 seconds on average.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/76cb8e39479ca79c859eeccb79c9d418/8d0ff/image4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.916666666666668%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAAwklEQVQY02VQ2QrCQAzs/3+eL+KLIIK1as+9ulfHzEqp2oGQbCbHZKsQI3wIyHkBsSybj8KFL1u5lDKcm2GshXVObP7UhoiqHydoIb4HEVk8Cyeli7GRi4kgvusHvNoez1eHfpjKW0ldxc35b9ga55wRUyrbGdMI5rjAWIdJa2hjimLvAyomtbHlpB+F0jwqBaXNjmtFzeF4wvlyxe3+QH1vYGQGhxaFPG32fveHRlS4ecuvHBvr5om2Gz5XxFS+gQrf+WiHFbX5pX4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Before build times&quot;
        title=&quot;Before build times&quot;
        src=&quot;/blog/static/76cb8e39479ca79c859eeccb79c9d418/8ff1e/image4.png&quot;
        srcset=&quot;/blog/static/76cb8e39479ca79c859eeccb79c9d418/9ec3c/image4.png 200w,
/blog/static/76cb8e39479ca79c859eeccb79c9d418/c7805/image4.png 400w,
/blog/static/76cb8e39479ca79c859eeccb79c9d418/8ff1e/image4.png 800w,
/blog/static/76cb8e39479ca79c859eeccb79c9d418/6ff5e/image4.png 1200w,
/blog/static/76cb8e39479ca79c859eeccb79c9d418/8d0ff/image4.png 1440w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Even accounting for four months of increased codebase size, after the majority of this work was done, our average build speed dropped to 36.84 seconds on average. You can also note the increased remote and local build cache usage.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/80c61460f17ee696ff909c16ef038f4a/8d0ff/image2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.63888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAAuklEQVQY012Q2QrCMBRE8//f5osi4rsgLmCbfW3HzNUWbSBMkjs5d1ExJaScMc8zuH611IqUMnIu4pmmSWJU6zy0sRi1EQ0xIsYENcolrZAFyE8E+RDgae6Ja2urjyDu1zAK0BiHYdRQrZu21S1nQhlfPEu81gZjnSSjEhRCRC4FyveDcU7a+4USpq2F9b4D/mNsb386Y3c44nK94f54wnYwoVIhyVugzPD7vu2g9WRSmdZ9LJ/5k8PEb7uQhyl/NWlhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;After build times&quot;
        title=&quot;After build times&quot;
        src=&quot;/blog/static/80c61460f17ee696ff909c16ef038f4a/8ff1e/image2.png&quot;
        srcset=&quot;/blog/static/80c61460f17ee696ff909c16ef038f4a/9ec3c/image2.png 200w,
/blog/static/80c61460f17ee696ff909c16ef038f4a/c7805/image2.png 400w,
/blog/static/80c61460f17ee696ff909c16ef038f4a/8ff1e/image2.png 800w,
/blog/static/80c61460f17ee696ff909c16ef038f4a/6ff5e/image2.png 1200w,
/blog/static/80c61460f17ee696ff909c16ef038f4a/8d0ff/image2.png 1440w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;From 49.55 to 36.84 seconds per build on average is a huge change in the day-to-day lives of developers, as our team averages around 40 builds per developer per day. It doesn’t just mean that developers are faster at writing code. Having faster build speeds means that developers have more time to explore the realm of possibilities when building new features. More explored possibilities leads to more maintainable code, increased app stability, and fewer bugs.&lt;/p&gt;
&lt;p&gt;If you’d like to learn more, check out “Remote Build Cache Misses” from &lt;a href=&quot;https://www.meetup.com/berlindroid/events/259762503/&quot;&gt;BerlinDroid Gradle Night&lt;/a&gt;. You can &lt;a href=&quot;https://youtu.be/2frfDMJwvf4?t=384&quot;&gt;watch the talk&lt;/a&gt; or &lt;a href=&quot;https://speakerdeck.com/runningcode/remote-build-cache-misses-and-how-to-solve-them-by-annoying-your-colleagues&quot;&gt;browse the slides&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Gradle Remote Build Cache Misses]]></title><description><![CDATA[Until recently, one of the top technical risks facing SoundCloud’s Android team was increasing build times. Our engineering leadership was well aware of the problem, and it was highlighted in our company’s quarterly goals and objectives as modularization. Faster build times means more productive developers. More productive developers are happier and can iterate on products more quickly. Modularization is key to decreasing build times, but avoiding work is another important part of the puzzle, and build caching is one way to avoid that work. Gradle, our tool for building Android, has a local file system cache that reuses outputs of previously performed tasks. We have been using the Gradle remote build cache in order to save our developers’ time. It helps us avoid redoing work that other teammates have already done or switching to old branches. However, to get the full benefits of caching, you have to go beyond simply setting it up.]]></description><link>https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/gradle-remote-build-cache-misses</guid><pubDate>Fri, 30 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Until recently, one of the top technical risks facing SoundCloud’s Android team was increasing build times. Our engineering leadership was well aware of the problem, and it was highlighted in our company’s quarterly goals and objectives as &lt;em&gt;modularization&lt;/em&gt;. Faster build times means more productive developers. More productive developers are happier and can iterate on products more quickly.&lt;/p&gt;
&lt;p&gt;Modularization is key to decreasing build times, but avoiding work is another important part of the puzzle, and build caching is one way to avoid that work. Gradle, our tool for building Android, has a local file system cache that reuses outputs of previously performed tasks. We have been using the &lt;a href=&quot;https://proandroiddev.com/speed-up-your-build-with-gradle-remote-build-cache-2ee9bfa4e18&quot;&gt;Gradle remote build cache&lt;/a&gt; in order to save our developers’ time. It helps us avoid redoing work that other teammates have already done or switching to old branches. However, to get the full benefits of caching, you have to go beyond simply setting it up.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;This is the story about how we solved some cache misses with our remote build cache and, in turn, saved our developers’ time. Maybe some of these tips can help you and your team save some time as well. This is part one of two. More cache misses and results will come in part two.&lt;/p&gt;
&lt;h2&gt;Tasks and Caching&lt;/h2&gt;
&lt;p&gt;A task is an amount of work with inputs and outputs, and the remote build cache works by storing the outputs of Gradle tasks. If all the inputs are the same, we can skip the work and just reuse the outputs.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8f009accc900f96648f1252ab248035a/640e4/image2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.427732079905994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABXklEQVQoz2NgYGDgAWI2IBYFYnMg1gNiQQYCgJmdiwtIsUMxK7KcIhBrA7E1EIeBaEYWNnGQRNv//2yu878GO877EeQ8/3uQ87xvIZ7L/vtoRPeA5N0YGJm8gbQPEMtAzRJBNpgDiLmBWIWZnVsTSOso+pT5ey79999/xb//Psv+/PdZ8f+/+8IfP226ruqDfMXCJSAPpI2gvgKJOYMMYsLhK2kxh8ywoBV/fk88/ut//d6ffxv2//kfuPzXRxmvsmCgvC4rt6AM1Lvs6Jo5gZgXiIWBWIOZjQsUBKoyjqkRHot+/PZZ/OMPEP/yXfLjH5D/XjNhihPIm8zsPCCfaED1icPCUgeI1YHYHog9QM5nZGKWAkn8//+fwXX+dwuHud/NQdh+7ncLl/nfTI/8/88MkgeGNT/UIUpA7A4NNrDrGKCxbQvEZlBFYD24YpmRmVUASAUAsRc0YkCphAEAOJRdBVsH8ekAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Gradle Task Diagram&quot;
        title=&quot;Gradle Task Diagram&quot;
        src=&quot;/blog/static/8f009accc900f96648f1252ab248035a/8ff1e/image2.png&quot;
        srcset=&quot;/blog/static/8f009accc900f96648f1252ab248035a/9ec3c/image2.png 200w,
/blog/static/8f009accc900f96648f1252ab248035a/c7805/image2.png 400w,
/blog/static/8f009accc900f96648f1252ab248035a/8ff1e/image2.png 800w,
/blog/static/8f009accc900f96648f1252ab248035a/640e4/image2.png 851w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For example, in the &lt;a href=&quot;https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;JavaCompile&lt;/code&gt;&lt;/a&gt; task, the inputs are Java files such as &lt;code class=&quot;language-text&quot;&gt;Audio.java&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Waveform.java&lt;/code&gt;, and the outputs of the compile task are the compiled class files such as &lt;code class=&quot;language-text&quot;&gt;Audio.class&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Waveform.class&lt;/code&gt;. If we haven’t added any new Java files or changed any of the contents of the Java files, we can reuse the output of the previous &lt;code class=&quot;language-text&quot;&gt;JavaCompile&lt;/code&gt; task.&lt;/p&gt;
&lt;p&gt;To determine that none of the inputs have changed, Gradle computes a build cache key, which uniquely identifies a task. It is computed based on the list of inputs and outputs for that task. Gradle then packs and stores the outputs of cacheable tasks in the Gradle build cache. A cache is simply a store of the build cache keys and the outputs. The cache can be local (file system) or remote (http).&lt;/p&gt;
&lt;h2&gt;Comparing Builds&lt;/h2&gt;
&lt;p&gt;A cache miss occurs when Gradle can’t find a corresponding entry for a task that should have been cacheable. To identify build cache misses, we use Gradle Enterprise build scans. Gradle Enterprise identifies cache misses by recording the inputs to a Gradle task, which can then be used &lt;a href=&quot;https://docs.gradle.com/enterprise/tutorials/task-inputs-comparison/&quot;&gt;to compare two builds&lt;/a&gt;. Here is an example of the same exact build on the same computer but with a whitespace added to a specific file:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/a6404/image3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.412060301507537%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAtElEQVQY01WPOw4CMQxE9/5n4gg01NQI0JJN4vyVz2B7RUGkJ0v2ZOzZWiM831c48jg8wTiHUgpqrcqcE/LWWn/03lX300qV3sYzlDox+GOJEWQtcs5orWGMoYY/xEgrzqf9dc5EK/NNXHmkAve64XG/wLqAwOZiLKSUEVOC916hEPmIqkuFOeXqc8FWa+O4AZ6hyLHdB8dhFTERs1I4EusiL0ncCyHAcpJ932GMARFp7N4HvldXNv6ee803AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;BuildConfig comparison&quot;
        title=&quot;BuildConfig comparison&quot;
        src=&quot;/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/8ff1e/image3.png&quot;
        srcset=&quot;/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/9ec3c/image3.png 200w,
/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/c7805/image3.png 400w,
/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/8ff1e/image3.png 800w,
/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/6ff5e/image3.png 1200w,
/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/2f950/image3.png 1600w,
/blog/static/d0fe34af665f765511fb9fe8dc7a49cf/a6404/image3.png 1990w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;From this, we can gather that whitespace in Java source files is treated as part of the input’s cache key. Gradle must re-execute this task if any of the whitespace in any of the inputs changes, which is unnecessary.&lt;/p&gt;
&lt;h2&gt;BuildConfig Misses&lt;/h2&gt;
&lt;p&gt;We can also use Gradle Enterprise to compare the same task running locally vs. in our build seed cache. In order for the remote cache to work, the inputs to the tasks between the local machine and the remote machine must match.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/298e897fb464f06b1d46b431500d0140/a987b/image5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21.770833333333332%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAoUlEQVQY01VPCQ7DMAjr/19atVnuNHc9HGnVimSBwBiz3fcNHyJ6H8g5w4WAlNKqe+/g/BdjjBf+Z4w5J7ba2hIjrivjPE8cxyFZQWsDYy2M8wKHIMesZCew0vfey84l4nMdoMbWRPBQHyhZJklrvYhcpNPWOng0xoh936GUgjFm8ShWSn0cP4IhJsR0yZtFCOVxTFCQrxOc19pejtgn54cv/783SeEU2eAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Gradle Task Diagram&quot;
        title=&quot;Gradle Task Diagram&quot;
        src=&quot;/blog/static/298e897fb464f06b1d46b431500d0140/8ff1e/image5.png&quot;
        srcset=&quot;/blog/static/298e897fb464f06b1d46b431500d0140/9ec3c/image5.png 200w,
/blog/static/298e897fb464f06b1d46b431500d0140/c7805/image5.png 400w,
/blog/static/298e897fb464f06b1d46b431500d0140/8ff1e/image5.png 800w,
/blog/static/298e897fb464f06b1d46b431500d0140/6ff5e/image5.png 1200w,
/blog/static/298e897fb464f06b1d46b431500d0140/2f950/image5.png 1600w,
/blog/static/298e897fb464f06b1d46b431500d0140/a987b/image5.png 1920w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;BuildConfig&lt;/code&gt; is a file in Android builds used to keep environment and configuration constants. Here is a sample &lt;code class=&quot;language-text&quot;&gt;BuildConfig&lt;/code&gt; class, and as you can see, all the values are constants:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public final class BuildConfig {
    public static final boolean DEBUG = Boolean.parseBoolean(&amp;quot;true&amp;quot;);
    public static final String APPLICATION_ID = &amp;quot;com.soundcloud.android&amp;quot;;
    public static final String BUILD_TYPE = &amp;quot;debug&amp;quot;;
    public static final int VERSION_CODE = -1;
    public static final int TEST_RETRY_COUNT = 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, the constants can be set by functions, which may be different depending on the environment:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;afterEvaluate {
  android.libraryVariants.all { variant -&amp;gt;
    variant.resValue &amp;quot;bool&amp;quot;, &amp;quot;analytics_enabled&amp;quot;, &amp;quot;true&amp;quot;
    variant.resValue &amp;quot;bool&amp;quot;, &amp;quot;verbose_logging&amp;quot;, &amp;quot;false&amp;quot;
    variant.buildConfigField &amp;quot;int&amp;quot;, &amp;quot;TEST_RETRY_COUNT&amp;quot;, &amp;quot;${getRetryCount()}&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our case, the &lt;code class=&quot;language-text&quot;&gt;TEST_RETRY_COUNT&lt;/code&gt; class was being set by a function, &lt;code class=&quot;language-text&quot;&gt;getRetryCount()&lt;/code&gt;. This function was very simple, but it led to different values locally vs. on the CI server:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private static int getRetryCount() {
  if (isCI) {
    return 1
  } else {
    return 0
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since the value was always &lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt; locally and &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; on the CI server, we could not reuse the outputs of this task with CI for local builds. Unfortunately, this was in one of the largest modules in our codebase, which meant a large portion of our code would have had to be recompiled, even if it didn’t need to be.&lt;/p&gt;
&lt;p&gt;Our solution was to remove this check from our build configuration and instead check it at runtime. For this case, we could determine at runtime if we were running on the CI server or not:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class ActivityTest&amp;lt;T : Activity&amp;gt; constructor(activityClass: Class&amp;lt;T&amp;gt;) {

  val isRunningOnTestLab = Settings.System.getString(contentResolver, &amp;quot;firebase.test.lab&amp;quot;) == &amp;quot;true&amp;quot;

  @JvmField
  val retryRule = RetryRule(if (isRunningOnTestLab) 1 else 0)
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Kotlin Compiler Flags&lt;/h2&gt;
&lt;p&gt;We also noticed that all of our Kotlin compile tasks were not pulling anything from the cache. Since we use Kotlin in nearly every module, this meant we were pulling almost nothing from the Gradle cache.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7d8143c091a260f7e2d789924aa10c89/774fc/image1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.728813559322035%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABoklEQVQoz2VSi3KbMBD0//9ex5mmcWK3NrYAPUBvBHh7J+y2STWz6HGn1e2xu5Qycs4IMcGHCOfDHwTaT1P5gukLtrNSCu73O3ajddBmwOUqcDo3aG4CvVTopITSBoWTn5czrwtK/h+ZYutKhJkSFRFyddZ7jCEjljvCtMLnBS7NsHFGoHWcC+xEKqb0CZ6wrMtWYSSpV9FRRaqWfREa348C71eNl8MZ+7czvr3+xOtHgx+/TtgfD3g5HbD/eKs4igYX3SOmtBFyD5iwES16pTEOBkZrWGuhlawwWpHcRGsFcbtBST7XFYbasswLlmUhySt2pcxoKaHtZZUeY6iX6TnQ5+9MI9MP9M5jJiV1cHjdYkxWCSMlMRH3L1DZI/VLuQJlJ8gxQ9LcDVQdzZZ6paKrkMF+QpnnTTKz3roeHZXP8ltpcLy0aKRFbxzBVwwu02MBvTX0yIB+NHXN0P8SLkQoSK54SHZ2hB036emBnGKVFUPAyI4g2YlcURG3GJNVyYlMzT5kWEq05EtPps4P03KPn7Oj+DCM1fC8Z1c84wz24W/SPgIQHrD1FQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;KotlinCompile comparison&quot;
        title=&quot;KotlinCompile comparison&quot;
        src=&quot;/blog/static/7d8143c091a260f7e2d789924aa10c89/8ff1e/image1.png&quot;
        srcset=&quot;/blog/static/7d8143c091a260f7e2d789924aa10c89/9ec3c/image1.png 200w,
/blog/static/7d8143c091a260f7e2d789924aa10c89/c7805/image1.png 400w,
/blog/static/7d8143c091a260f7e2d789924aa10c89/8ff1e/image1.png 800w,
/blog/static/7d8143c091a260f7e2d789924aa10c89/6ff5e/image1.png 1200w,
/blog/static/7d8143c091a260f7e2d789924aa10c89/774fc/image1.png 1416w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We looked at the &lt;a href=&quot;https://github.com/JetBrains/kotlin/blob/deb416484c5128a6f4bc76c39a3d9878b38cec8c/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt#L387&quot;&gt;source of &lt;code class=&quot;language-text&quot;&gt;KotlinCompile&lt;/code&gt;&lt;/a&gt; to understand what &lt;code class=&quot;language-text&quot;&gt;incremental&lt;/code&gt; (shown above) was and what it did. &lt;code class=&quot;language-text&quot;&gt;incremental&lt;/code&gt; is &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; by default, but it took us a while to realize that we had disabled incremental compilation for all our CI tasks.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a78b43f8beffe810a9bf35527f8eb21d/1002f/image4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.52080123266563%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABVklEQVQoz4WSWXLDIBBEdf8rZnFsa0MLMjsCSZ0WdvKVqlC86gGaERqozEPDWQcXIrSxUNo8Ufo3PueXh4Kh72d8EuKKNWWSXppRTdcbZD9AWY+269G0XdG6aSGGsei9bvBFX9cL6h01PafPOo+Ut5IovaguywGhE7pZQdqESQfMdkUvebqwwW+AI0UzdX/GXALtsOsBHfbi1aRqZITUEZOYsUiNx8NiZjyOEkZ7eBvgqNYE2FO5rpTjHg9JnZWFjRnsCPlAhWPDkROSEEjThMhfCfcaeRwR+h7L7Q7LEmSlCmkYYOiVy8KPjhCM1zWitIMJmRj7viOyXieeNVulhGfdLGvWcsMwS/zXjpdWkReS5gX+4wJ/uT7hBZzq3j/RvL2juXxhnWbsxmHnRfywvShjHwpVaLpiDm2PM3kUPGXdwt9qeM5JIlgCw3Hm08l8Llnbp/7BNxT9ZW4VkUlzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;gradle.properties&quot;
        title=&quot;gradle.properties&quot;
        src=&quot;/blog/static/a78b43f8beffe810a9bf35527f8eb21d/8ff1e/image4.png&quot;
        srcset=&quot;/blog/static/a78b43f8beffe810a9bf35527f8eb21d/9ec3c/image4.png 200w,
/blog/static/a78b43f8beffe810a9bf35527f8eb21d/c7805/image4.png 400w,
/blog/static/a78b43f8beffe810a9bf35527f8eb21d/8ff1e/image4.png 800w,
/blog/static/a78b43f8beffe810a9bf35527f8eb21d/6ff5e/image4.png 1200w,
/blog/static/a78b43f8beffe810a9bf35527f8eb21d/1002f/image4.png 1298w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We turned incremental compilation back on for our CI nodes that seed our build cache and immediately saw a big boost in cache hits. More cache hits meant less time spent building something that was already built.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In the end, we learned that simply turning on a remote build cache is not very effective in improving build speeds. In fact, blindly turning it on might not get any cache hits at all. It is important to keep in mind how tasks work, how they behave on different systems, and how to use the right tools to analyze differences in task inputs across different systems. That’s how we solved some cache misses with our remote build cache and, in turn, saved our developers’ time. Maybe some of these tips can help you and your team save some time as well.&lt;/p&gt;
&lt;p&gt;In part two, we’ll dive into more complicated cache misses and talk more about the impact of solving them.&lt;/p&gt;
&lt;p&gt;In the meantime, if you’d like to learn more, check out “Remote Build Cache Misses” from &lt;a href=&quot;https://www.meetup.com/berlindroid/events/259762503/&quot;&gt;BerlinDroid Gradle Night&lt;/a&gt;. You can &lt;a href=&quot;https://youtu.be/2frfDMJwvf4?t=384&quot;&gt;watch the talk&lt;/a&gt; or &lt;a href=&quot;https://speakerdeck.com/runningcode/remote-build-cache-misses-and-how-to-solve-them-by-annoying-your-colleagues&quot;&gt;browse the slides&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Is Playing the Oboe]]></title><description><![CDATA[Media and playback are at the core of SoundCloud’s experience. For that reason, we have established and grown an engineering team that is specialized in providing the best possible streaming experience to our users across multiple platforms. To do this, we combine the industry’s best-fitting solutions with our own custom technologies, libraries, and tools. In this article, let’s dive into how we improved latency in our Android application by leveraging a new engine for our player’s audio sink.]]></description><link>https://developers.soundcloud.com/blog/soundcloud-is-playing-the-oboe</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-is-playing-the-oboe</guid><pubDate>Fri, 21 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Media and playback are at the core of SoundCloud’s experience. For that reason, we have established and grown an engineering team that is specialized in providing the best possible streaming experience to our users across multiple platforms.&lt;/p&gt;
&lt;p&gt;To do this, we combine the industry’s best-fitting solutions with our own custom technologies, libraries, and tools. In this article, let’s dive into how we improved latency in our Android application by leveraging a new engine for our player’s audio sink.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/playback-on-web-at-soundcloud&quot;&gt;Maestro powers our web applications&lt;/a&gt;, but for the ones that are compiled with native code, we created Flipper, a lightweight cross-platform audio streaming engine written in C++. Flipper drives tens of millions of plays per day in our Android and iOS mobile apps, and it is designed with SoundCloud’s use cases in mind, which provides the flexibility we need for innovation and fast delivery.&lt;/p&gt;
&lt;h2&gt;Flipper&lt;/h2&gt;
&lt;p&gt;As is the case with many other media players, Flipper is designed as a pipeline of components, each of which is responsible for one step of the streaming technology. Having multiple discrete steps — or &lt;em&gt;stages&lt;/em&gt; — allows for parallel processing of a continuously flowing stream of data. Think of it like a car manufacturing plant: While one group of workers is focused on constructing the engine of the car, someone else is assembling the tires.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/bd853f55bc860bb07babfcec150f122d/4d248/player_arch.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.9183922046285%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABN0lEQVQY02O4detW48OHD588evTInwEJPHjwgPno0aNMyGIXL15kvXbtGiMDJkCIPX78OOLp06dbgAodT506pfb////QDx8+OMDkgWynb9++BQPVKSEZwPT27Vs7oNrg06dPG+7cucsdSGt9/PhRFMUaoEvnfv78+f+rV69uAF2XDbSg4uXLl3eBGkFic86cOdOzadOmSKBSDqD4LZD4ixcvZp49e3bK/fv3fz158uQFw/r165lWr17NCzIQKDgNaMv/169fXzhx4kQk0IvpQA1Xf/z48R+oeMrJkyfLgGp9gErZgWou/v379z/QB727du2yBvpwHTD46hju3bvnBXTZya1bt4oBbZIFWmoHVGSA5GXD79+/2z579kwCOczevHmj++fPHyegpbzA4Mj48uXLf6CrdwEAXkbZ4AF9+WkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Generic media streaming pipeline&quot;
        title=&quot;Generic media streaming pipeline&quot;
        src=&quot;/blog/static/bd853f55bc860bb07babfcec150f122d/8ff1e/player_arch.png&quot;
        srcset=&quot;/blog/static/bd853f55bc860bb07babfcec150f122d/9ec3c/player_arch.png 200w,
/blog/static/bd853f55bc860bb07babfcec150f122d/c7805/player_arch.png 400w,
/blog/static/bd853f55bc860bb07babfcec150f122d/8ff1e/player_arch.png 800w,
/blog/static/bd853f55bc860bb07babfcec150f122d/4d248/player_arch.png 821w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For the scope of this article, let’s focus on one single component: the &lt;strong&gt;sink&lt;/strong&gt;. This is the last stage in the pipeline, and the sink’s responsibility is to offload the decoded audio samples to the underlying hardware abstraction layer (HAL) of the operating system in which the application is running. It is critical for this to &lt;a href=&quot;https://medium.com/@juliozynger/your-app-and-low-latency-audio-output-d21d7b672305&quot;&gt;happen in a timely manner&lt;/a&gt;, so as to avoid unwanted audio artifacts (crackling or popping).&lt;/p&gt;
&lt;p&gt;Having the sink as a component abstraction is very useful when building Flipper, since it enables portability of the code. As a result, we can unify the exposed API to the rest of the stages in the pipeline, regardless of the specific platform it runs on. On iOS, these specificities are in &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/WhatisCoreAudio/WhatisCoreAudio.html&quot;&gt;Core Audio’s APIs&lt;/a&gt;, while on Android, the samples are processed with other bindings.&lt;/p&gt;
&lt;h2&gt;Android Audio APIs&lt;/h2&gt;
&lt;p&gt;The Android OS has evolved over the years, and along with it, so has its audio functionality. As a result, there are now a few sets of audio APIs. In Android Gingerbread, the Open Sound Library for Embedded Systems (&lt;a href=&quot;https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android&quot;&gt;OpenSL ES&lt;/a&gt;), a C library built with portability (and mobile) in mind, graduated to be the recommended interface to the audio hardware. Both the application-level player (MediaPlayer) and third-party developers have access to OpenSL ES APIs via the &lt;a href=&quot;https://developer.android.com/ndk&quot;&gt;Android NDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even though OpenSL was able to support Android’s use cases for a considerable amount of time, it started showing signs of being outdated as applications evolved to require higher performance. Beginning with Android Oreo, however, Google integrated a new C library with a simpler API surface to target these low-latency scenarios: &lt;a href=&quot;https://developer.android.com/ndk/guides/audio/aaudio/aaudio&quot;&gt;AAudio&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since then, Google has been advising developers to use AAudio for building audio applications, but the API availability becomes an issue when targeting a large audience of users, as fragmentation becomes more evident. Developers still have to maintain code for OpenSL ES bindings if they want to provide audio capabilities on older versions of the operating system. On top of that, manufacturers also apply changes to the behavior of the APIs, deviating from the official documentation Google provides and forcing all sorts of workarounds and device-specific hacks.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href=&quot;https://github.com/google/oboe&quot;&gt;Oboe&lt;/a&gt;, a C++ wrapper built on top of OpenSL ES and AAudio, that exposes an “AAudio-like” interface, includes fixes (or workarounds) for known manufacturer-specific issues, and automatically picks the most suitable audio API based on a device’s capabilities. If you’re already familiar with Android development, this is the same idea behind the &lt;a href=&quot;https://developer.android.com/topic/libraries/support-library&quot;&gt;Support Libraries&lt;/a&gt;, in that it provides a compatibility layer between the application and platform code.&lt;/p&gt;
&lt;h2&gt;SoundCloud and Oboe&lt;/h2&gt;
&lt;p&gt;The cost of maintaining two implementations for our Flipper sink on Android and the risk of replacing a critical part of our already battle-tested core library for playback were among the reasons we postponed the integration of our player with AAudio, but the release, and later, &lt;a href=&quot;https://android-developers.googleblog.com/2018/10/introducing-oboe-c-library-for-low.html&quot;&gt;stable graduation&lt;/a&gt;, of Oboe motivated the work on a prototype integration.&lt;/p&gt;
&lt;p&gt;Following our &lt;a href=&quot;https://developers.soundcloud.com/blog/quality-mobile-trains&quot;&gt;usual rollout process&lt;/a&gt;, we started by deploying Oboe to a controlled subgroup of our userbase by using a remote feature toggle, and as we got more confident with the performance in production, we extended its availability.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 668px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c6d1a929204d97718690da3a644d8452/cc651/oboe_branching.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.07185628742515%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAACIElEQVQoz52P3WtSARiHj5oIW2yUJESji1G2Pqig/oJ1UzGshjFowrxwTT2sZhflZdC6CulSChr0YUsztePONuZQp/Pz+HFOetR0HufKoJIyN+bnetNiF1Hd9Lt93+f5vS+C/CU220Kf0754Q+QC3trUZUnxxSiUDFfhwzPJlfY8b77JuXN7EtE+0rL/gCGo6GhgcnMDQ5caVhRvaDp2ewlqIBwKJmYysC/3cPD6V4MS1o0obJhkyjbzsR9hbfOZCr0nvR4/kSxRxyKffD2IXt3fUzKMVZMPpFB+qWgmtBdO/lboRI8ALr/XtI5pYF52WAfw8yqapifjsfiTBWLOEs6FwJvywCxpAqTwdIhftSg2v+jlULMoa++M6OkwzZwrrDFi1Wyzs6wfPt96N7ppVlAbuotn3FnCFMr5ngd8ftEbKoYSjP+aL+OxBRm/xUZbHyPgGOFuzYyKwT4h+W6WiIoxXOgPBEmP25XHqeLRb7ohVdM6AYCroGocHp8jl3LumCOP/G8Alx2vv5ZrapjiPry6dIgsJtH458hINBJVUyS1GM4Rdz0pd9D31uNyZuZxBMyDnQ3dgPjUAb6gd2+3gLcD2dXydLVl0v3/Lsoy2bOpREoaXiWmg9kALNOurZawgjBZTECR01M8HlfY2utjs1lCLpe78xfWjTQxJauB3WI3MTWnbpayUuUCm0xHOdti73tH12o9fXClkuhdXrHzfwCY/CTVrriWWAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Flipper sink branching&quot;
        title=&quot;Flipper sink branching&quot;
        src=&quot;/blog/static/c6d1a929204d97718690da3a644d8452/cc651/oboe_branching.png&quot;
        srcset=&quot;/blog/static/c6d1a929204d97718690da3a644d8452/9ec3c/oboe_branching.png 200w,
/blog/static/c6d1a929204d97718690da3a644d8452/c7805/oboe_branching.png 400w,
/blog/static/c6d1a929204d97718690da3a644d8452/cc651/oboe_branching.png 668w&quot;
        sizes=&quot;(max-width: 668px) 100vw, 668px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It’s been a few months since we began measuring Oboe’s integration with Flipper in production, and while it is still a rather new tool, it has proven to be beneficial to us in many ways.&lt;/p&gt;
&lt;p&gt;For one, the global community united around improving Oboe introduces new concepts and capabilities faster than what we could do internally. It also provides us with access to the experts behind the platform APIs, along with the possibility to contribute back to the upstream repository, due to its open-source nature. Finally, the combination of Oboe and AAudio gives us richer insights into the system’s HAL and its performance — for example, the number of &lt;a href=&quot;https://en.wikipedia.org/wiki/Buffer_underrun&quot;&gt;buffer underruns&lt;/a&gt;, which allows for additional optimizations.&lt;/p&gt;
&lt;p&gt;Most importantly, our users also gained: We have been able to provide a better experience for playback, latency-wise, as our telemetry systems show that AAudio contributed to an approximate 10 percent decrease in overall latency in comparison to the same sampling size with OpenSL ES.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/63d2a157e4f4f9948d127e5eaa684460/79d86/latency_improvement.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.00976800976801%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAClElEQVQ4y11TW2sTQRjdnyBNNpts2yR7mdnrbGYvybb2Yh8qlj6IFquCVpC2glpsQbFURMSHKvgmCv7b4zezSVr7cDhnznxz8mVmP8PsdNGxelA8023TwtLyAJ7P0R84GDqehuP62uvZS/Pam2eNmXkdKnAwdDGe1EhFhqKsiEcQmUQ1rvWeqpmFXT9rqA0Fq9sj2DCtLqyerQtvLbTQMjtYaJkarXajm3r7ql7raWAYxvBZANOktk3qrmVp1mhTsWWj0+np/Q55at1ud+c1V7pL2oIx+6WBPwQTDG7sIpAcYRFoVl4yjpCuxIjIi8qQPB9BHiCtY8gNoTkkX9UYrssQRBFG6xnKLYnRWoZ8U0ISJ3UKuZoirtQBQpVQeIKIOF8T2vOEChKo79C52ykMe3GZ7qmLiDrr9YdwvAES0hu1j2LkYFx4cH0HZe6iIr2/E2B94qGuPGxvMDza4Sili3ub9E8SBwYPEsgswMVxiuMnCc6PUnw/G+HPJ4kfZxl+vpf48lrg8l2GX+cSl6cjfDvJ8PVtpvnvZ4nfF41/+oI6XKBLDfwlfDwS+HAo8HI/whEFK7x5luDkIMGrpzGeP4ywtxtia4NjdzvA0/sR7m5Rt6sMD3ZCHD6OcbAX0R16jF6Zo6oYgSPPfeQFR0EoS0bfIHkFI5/RmmMymfq0P6b6qmz8gmok1dArL6Lf72NlwrBScwLD6pSbNUc93dOsdYP/1lM2HMeH63mQI+pMNhertFQ6HqDIVRchREoPQ59NWQZzrX1x5WfCpUBPBTJ65RTqI7dtG3EsEEUJlunVOY9ojn2akM58ptXEDIbNbDfa1VpNkOFzjjgROpDxAL431OsgjJCkggJcPW6zGZ/NsGIVdtP/B9GRoZsSecQVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Latency improvement graph&quot;
        title=&quot;Latency improvement graph&quot;
        src=&quot;/blog/static/63d2a157e4f4f9948d127e5eaa684460/8ff1e/latency_improvement.png&quot;
        srcset=&quot;/blog/static/63d2a157e4f4f9948d127e5eaa684460/9ec3c/latency_improvement.png 200w,
/blog/static/63d2a157e4f4f9948d127e5eaa684460/c7805/latency_improvement.png 400w,
/blog/static/63d2a157e4f4f9948d127e5eaa684460/8ff1e/latency_improvement.png 800w,
/blog/static/63d2a157e4f4f9948d127e5eaa684460/79d86/latency_improvement.png 819w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;All in all, we are happy with the results of what started as a prototype implementation, and we’re aiming to make the new audio sink available to our entire userbase in the near future, while still keeping an eye open for further improvements to latency and the playback experience in general.&lt;/p&gt;
&lt;p&gt;If you’re interested in media streaming, Android, large-scale challenges, and what we do at SoundCloud, &lt;a href=&quot;https://jobs.soundcloud.com/&quot;&gt;make sure to check out our jobs page&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Alerting on SLOs like Pros]]></title><description><![CDATA[If there is anything like a silver bullet for creating meaningful and actionable alerts with a high signal-to-noise ratio, it is alerting based on service-level objectives (SLOs). Fulfilling a well-defined SLO is the very definition of meeting your users’ expectations. Conversely, a certain level of service errors is OK as long as you stay within the SLO — in other words, if the SLO grants you an error budget. Burning through this error budget too quickly is the ultimate signal that some rectifying action is needed. The faster the budget is burned, the more urgent it is that engineers get involved. This post describes how we implemented this concept at SoundCloud, enabling us to fulfill our SLOs without flooding our engineers on call with an unsustainable amount of pages.]]></description><link>https://developers.soundcloud.com/blog/alerting-on-slos</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/alerting-on-slos</guid><pubDate>Tue, 04 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If there is anything like a silver bullet for creating meaningful and actionable alerts with a high signal-to-noise ratio, it is alerting based on service-level objectives (SLOs). Fulfilling a well-defined SLO is the very definition of meeting your users’ expectations. Conversely, a certain level of service errors is OK as long as you stay within the SLO — in other words, if the SLO grants you an &lt;em&gt;error budget&lt;/em&gt;. Burning through this error budget too quickly is the ultimate signal that some rectifying action is needed. The faster the budget is burned, the more urgent it is that engineers get involved.&lt;/p&gt;
&lt;p&gt;This post describes how we implemented this concept at SoundCloud, enabling us to fulfill our SLOs without flooding our engineers on call with an unsustainable amount of pages.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Let’s Talk SRE&lt;/h2&gt;
&lt;p&gt;SLOs, alerting, and error budgets are central concepts of &lt;a href=&quot;https://landing.google.com/sre/&quot;&gt;Site Reliability Engineering (SRE)&lt;/a&gt;, a discipline invented by Google in the early 2000s. Although it was initially proprietary to Google, over the last few years, SRE has become a widely discussed topic and is now applied in various flavors throughout the industry. An important milestone in this development was Google’s publication of the bestselling book &lt;a href=&quot;https://landing.google.com/sre/sre-book/toc/index.html&quot;&gt;&lt;em&gt;Site Reliability Engineering: How Google Runs Production Systems&lt;/em&gt;&lt;/a&gt;. The book is available online for free. If you run anything even remotely at scale, there is really no excuse as to why you shouldn’t read this book in its entirety. Among all of its important chapters, there are three of particular importance for this blog post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/service-level-objectives&quot;&gt;Chapter 4 — Service Level Objectives&lt;/a&gt; explains everything you need to know about SLOs (and SLIs and SLAs, in case you’ve ever wondered about the difference).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems&quot;&gt;Chapter 6 — Monitoring Distributed Systems&lt;/a&gt; lays the foundation of “alerting philosophy” and creates an understanding of when it makes sense to page engineers (and when it does not).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/practical-alerting&quot;&gt;Chapter 10 — Practical Alerting&lt;/a&gt; mostly describes Borgmon, Google’s traditional internal monitoring system. While Borgmon is proprietary to Google, the author points out that you can try out the described concepts at home (or at work, even) because many current open-source monitoring systems follow similar semantics, with &lt;a href=&quot;https://prometheus.io&quot;&gt;Prometheus&lt;/a&gt; receiving a special shoutout. Conveniently, we use Prometheus for the large majority of our monitoring (which is not surprising, as most of Prometheus’ &lt;a href=&quot;https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud&quot;&gt;initial development took place here at SoundCloud&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep in mind, though, that &lt;a href=&quot;https://blog.bradfieldcs.com/you-are-not-google-84912cf44afb&quot;&gt;you are not Google&lt;/a&gt;. We all can learn from the giants, but blindly copying them could go horribly wrong. On that note, how different organizations ventured into SRE territory is the main topic of yet another SRE-themed book: &lt;a href=&quot;https://www.oreilly.com/library/view/seeking-sre/9781491978856/&quot;&gt;&lt;em&gt;Seeking SRE&lt;/em&gt;&lt;/a&gt;, which was curated and edited by David Blank-Edelman. We at SoundCloud contributed a chapter, too, in which we talked about our somewhat bumpy road to applying SRE principles in an organization that is totally not Google.&lt;/p&gt;
&lt;p&gt;The Google SREs themselves also felt the pain of us “mere mortals” trying to figure out how to make use of the lessons from the SRE book in our day-to-day work, so they published a companion to the original SRE book, called [&lt;em&gt;The Site Reliability Workbook&lt;/em&gt;], which they again made available online for free. There are a bunch of cookbook-style chapters that are directly applicable in practice. One of them, &lt;a href=&quot;https://landing.google.com/sre/workbook/chapters/alerting-on-slos/&quot;&gt;Chapter 5 — Alerting on SLOs&lt;/a&gt;, describes perfectly what we had been doing already, albeit in a less sophisticated way. With the inspiration from that chapter, we were able to refine our SLO-based alerting with great success. In fact, the solution we ended up using is so similar to the one described in the chapter that you should just read it together with this blog post to get the most out of it.&lt;/p&gt;
&lt;h2&gt;Symptoms vs. Causes&lt;/h2&gt;
&lt;p&gt;But let’s first take a step back and think about alerting on symptoms vs. causes. This is an important topic in &lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems&quot;&gt;Chapter 6&lt;/a&gt; from the original SRE book (where you can read up all the details if you feel so inclined).&lt;/p&gt;
&lt;p&gt;Back in the days when your entire site ran on one LAMP server, there wasn’t much of a need to think about the difference between symptoms and causes. If your one web server went down (cause), your site was down (symptom). Waking somebody up the moment your web server stopped replying to ping probes was the right thing to do.&lt;/p&gt;
&lt;p&gt;Nowadays, even mid-size sites run on hundreds, if not thousands of servers, and one or two of them are almost guaranteed to be down at any time. And that’s fine, because the software is designed to tolerate some failures. As such, it wouldn’t make any sense to wake somebody up whenever a single machine goes down.&lt;/p&gt;
&lt;p&gt;You can apply the same thought to many other — actual or potential — &lt;em&gt;causes&lt;/em&gt; of an outage. Paging alerts, those that wake you up in the night, should be based on &lt;em&gt;symptoms&lt;/em&gt;, i.e. something that actively or imminently impacts the user experience. In addition, the alerting condition should also be urgent and actionable. If any of these requirements are not met, the alert should not be a page.&lt;/p&gt;
&lt;p&gt;However, there could still be a meaningful alert on a non-paging level. Let’s call these kinds of alerts &lt;em&gt;tickets&lt;/em&gt;, as somebody will have to look at them eventually, e.g. a dead server has to be dealt with at a suitable time. A user-visible but not urgent problem can easily be addressed during work hours rather than in the middle of the night, but even during an actual outage, it is helpful to get informed (via some kind of dashboard rather than via your pager) about possible causes of an outage that are detected by the monitoring system.&lt;/p&gt;
&lt;p&gt;Ideally, your SLO is a precise definition of an unimpacted user experience. In other words, symptom-based alerts are usually alerts on (active or imminent) violations of your SLO.&lt;/p&gt;
&lt;h2&gt;Black-Box vs. White-Box Monitoring&lt;/h2&gt;
&lt;p&gt;With the lessons of symptoms vs. causes in mind, you might think now that black-box monitoring is the obvious choice for detecting symptoms, while white-box monitoring is ideal for causes. That’s a reasonable thought, but in reality, our practice at SoundCloud is a perfect match of what Google tells us in &lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems&quot;&gt;Chapter 6&lt;/a&gt; of the SRE book: “We combine heavy use of white-box monitoring with modest but critical uses of black-box monitoring.”&lt;/p&gt;
&lt;p&gt;We mostly use black-box probes as a workaround for legacy software that isn’t yet instrumented for white-box monitoring. And we run fairly complex Catchpoint probes to simulate typical usage of our site. Interestingly, these probes are what we use to determine if we have hit our monthly, quarterly, and yearly availability goals, but we don’t use them to page anybody. Why is that so? The lazy answer is: We need white-box monitoring anyway (to investigate causes). Why should we invest in additional black-box monitoring except for in the most critical cases? But there are more reasons (quotes are again from the SRE book):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ideally, we want to detect user-impacting outages &lt;em&gt;before&lt;/em&gt; they do noticeable harm. However, “for not-yet-occurring but imminent problems, black-box monitoring is fairly useless.”&lt;/li&gt;
&lt;li&gt;Probes are &lt;em&gt;not&lt;/em&gt; real user traffic. For example, the test track that our Catchpoint probes attempt to play might be perfectly available, while, for some complex reasons, the rap battle that has just gone viral is not.&lt;/li&gt;
&lt;li&gt;Measuring long-tail latency and detecting low error rates take a lot of probes and thus a long time. However, tail latency is quite crucial in distributed systems, and our availability goals are usually measured in many nines, and so even a small error rate might violate our SLO. This could be the main reason why we don’t page anybody based on our Catchpoint probe. The probes are too slow to detect low but significant error rates, despite already limiting them to probing a very specific business KPI. The latter is a problem on its own, as we want our paging alerts to cover all relevant features of the site. However, the long-term results of the Catchpoint probes are great as a general quality calibration for our other monitoring. If they detect something, we can revisit historical alerting records to see if the near-real-time monitoring caught the issue, too.&lt;/li&gt;
&lt;li&gt;In a multilayered system, like a microservice architecture, “one person’s symptom is another person’s cause.” A frontend service sees a failing backend service as a cause, while for the backend service, its own failure is a symptom, as it is now violating the (internal service-to-service) SLO. Adopting this view, we can use the white-box monitoring of the instrumented frontend service to track requests issued to the backend service. This is essentially black-box probing of the backend with the complete and real user traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most rewarding place to apply the pattern described in the last point above is our HAProxy-based terminator layer at the edge of our infrastructure. An &lt;a href=&quot;https://developers.soundcloud.com/blog/how-soundcloud-uses-haproxy-with-kubernetes-for-user-facing-traffic&quot;&gt;earlier Backstage Blog post&lt;/a&gt; described it in detail.&lt;/p&gt;
&lt;p&gt;Recently, HAProxy announced the introduction of a native &lt;a href=&quot;https://www.haproxy.com/blog/haproxy-exposes-a-prometheus-metrics-endpoint/&quot;&gt;Prometheus metrics endpoint&lt;/a&gt;, but the versions we have been working with are not yet instrumented for Prometheus. This gap is bridged by the &lt;a href=&quot;https://github.com/prometheus/haproxy_exporter&quot;&gt;Prometheus HAProxy exporter&lt;/a&gt;, a small glue binary that can be scraped by the Prometheus server in the usual way, whereupon it talks to HAProxy in its own fashion to retrieve metrics and convert them to the Prometheus format and then sends them back as the scrape result. By running such an HAProxy exporter next to each HAProxy instance, we get all the metrics we need. In particular, there is a counter for all HTTP requests HAProxy has sent to the various backends, partitioned not only by the backend, but also by the HTTP status code. With Prometheus, we can easily calculate relative rates of 5xx responses over arbitrary time spans.&lt;/p&gt;
&lt;h2&gt;Multiwindow, Multi-Burn-Rate Alerts&lt;/h2&gt;
&lt;p&gt;With the error rate as seen by the HAProxy terminator layer, we can now apply the alerting technique described in &lt;a href=&quot;https://landing.google.com/sre/workbook/chapters/alerting-on-slos/&quot;&gt;Chapter 5&lt;/a&gt; of the SRE workbook. If you haven’t yet read that chapter, now is probably really a good time to do so. It progresses quite nicely, approaching the ultimate solution iteratively and solving one problem after another. Spoiler alert: Here, we will only implement the ultimate solution, the sixth iteration in the chapter, which is called &lt;em&gt;Multiwindow, Multi-Burn-Rate Alerts&lt;/em&gt;. This features a set of alerts, each of which has a different alerting threshold that corresponds to different rates of burning through the monthly error budget. Depending on the burn rate, each alert uses a combination of two different time windows over which the error rate is calculated. Fast burning is detected quickly. Slow burning needs a longer time window.&lt;/p&gt;
&lt;p&gt;First, we need to actually set the SLO. The chapter from the SRE workbook assumes an error rate of 0.1% (or a 99.9% success rate) as a typical value. That’s also generally true for SoundCloud, so the various numbers calculated in the chapter make sense for our scenario. However, some backends might have a stricter or more relaxed SLO. We still keep the various burn rates and window sizes the same for them, but we allow the target error rate to be configured by backend. This configuration takes the form of rules in PromQL (the Prometheus expression language, which is also used in the SRE workbook). They look like the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; slos_by_backend
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;error_slo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;percent
    &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;api-v4&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;error_slo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;percent
    &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;api-v3&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ... Many more backends.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we need the actual error rate averaged over various time windows: 5m, 30m, 1h, 2h, 6h, 1d, 3d. These are also calculated as recording rules, but they are now based on actual live data instead of a constant number. This is the rule for the 5m case:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; multiwindow_recording_rules
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;haproxy_backend_http_errors_per_response&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;ratio_rate5m
    &lt;span class=&quot;token key atrule&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;2
        sum by (backend)(rate(haproxy_backend_http_responses_total&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;job=&quot;haproxy&quot;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code=&quot;5xx&quot;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;5m&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;))
      /
        sum by (backend)(rate(haproxy_backend_http_responses_total&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;job=&quot;haproxy&quot;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;5m&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;))
  &lt;span class=&quot;token comment&quot;&gt;# Other rules accordingly with 5m replaced by 30m, 1h, 2h, 6h, 1d, 3d.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;rate&lt;/code&gt; with the &lt;code class=&quot;language-text&quot;&gt;[5m]&lt;/code&gt; range selector calculates requests per second, averaged over the specified timeframe, from the ever-increasing counters. The &lt;code class=&quot;language-text&quot;&gt;sum&lt;/code&gt; aggregator in both enumerator and denominator separately sums up the rate from all HAProxy instances for each &lt;code class=&quot;language-text&quot;&gt;backend&lt;/code&gt;. Each &lt;code class=&quot;language-text&quot;&gt;backend&lt;/code&gt; ends up as a label on the elements in the resulting vector. The denominator includes all requests, while the enumerator only selects those with &lt;code class=&quot;language-text&quot;&gt;code=&amp;quot;5xx&lt;/code&gt; — which results in an error rate.&lt;/p&gt;
&lt;p&gt;With all these recording rules in place, we can now assemble the multiwindow, multi-burn-rate alerts. We have chosen to use four different alerts, each combining a &lt;em&gt;long window&lt;/em&gt;, a &lt;em&gt;short window&lt;/em&gt;, and a &lt;em&gt;burn rate factor&lt;/em&gt;, as listed in the following table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alert&lt;/th&gt;
&lt;th&gt;Long Window&lt;/th&gt;
&lt;th&gt;Short Window&lt;/th&gt;
&lt;th&gt;&lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; Duration&lt;/th&gt;
&lt;th&gt;Burn Rate Factor&lt;/th&gt;
&lt;th&gt;Error Budget Consumed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Page&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;2m&lt;/td&gt;
&lt;td&gt;14.4&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;30m&lt;/td&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticket&lt;/td&gt;
&lt;td&gt;1d&lt;/td&gt;
&lt;td&gt;2h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticket&lt;/td&gt;
&lt;td&gt;3d&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each alert fires if the SLO error rate times the &lt;em&gt;burn rate factor&lt;/em&gt; is exceeded when averaged over both the &lt;em&gt;long window&lt;/em&gt; and the &lt;em&gt;short window&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; The SLO error rate for &lt;code class=&quot;language-text&quot;&gt;api-v4&lt;/code&gt; is 0.1% (to achieve “three 9s” of availability). The owners of &lt;code class=&quot;language-text&quot;&gt;api-v4&lt;/code&gt; get paged if their backend has returned more than 1.44% 5xx responses over the last 1h &lt;em&gt;and&lt;/em&gt; over the last 5m. They also get paged if their backend has returned more than 0.6% 5xx responses over the last 6d &lt;em&gt;and&lt;/em&gt; over the last 30m.&lt;/p&gt;
&lt;p&gt;The short window ensures a short reset time of the alert, i.e. an alert should stop firing soon after the problem has been solved (which is not only convenient but also allows for paging to happen again if the problem comes back).&lt;/p&gt;
&lt;p&gt;Why the different burn rate factors? They enable pages for fast burning of the monthly error budget and tickets for slow burning of the budget. (Slow burning of the error budget must not go undetected and must be addressed eventually, but it’s no reason to wake somebody up in the middle of the night.) The Error Budget Consumed column lists the percentage of the monthly error budget consumed at the time the alert triggers. The math is easiest to check in the last row, with a burn rate factor of 1: If the error rate happens to be exactly at the target of 0.1%, and that happens for 3 days, then 10% of the monthly error budget is burned because 3 days is 10% of a 30-day-long month.&lt;/p&gt;
&lt;p&gt;Note that an alert can fire much more quickly than within the length of the long window. A backend that suddenly starts to return 100% 5xx will cross the 1.44% hourly error rate after only 52s (1.44% of 3,600s).&lt;/p&gt;
&lt;p&gt;This leaves us with the &lt;em&gt;&lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; duration&lt;/em&gt; column, which is interestingly missing in the SRE workbook. We’ll talk about it in a minute. But let’s first see how the alerting rules actually appear:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; multiwindow_alerts
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ErrorBudgetBurn
    &lt;span class=&quot;token key atrule&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;2
        (
          100 * backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;haproxy_backend_http_errors_per_response&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;ratio_rate1h
        &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; on (backend)
          14.4 * backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;error_slo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;percent
        )
      and
        (
          100 * backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;haproxy_backend_http_errors_per_response&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;ratio_rate5m
        &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; on (backend)
          14.4 * backend&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;error_slo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;percent
        )
    &lt;span class=&quot;token key atrule&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2m
    &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{{$labels.backend}}&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;severity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;page&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;long_window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1h&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;a backend burns its error budget very fast&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Backend {{$labels.backend}} has returned {{ $value | printf `%.2f` }}% 5xx over the last hour.&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;runbook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://runbooks.soundcloud.com/terminator/#errorbudgetburn&quot;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Followed by the other three alerting rules.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the sake of brevity, this only lists the first of the four alerting rules. By replacing the relevant recording rules, the factors, the &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; duration, and the applicable label values with the values from the table above, you can easily deduce how the other three alerting rules appear.&lt;/p&gt;
&lt;p&gt;The SRE workbook also lists the alerting rules in PromQL, but in a more compact form. Our example here is more verbose because we have added a few things we needed for our particular use case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As explained above, the SLO error rate might be different per backend and the threshold is configured as a recording rule named &lt;code class=&quot;language-text&quot;&gt;backend:error_slo:percent&lt;/code&gt;. You can find it in the expression above. Note that the &lt;code class=&quot;language-text&quot;&gt;&amp;gt;&lt;/code&gt; comparison operator is used with a modifier, &lt;code class=&quot;language-text&quot;&gt;on (backend)&lt;/code&gt;, to tell Prometheus which label to use for the label matching.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;annotations&lt;/code&gt; which follow internal conventions, are added to facilitate handling the alerts. For example, each alert should contain a link to the relevant runbook in our runbook repository.&lt;/li&gt;
&lt;li&gt;We also add a &lt;code class=&quot;language-text&quot;&gt;system&lt;/code&gt; label for alert routing. The Prometheus Alertmanager allows us to configure alert routing based on labels in a very powerful way. Somewhere else in our codebase, we have a registry of which team owns which systems. From there, we autogenerate an Alertmanager config that routes alerts to their owners based on the &lt;code class=&quot;language-text&quot;&gt;system&lt;/code&gt; label. Everything that can be labeled (for example, pods on Kubernetes) also gets a &lt;code class=&quot;language-text&quot;&gt;system&lt;/code&gt; label, and that label is propagated into Prometheus. HAProxy only knows the backend, and thus the HAProxy exporter doesn’t expose a system label. However, by convention, we use the system name as the backend name, and thus we can simply set the &lt;code class=&quot;language-text&quot;&gt;system&lt;/code&gt; label to the value of the &lt;code class=&quot;language-text&quot;&gt;backend&lt;/code&gt; label. (In harsh reality, the &lt;code class=&quot;language-text&quot;&gt;backend&lt;/code&gt; label is a multi-component string with other components, too. Prometheus has you covered there as well, with the &lt;code class=&quot;language-text&quot;&gt;label_replace&lt;/code&gt; function used to cut out the system part. This little complication is left out above to keep things a bit shorter.)&lt;/li&gt;
&lt;li&gt;Finally, there is the &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clause, with the duration from the table above. An alert with a &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clause only fires once the alerting condition has been fulfilled continuously for the configured duration. Intriguingly, the SRE workbook explicitly advises against &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clauses because they inevitably increase the detection time for the alerting condition. We still decided to use a &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clause with a duration that is short compared to expected response times (of the alert itself and of the engineers getting alerted). The &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clause is essentially a simple but effective defense against statistical outliers. For example, if a service has just started to receive traffic after a launch or an undrain operation, the error rate over any time window will be dominated by the very short time it has received traffic at all. Without the &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; clause, even a very short and modest initial error spike (maybe caused by cold caches) will trigger all the alerts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With the alerts described above, we can reliably detect breaches of configurable SLOs with an appropriate detection time (the quicker the error budget is burned, the sooner the alert will fire) and a sufficiently short reset time. The resulting alerts are strictly symptom-based and therefore always relevant. To reach a person able to act on a particular alert, it is routed to the on-call rotation owning the affected backend.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Playback on Web at SoundCloud]]></title><description><![CDATA[Maestro is a library we have developed to handle all playback across SoundCloud web applications. It successfully handles tens of millions of plays per day across soundcloud.com, our mobile site, our widget, Chromecast, and our Xbox application. We are considering open sourcing it, and this blog post is a technical overview of what we’ve achieved thus far with Maestro.]]></description><link>https://developers.soundcloud.com/blog/playback-on-web-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/playback-on-web-at-soundcloud</guid><pubDate>Fri, 03 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Maestro is a library we have developed to handle all playback across SoundCloud web applications. It successfully handles tens of millions of plays per day across &lt;a href=&quot;https://soundcloud.com/&quot;&gt;soundcloud.com&lt;/a&gt;, our mobile site, our &lt;a href=&quot;https://soundcloud.com/pages/embed&quot;&gt;widget&lt;/a&gt;, Chromecast, and our &lt;a href=&quot;https://developers.soundcloud.com/blog/garbage-collection-in-redux-applications&quot;&gt;Xbox application&lt;/a&gt;. We are considering open sourcing it, and this blog post is a technical overview of what we’ve achieved thus far with Maestro.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;What We Support&lt;/h2&gt;
&lt;p&gt;At SoundCloud, we aim to support all modern web browsers, mobile browsers, and IE 11. Our goal is to provide the best possible playback experience using the functionality provided by your browser.&lt;/p&gt;
&lt;h2&gt;What We Stream&lt;/h2&gt;
&lt;p&gt;We currently stream three codecs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mp3&lt;/li&gt;
&lt;li&gt;opus&lt;/li&gt;
&lt;li&gt;aac&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our primary protocol is HLS (HTTP Live Streaming). This means the contents of a track are split up into short segments, and we have a separate file (playlist), which contains URLs to all of the segments, along with their corresponding times in the track. You can find more information about HLS &lt;a href=&quot;https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/video_on_demand_playlist_construction&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What the Browser Provides&lt;/h2&gt;
&lt;p&gt;We make use of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;audio /&amp;gt;&lt;/code&gt;&lt;/a&gt; element, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API&quot;&gt;Media Source Extensions (MSE)&lt;/a&gt;, and the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&quot;&gt;Web Audio API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The minimum support we require is the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;audio /&amp;gt;&lt;/code&gt; element and the ability to play one of our codecs. MSE and the Web Audio API are required for the best experience.&lt;/p&gt;
&lt;p&gt;We are able to gracefully degrade when either the Web Audio API or MSE is missing or there are errors during playback.&lt;/p&gt;
&lt;p&gt;We’ll cover what we use MSE and the Web Audio API for in a bit, but first, let’s see what the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;audio /&amp;gt;&lt;/code&gt; element alone does for us.&lt;/p&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;audio /&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is able to take a URL of an audio file and play it back if the codec is supported by the browser. It is informed of the codec in the &lt;code class=&quot;language-text&quot;&gt;content-type&lt;/code&gt; header on the response, and it provides an API, which can be used to control playback and to determine if the browser supports the codec:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; audio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;audio&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://example.invalid/something.mp3&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Media Source Extensions&lt;/h3&gt;
&lt;p&gt;With the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;audio /&amp;gt;&lt;/code&gt; element alone, the browser does all the work behind the scenes, and you don’t have access to the underlying buffer.&lt;/p&gt;
&lt;p&gt;With MSE, we are able to create a buffer for a codec that is supported by the browser. We are then able to handle both downloading the media ourselves and appending it to the buffer. This means we can make optimizations, such as preloading, which is where we download the first few seconds of tracks we think you will play before you click the play button and store them in memory. And then when you click play, we can append this data straight from memory into the buffer instead of having to go to the network:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; audio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;audio&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

mse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sourceopen&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// &apos;audio/mpeg&apos; for mp3&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addSourceBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;audio/mpeg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sequence&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;http://example.invalid/segment0.mp3&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Web Audio API&lt;/h3&gt;
&lt;p&gt;The Web Audio API is the newest of the APIs mentioned here. We use a small portion of this API to perform a quick fade in and out when you play, pause, or seek. This makes seeking feel snappier, and playing/pausing is less abrupt:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; audio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;audio&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AudioContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sourceNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createMediaElementSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audio&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; gainNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createGain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
sourceNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://example.invalid/something.mp3&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
audio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Schedule fade out.&lt;/span&gt;
gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;linearRampToValueAtTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Goals of Maestro&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simple API&lt;/li&gt;
&lt;li&gt;Plugin architecture&lt;/li&gt;
&lt;li&gt;Easy feature detection&lt;/li&gt;
&lt;li&gt;Type safety&lt;/li&gt;
&lt;li&gt;Support all major browsers&lt;/li&gt;
&lt;li&gt;Handle bugs/differences in browser implementations&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Great performance&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to preload&lt;/li&gt;
&lt;li&gt;As responsive as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configurable buffer length and cache size&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should be able to work on devices with constrained memory, such as Chromecast&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Metrics&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide error and performance data, which can be monitored to detect bugs and make more improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tech Stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Microsoft/TypeScript&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lerna/lerna&quot;&gt;Lerna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yarnpkg/yarn&quot;&gt;Yarn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/webpack&quot;&gt;webpack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The API&lt;/h2&gt;
&lt;p&gt;Maestro consists of many packages. The core package provides an abstract &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; class, which provides the player API. It delegates tasks to specific implementations, but external communication happens through &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt;. Up-to-date state can be retrieved through player methods, and the user is also notified of any changes.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code class=&quot;language-text&quot;&gt;play()&lt;/code&gt; method returns a &lt;code class=&quot;language-text&quot;&gt;Promise&lt;/code&gt; which may resolve or reject. The &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; will inform the implementation when it should play or pause, and the implementation will inform the &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; when it is actually playing. Each player implementation is decoupled from the actual &lt;code class=&quot;language-text&quot;&gt;play()&lt;/code&gt; method. This also means the &lt;code class=&quot;language-text&quot;&gt;isPlaying()&lt;/code&gt; method and corresponding updates can be handled completely by &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt;. Another example is &lt;code class=&quot;language-text&quot;&gt;getPosition()&lt;/code&gt;, which will normally ask the implementation for the current time, except for when a seek is in progress, in which case &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; will return the requested position. This means the time from &lt;code class=&quot;language-text&quot;&gt;getPosition()&lt;/code&gt; always makes sense and users don’t need to override it when seeking to ensure it doesn’t jump around.&lt;/p&gt;
&lt;p&gt;Player implementations are contained in separate packages, and they all extend &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt;. We currently have the following players:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;HTML5Player&lt;/code&gt; — This is the simplest player. It takes a URL and a MIME type, which are passed directly to a media element.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;HLSMSEPlayer&lt;/code&gt; — This extends &lt;code class=&quot;language-text&quot;&gt;HTML5Player&lt;/code&gt;, and it takes a &lt;code class=&quot;language-text&quot;&gt;Playlist&lt;/code&gt; object, which is responsible for providing segment data. This player uses MSE.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ChromecastPlayer&lt;/code&gt; — This player is a proxy to controlling a Chromecast.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ProxyPlayer&lt;/code&gt; — This player can control another player, which can be switched on the fly. It also has some configuration related to the direction to sync when a new player is provided. One of the benefits of this player is that it can be provided to apps synchronously, even when the real player isn’t available yet. Then, once the real player is available, its state will be synced to match the proxy. Some other use cases of this are switching between playback on a Chromecast and locally, or switching qualities. The app only has to interact with one player, and the switch can happen behind the scenes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;State Management and Events&lt;/h2&gt;
&lt;p&gt;There is a lot of playback state to manage, and in Maestro, most of this is contained inside &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt;. Users also want to know when parts of the state change and will sometimes react to changes by performing other player actions. This introduces some complexities when we are running on a single thread. Sometimes we also want to update several parts of the state atomically (across multiple functions). An example is: If the user seeks to the end of the media, we also want to update the &lt;code class=&quot;language-text&quot;&gt;ended&lt;/code&gt; flag to &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;. The logic concerned with updating the &lt;code class=&quot;language-text&quot;&gt;ended&lt;/code&gt; flag is not tied to the seeking logic in code, but the update of the seeking state and the ended state should happen together in the API.&lt;/p&gt;
&lt;p&gt;To achieve this, we built a component called &lt;code class=&quot;language-text&quot;&gt;StateManager&lt;/code&gt;, which enables us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update multiple parts of the state across functions before calling out to inform the user of the changes.&lt;/li&gt;
&lt;li&gt;Notify the user of state changes at the end of the player call stack so that any future interactions they have with the player are not interleaved in the call stack as a result. (For example, do the work and then fire the event, instead of firing the event and then doing the work.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;StateManager&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The state manager maintains a state object. All changes to this object are made using an &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; method, and a callback can be provided, which is then notified of any state changes that happened inside the last &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt;. These calls can be nested:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; ChangesCallback&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token parameter&quot;&gt;changes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Readonly&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Partial&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Readonly&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; Control &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; Subscriber&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  callback&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ChangesCallback&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  localState&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StateManager&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Object &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; _state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; _subscribers&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Subscriber&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; _updating &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;initialState&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initialState&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wasUpdating &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_updating&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_updating &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// error handling...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;wasUpdating&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_updating &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_afterUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ChangesCallback&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; skipPast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Control &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_afterUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_subscribers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;subscriber&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; diff &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_calculateDiff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localState&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// We always recalculate the diff just before calling a subscriber,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// which means that the state is always up to date at the point when&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// the subscriber is called.&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;diff&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;deferException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;diff&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localState&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_calculateDiff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compare&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Readonly&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Partial&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Example Usage&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; OurState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stateManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StateManager&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;OurState&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;something&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  c&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  d&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

stateManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; d &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// On first execution:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// a === 2&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// b === &apos;something else&apos;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// c === false&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// d === undefined&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// On second execution:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// a === undefined&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// b === undefined&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// c === undefined&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// d === 3&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;updateD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

stateManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; d &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// a === 2&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// b === &apos;something else&apos;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// c === false&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// d === 3&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;doSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  stateManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;updateB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;c &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  stateManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;something else&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  stateManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;d &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that the first subscribe callback will be executed twice, but the second will only be executed once, and only with the latest state (i.e. &lt;code class=&quot;language-text&quot;&gt;d === 3&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Also note that we don’t get nested call stacks, because the callbacks are only executed once the work is finished.&lt;/p&gt;
&lt;h2&gt;Browser Limitations&lt;/h2&gt;
&lt;p&gt;Unfortunately, different browsers have different codec support (which can also depend on the OS) and different container requirements.&lt;/p&gt;
&lt;p&gt;Chrome, for example, supports a raw MP3 file in MSE, but Firefox requires the MP3 to be in an MP4 container. This means that in Firefox, we need to package the MP3 that we download into an MP4 in the browser. Other codecs have similar complexities.&lt;/p&gt;
&lt;p&gt;It’s also inevitable that there are bugs. Supporting a media processing pipeline that can handle a huge variety of media, without breaking backward compatibility, in a secure way, and in a web browser, is a huge task! Luckily, Maestro is able to handle workarounds for various bugs in different browsers, some which differ under the hood between versions.&lt;/p&gt;
&lt;p&gt;Autoplay policies also differ between browsers, and this means we currently have to share media elements between players. This adds complexity because when the source of an element is changed, there are still events emitted for the previous source for a short time after, meaning we have to wait for the “emptied” event before attempting to use it, and we have to keep track of everything that was requested in the meantime. Maestro’s &lt;code class=&quot;language-text&quot;&gt;HTML5Player&lt;/code&gt; makes this simple with &lt;code class=&quot;language-text&quot;&gt;provideMediaElement(mediaEl)&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;revokeMediaElement()&lt;/code&gt;. This allows you to move a media element between players at runtime. When a player doesn’t have a media element, the player is simply stalled.&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; and player implementations are covered by unit tests and integration tests: We use &lt;a href=&quot;https://github.com/mochajs/mocha&quot;&gt;Mocha&lt;/a&gt;, &lt;a href=&quot;https://github.com/sinonjs/sinon&quot;&gt;Sinon&lt;/a&gt;, and &lt;a href=&quot;https://github.com/karma-runner/karma&quot;&gt;Karma&lt;/a&gt;, along with &lt;a href=&quot;https://github.com/schnittstabil/mocha-screencast-reporter&quot;&gt;mocha-screencast-reporter&lt;/a&gt;. The latter is great for viewing the progress of tests running remotely.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; alone currently has more than 700 tests, which ensures that the API behaves correctly. One test, for example, checks that the &lt;code class=&quot;language-text&quot;&gt;play()&lt;/code&gt; promise is resolved when the implementation reports it is playing. Another checks that &lt;code class=&quot;language-text&quot;&gt;play()&lt;/code&gt; is rejected with the correct error if the player is killed before the play request completes. There are also tests that check that the player errors if an inconsistency is detected — such as a player implementation reporting that a seek request could not be completed when the &lt;code class=&quot;language-text&quot;&gt;BasePlayer&lt;/code&gt; never requested a seek operation.&lt;/p&gt;
&lt;p&gt;We also run all the tests on a variety of browsers and browser versions (including Chrome and Firefox beta) using &lt;a href=&quot;https://saucelabs.com/&quot;&gt;SauceLabs&lt;/a&gt;. This takes several hours to complete, and so we test the major browsers for pull requests, and then we test everything just before a release. We also run all the tests weekly to ensure that there are no issues arising with new browser versions. Doing so once highlighted a bug with Web Audio in Firefox beta which would cause playback to freeze after the first few seconds.&lt;/p&gt;
&lt;h2&gt;Progressive Streaming (with the &lt;code class=&quot;language-text&quot;&gt;fetch()&lt;/code&gt; API)&lt;/h2&gt;
&lt;p&gt;We recently added support for progressive streaming (in supported browsers). This means that instead of having to wait for an entire segment to be downloaded before we process it and append it to the buffer, we’re able to process the data as it arrives, meaning we’re able to start playback before the segment download has finished.&lt;/p&gt;
&lt;p&gt;This was made possible with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;fetch()&lt;/code&gt;&lt;/a&gt; API (and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestResponseType&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;moz-chunked-arraybuffer&lt;/code&gt;&lt;/a&gt; in Firefox), which provides small parts of the data while it is still being downloaded:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; body &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WritableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;chunk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Got part&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Aborted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Got everything&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before we added progressive streaming, if a download failed, we would just retry it, and this logic was pretty self-contained. With progressive streaming, it is more complex because, if a download fails part-way through, the entire pipeline has already started working on the data. We decided to retry the request on an error and discard all bytes that we have already seen. If the retry fails, then we are able to signal the error down the pipeline.&lt;/p&gt;
&lt;p&gt;This also brings about more complexity. Before, we knew that each segment contained a complete number of valid units of audio, meaning the different parts of the pipeline could make certain assumptions. Now each part of data can contain fractions of units of audio, so we need to be able to detect when this happens and keep a buffer that waits for a complete unit to arrive.&lt;/p&gt;
&lt;h2&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;We have run Maestro in production since June 2017, and we’ve have had minimal reports of playback issues. We are able to monitor the performance and errors in real time, and in cases where errors occur, we are able to retrieve playback logs, which help with debugging.&lt;/p&gt;
&lt;p&gt;We are looking for where to take Maestro next, and that’s where you come in: Let us know how you would use it and what you would like to see :D&lt;/p&gt;
&lt;p&gt;If you have any questions regarding this post, or you notice any playback problems on &lt;a href=&quot;https://soundcloud.com/rick-astley-official/never-gonna-give-you-up&quot;&gt;soundcloud.com&lt;/a&gt; ;), please get in touch!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Taking Our Bug Bounty Program Public]]></title><description><![CDATA[We’re excited to announce the launch of our public bug bounty program with Bugcrowd — the #1 crowdsourced security platform. This public program is open to Bugcrowd’s full crowd of top, trusted whitehat hackers, and we will award up to $1,500 per vulnerability identified on our website, API, and mobile apps.]]></description><link>https://developers.soundcloud.com/blog/taking-our-bug-bounty-program-public</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/taking-our-bug-bounty-program-public</guid><pubDate>Thu, 18 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re excited to announce the launch of our &lt;a href=&quot;https://bugcrowd.com/soundcloud&quot;&gt;public bug bounty program&lt;/a&gt; with Bugcrowd — the #1 crowdsourced security platform. This public program is open to Bugcrowd’s full crowd of top, trusted whitehat hackers, and we will award up to $1,500 per vulnerability identified on our website, API, and mobile apps.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Security is a top priority at SoundCloud, and we’re committed to keeping the community and its content safe. And, as a leading audio streaming platform, we’re prepared to handle an extremely unique set of security issues. These span from processing, transcoding, and formatting user-generated content without risking remote code executions, to detecting and blocking malware distribution and preventing illegitimate downloads and streaming access. Additionally, since the platform offers a highly social streaming experience with user-generated content and integration, we have to be mindful of potential XSS and CSRF attacks.&lt;/p&gt;
&lt;p&gt;As part of our commitment to our users, we’re focused on building state-of-the-art security monitoring and protection solutions for our platform. In order to balance that focus with the team’s operational work, we’re always looking for ways to improve our efficiency. And one of those ways is to have additional support for handling top-of-funnel security work for vulnerability reports. Examples of this work include triaging, reproducing, prioritizing, and resolving duplicates.&lt;/p&gt;
&lt;p&gt;This is where Bugcrowd comes in. Bugcrowd’s community-driven vulnerability testing is a key tool for us to receive external testing on our services and platform, along with explicit pentesting by security agencies and our various internal automated tests and peer reviews. With Bugcrowd, the quantity and quality of vulnerability reports is higher than ever before. Many of Bugcrowd’s security testers follow the same news and read the same forums as malicious users, so they help us react to new attack vectors much faster.&lt;/p&gt;
&lt;p&gt;Since using Bugcrowd, we’ve seen several benefits, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A significantly lowered barrier to reporting security vulnerabilities and increased quality in security vulnerability reports&lt;/li&gt;
&lt;li&gt;Additional dedicated time to focus on building services specific to our needs&lt;/li&gt;
&lt;li&gt;Having a known platform with clear processes, taxonomy, and rules that attracts more professional researchers with more expertise&lt;/li&gt;
&lt;li&gt;Increased confidence that critical issues are continuously being probed, identified, and addressed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re excited to take this next step in our crowdsourced security journey: making our bug bounty program public. To engage in our program, take a look at our program brief: &lt;a href=&quot;https://bugcrowd.com/soundcloud&quot;&gt;https://bugcrowd.com/soundcloud&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Release Quality and Mobile Trains]]></title><description><![CDATA[Once every two weeks, we prepare new versions of our mobile apps to be published to the app stores. Being confident about releasing software at that scale — with as many features and code contributions as we have and while targeting a wide range of devices like we do at SoundCloud — is no easy task. So, over the last few years, we have introduced many tools and practices in our release process to aid us. In this blog post, I’ll cover some of the techniques we use to guarantee we’re always releasing quality Android applications at SoundCloud.]]></description><link>https://developers.soundcloud.com/blog/quality-mobile-trains</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/quality-mobile-trains</guid><pubDate>Wed, 03 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once every two weeks, we prepare new versions of our mobile apps to be published to the app stores. Being confident about releasing software at that scale — with as many features and code contributions as we have and while targeting a wide range of devices like we do at SoundCloud — is no easy task. So, over the last few years, we have introduced many tools and practices in our release process to aid us.&lt;/p&gt;
&lt;p&gt;In this blog post, I’ll cover some of the techniques we use to guarantee we’re always releasing quality Android applications at SoundCloud.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;All Aboard!&lt;/h2&gt;
&lt;p&gt;Maintaining high quality is about more than just hitting deadlines; it’s a continuous job. As such, we’re constantly keeping an eye on our code and asset changes to make sure we get the best out of our engineering efforts. In addition to human code reviews, our repositories’ pull requests go through an automated pipeline of tests for every single changeset. This pipeline makes use of industry standard tools for both static analysis and unit, integration, and UI testing.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b0c15442b3e61d6b97a050f20b2689a1/60bac/github-comment.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.20659062103929%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABK0lEQVQoz01RCXKEMAzj/09sO912CTcLCeFIuFTLbLdlRmPikWXLTm6dgx9GmKJC0/Yo6xaP3imaR4+wbljihhCveGF9veewYloirGgEySfVOGOTIusGuMFjmgMGPymmeYEfJ4XzI4aR+RHjNCuX+Si1rGGTdduRQL5T0DYNnHN8ous61HWDdV1hjEGaGlRVhbZt0QjPWouiLPEQ3r5vwq2Fu+E8TyQUY7fb1x29dZiXgFzsZ3mpE6RZjkrEyduPE4cUXREK1pLLqIKcaPAen1/fuKeZWkpNrnaJu8l0N7TGfcYnfv/ZlFa1yXFcgtzf2/uHHMIqyeQFFlm2l11lRakF8Z/YC5K3zusQ5Kggx6RN2l1C1Gk4PnN8s1l4XvWKf6AoD8RjcYe7CP4A2i8cUw2vM8gAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;An automatically generated GitHub comment with statistics about the code changes&quot;
        title=&quot;An automatically generated GitHub comment with statistics about the code changes&quot;
        src=&quot;/blog/static/b0c15442b3e61d6b97a050f20b2689a1/8ff1e/github-comment.png&quot;
        srcset=&quot;/blog/static/b0c15442b3e61d6b97a050f20b2689a1/9ec3c/github-comment.png 200w,
/blog/static/b0c15442b3e61d6b97a050f20b2689a1/c7805/github-comment.png 400w,
/blog/static/b0c15442b3e61d6b97a050f20b2689a1/8ff1e/github-comment.png 800w,
/blog/static/b0c15442b3e61d6b97a050f20b2689a1/6ff5e/github-comment.png 1200w,
/blog/static/b0c15442b3e61d6b97a050f20b2689a1/60bac/github-comment.png 1578w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;An automatically generated GitHub comment with statistics about the code changes&lt;/p&gt;&lt;br/&gt;
&lt;p&gt;We’ve also built tools that highlight other potential issues early on during the code review cycle. By integrating them with other pieces of our day-to-day routine, such as GitHub (above) and Slack (below), we ensure our developers have a more transparent understanding of their applied diffs.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5686bfe2d61df3b995d97dfddfa7b051/aa1b0/slack-message.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.42339832869081%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0ElEQVQY03WNWVLDQAxEfX44BlyI5IOAoGK8EXviLfYsHsd+jBOKP7rqSerqkhQ9pg88vz2RS85u/4KIUFaKs1JorTHG4Jy7zdbagGEK3jl781u+de89KxCJfUfOghyEg3zyut+RfAh106KqKqCo65qyLKnCnGQ5xfeJvDih1P3xxjgMbIq2Yi6GJE3IVUc7WP6TnReSxpAF0kCjPXpa0H5hcDO9nu4H264ljmOyquFY9hT1QDc65uvCuq5/XJew7DxfKuRh2UwzY/AXM9H/8gMiqDAKL81eKQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A Slack message sent to the author of the pull request after tests are run&quot;
        title=&quot;A Slack message sent to the author of the pull request after tests are run&quot;
        src=&quot;/blog/static/5686bfe2d61df3b995d97dfddfa7b051/8ff1e/slack-message.png&quot;
        srcset=&quot;/blog/static/5686bfe2d61df3b995d97dfddfa7b051/9ec3c/slack-message.png 200w,
/blog/static/5686bfe2d61df3b995d97dfddfa7b051/c7805/slack-message.png 400w,
/blog/static/5686bfe2d61df3b995d97dfddfa7b051/8ff1e/slack-message.png 800w,
/blog/static/5686bfe2d61df3b995d97dfddfa7b051/6ff5e/slack-message.png 1200w,
/blog/static/5686bfe2d61df3b995d97dfddfa7b051/aa1b0/slack-message.png 1436w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;A Slack message sent to the author of the pull request after tests are run&lt;/p&gt;&lt;br/&gt;
&lt;p&gt;Even so, it’s still a challenge to foresee how an application with our scale will behave in the wild, with the variety of devices, locales, and use cases we support. For that reason, we are also big believers in having a remote feature-toggling system. Not only does it give us a way of preventing issues from spreading once they are detected, but we also use these toggles to enable early internal testing of our new features via our alpha and beta builds and to decouple app releases from feature releases.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 465px;&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/blog/static/0e15abc6ffdc2c555cd20df1ec4c69f2/925b5/feature-flag.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70.10752688172043%; position: relative; bottom: 0; left: 0; background-image: url(&amp;apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACTElEQVQ4y5WUyW7bMBCGfS8KFOgDtEkk2ZIta6VEarHkRXaaFwjie9D2/YG2rmVO/6HkIF0uPXye4Yj8yRkOPZml5Rsryh5vgvh4F4qnD4v4+NFPDFaYHa2lON6xH8Cyj5gTy9c8jfYRvJvchKnjiZoi1ZGfN5Q2HcX1BmzJyyqaFy35GBtbtrSQK5omiqZpATsC30EMggKCie2l1XcIal+1veweerE+9Gm77+d53fv1tl82Xb9c7foAPs9xYtXbUQ4rr1zACSQT7GYj5VMotyTKTzpWex3KnRbNXmftXnui1Omm0Xm30VHTaLFrtZNIDcErF8CnOxtB/Nis7mY1zWWjAQ2saIE0F6o1vl+uMV4bf0hXEepPM2zIY4j+LjgVJbl5rT0s8CDIGxjylcExdYMA6srWQe1mosKcSjsvgiqZ2KOgSbm610l5MBfEl1N0DyS395RvDiRBvj5QiFNHRUH5qqJAFeSnhZZ5SV6izrdRnkxug8z2svI0S7B7pPQsxkkiSTOcCPUzlpnD57FJNZEYK3JTzI2lnifGni1O+S4StpuUpwWnplbalTXSKtk3eEyBEqAU/G0BuFWsMOc0GW0Ndqjhi6BquN802oMCpu1oCYJmR9HmnjjOxCjFzPScuVlGj3YQvA2ESTlSWKi22hXl2KTquuB6kpFhfI3/JYinZLlp8c0T1cXL6rPL5CNZdUZbnMd0Bkb/Vezn2OQ/BsEgm+JFcD+Z/jK99Sei/Hf81Xfz/GKZ8Ut5iz+BZ+z8FYHP4Mt/cl3zDN7/AsgYkEc34XEVAAAAAElFTkSuQmCC&amp;apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;feature flag&quot; title=&quot;feature flag&quot; src=&quot;/blog/static/0e15abc6ffdc2c555cd20df1ec4c69f2/925b5/feature-flag.png&quot; srcset=&quot;/blog/static/0e15abc6ffdc2c555cd20df1ec4c69f2/9ec3c/feature-flag.png 200w,
/blog/static/0e15abc6ffdc2c555cd20df1ec4c69f2/c7805/feature-flag.png 400w,
/blog/static/0e15abc6ffdc2c555cd20df1ec4c69f2/925b5/feature-flag.png 465w&quot; sizes=&quot;(max-width: 465px) 100vw, 465px&quot; loading=&quot;lazy&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;By using continuous integration tools, we ensure that our master branch is always deployable. This is accomplished by scheduling time-based jobs to be run — either nightly, weekly, or bi-weekly — or via triggered jobs (for example, when a pull request is merged into our master or release branches). With these CI jobs, we can guarantee our obfuscated and shrinked release builds are all set after the passes of tools like ProGuard or R8; update to the latest localized translations for all the languages we support; and automate other repetitive tasks.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/975510f5992ad5dd42726e017b5e1b32/2d15a/outdated-pr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 17.92929292929293%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAm0lEQVQY042NWw7CIBBFu//tuQG/qhaYAcpTIFeg1pj4I8nJfWRmWK5skfMTuwsTHyJ8TEi5ID//J3VCzFguD4LzAet9w3rbIBVDscHu4xwYjA9OPf2Rzy7DhQQ2Oxb050PAJgSMMdBag4gghIS1FlLKnrmrArOefswR8/RHR1CKpp8Hnff9kEFrDaVWlFJQu9Z3rh/al//txv4LxjY17Ft/JMIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;GitHub comment pointing to an outdated pull request&quot;
        title=&quot;GitHub comment pointing to an outdated pull request&quot;
        src=&quot;/blog/static/975510f5992ad5dd42726e017b5e1b32/8ff1e/outdated-pr.png&quot;
        srcset=&quot;/blog/static/975510f5992ad5dd42726e017b5e1b32/9ec3c/outdated-pr.png 200w,
/blog/static/975510f5992ad5dd42726e017b5e1b32/c7805/outdated-pr.png 400w,
/blog/static/975510f5992ad5dd42726e017b5e1b32/8ff1e/outdated-pr.png 800w,
/blog/static/975510f5992ad5dd42726e017b5e1b32/6ff5e/outdated-pr.png 1200w,
/blog/static/975510f5992ad5dd42726e017b5e1b32/2d15a/outdated-pr.png 1584w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Release Train Model&lt;/h2&gt;
&lt;p&gt;We have a two-week interval between each of our releases because we follow the &lt;a href=&quot;https://www.scaledagileframework.com/agile-release-train/&quot;&gt;Release Train model&lt;/a&gt;, in which, similarly to a train that departs a station on a specified schedule, we have specified a day for what we refer to as Code Freeze, which is when two members of the engineering collective (previously selected from a rotation of all members) “cut the master branch” into the release branch. We refer to them as release captains, but people familiar with release trains call them &lt;a href=&quot;https://www.scaledagileframework.com/release-train-engineer-and-solution-train-engineer/&quot;&gt;Release Train Engineers (RTE)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pragmatically, this means that once master gets cut, no new code gets into the changeset that the release captains are responsible for publishing (with the exception of emergency hotfixes, which are then merged back to the master branch).&lt;/p&gt;
&lt;p&gt;Following a release train not only helps in limiting the ownership of each pair of release captains, but it also gives everyone a sense of predictability over when changes will be rolled out to the public.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3e706dce4a12259168f3785b55a52c0b/b5cbf/release-calendar.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.406676783004556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACD0lEQVQoz5WR60/TYBjF+zdj/IAxMXHcLzKViCgxUYbAAG9Rg+gEnaACw2y4G2zIvezKOrqt69r+fFqiiQl88ElOTs9z3pz2PVWchoFZ1bHqDUy9hqXXMbQqVq3uaVv2jYqGUzdo6vpf7XJN06hqp5SKeY8No4HSO+PHN97D4JSf0bn7+AI9dAR6GQreYWTmnud1TfTTGejj4bNROif66BbdITwfesHGyhIrnxYIh95SyB2jLGU2WFPTxHJZ1g9TvE+t8u7nV5azUZL5Pb4fJYmoW6wL4uoOH5KrLKYjLMS/UapVcceybVqWjW07KFw0LedfbV/iS4AtYYZh4AhbEqp0hx5zc36M4XCQ2WiIW18mGQw/5cHaS9RyDk36Oj5RqQp/3o4wKL4/PIV/eZps6dDLNU0Tx3HOvzB/WkQt5MiXi9SbUrQL02X5KS1T3mphC6xWi0bT8PZ100A3zn03yJ3zQPuSK//n/CnIzVYOsltk4jH2tpPsC/a2EuwkNznIpNiVZ1dnEzHP291OsC/7jOiDbJqddJxMapNELEImGZV6yijtwQGuBfvpfj7M0KtR2sY7aHviwzd7m7uvx2if7uPG3BDXZwYYefOIq4Eu2id7uSLnFtc+8iudIL75g+jGKpVTCSzqGrpt0rBblA0dVStxVC5QOKt4uzOriS6oyZmadKdWSpxoZY6lc+uC6/8GBHzPGYfLHxEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The dates for each code freeze are displayed for the entire company in a shared calendar&quot;
        title=&quot;The dates for each code freeze are displayed for the entire company in a shared calendar&quot;
        src=&quot;/blog/static/3e706dce4a12259168f3785b55a52c0b/8ff1e/release-calendar.png&quot;
        srcset=&quot;/blog/static/3e706dce4a12259168f3785b55a52c0b/9ec3c/release-calendar.png 200w,
/blog/static/3e706dce4a12259168f3785b55a52c0b/c7805/release-calendar.png 400w,
/blog/static/3e706dce4a12259168f3785b55a52c0b/8ff1e/release-calendar.png 800w,
/blog/static/3e706dce4a12259168f3785b55a52c0b/6ff5e/release-calendar.png 1200w,
/blog/static/3e706dce4a12259168f3785b55a52c0b/b5cbf/release-calendar.png 1318w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;The dates for each code freeze are displayed for the entire company in a shared calendar&lt;/p&gt;&lt;br/&gt;
&lt;p&gt;As a result, our design and data analysis teams can estimate when their help will be needed for defining and delivering specifications for feature development; product managers know when their experiments and product launches will go live; and the backend and frontend engineering teams are able to plan their work to aim for certain code freeze dates.&lt;/p&gt;
&lt;p&gt;Code freeze is just one of the steps described in a detailed checklist for the captains (generated via our CI jobs). Additional steps include naming the new build for internal reference (we follow alphabetical order and always pick from a pop culture character set — we’re currently using Marvel superheroes as inspiration!) and updating documents, links, and Slack channels for the visibility of the outgoing train.&lt;/p&gt;
&lt;p&gt;The release captains are then responsible for performing a set of manual regression tests that cover the main use cases of the application and some of the scenarios we could not automate (yet). Once all is verified and the captains feel confident with their release, they are free to start the staged rollout process by deploying the build to the beta channel and subsequently rolling out to percentages of the production user base.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/5841d/ci-pipeline.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.251572327044023%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAyklEQVQY022QWXPCMAyE+f9/rweTB45CCGmCDY0P+ZCzyKZ9q16+2R1pR9LGOQfvPXIpCCGgFEZKCeu6whgDIoJWqnnzNMFaK1ojiK/E58ytr1ad2VzHEbv9AWox2LoeY7yjc5fGN3PEEBQ+3RnXqPFuvnChG7a+F1/jw5+wkEUkWUTCWuD3rLA/9bhbwpR+sGSPWWiEgRMSZ1COiJUckUrVoWkvzMwoXFrYK/CmcTgPeMjpHb0262jAHB5YE4NlIKf8L/mXfy+q9QR/TzSDRUNf9wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Continuous integration pipeline with staged rollout steps&quot;
        title=&quot;Continuous integration pipeline with staged rollout steps&quot;
        src=&quot;/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/8ff1e/ci-pipeline.png&quot;
        srcset=&quot;/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/9ec3c/ci-pipeline.png 200w,
/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/c7805/ci-pipeline.png 400w,
/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/8ff1e/ci-pipeline.png 800w,
/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/6ff5e/ci-pipeline.png 1200w,
/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/2f950/ci-pipeline.png 1600w,
/blog/static/b13e9e064cfa2cd5f3f20547dc627a2d/5841d/ci-pipeline.png 2385w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;Continuous integration pipeline with staged rollout steps&lt;/p&gt;&lt;br/&gt;
&lt;h2&gt;The Train Has Departed the Station&lt;/h2&gt;
&lt;p&gt;During the two-week period of staged rollout, it’s the release captains’ job to monitor data, metrics, and dashboards for user reports, reviews, and crashes that might have ended up in production. After evaluating the impact of each of these issues, it is their call whether to request new changes to be applied in their train, which is done by pinging the involved engineers and working toward fixes.&lt;/p&gt;
&lt;p&gt;We aim to always be attentive to feedback, but the sooner issues get reported, the sooner we can act to fix them. On that note, I’d like to &lt;a href=&quot;https://play.google.com/apps/testing/com.soundcloud.android&quot;&gt;encourage you to join the beta&lt;/a&gt; to get early access to new exciting features, in addition to supporting us by being part of this direct channel of communication.&lt;/p&gt;
&lt;p&gt;Just before the end of their two-week “shift,” the captains might decide to hold a &lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/postmortem-culture/&quot;&gt;“post-mortem meeting,”&lt;/a&gt; in which incidents are discussed and their root causes understood and documented so that the entire platform collective can come up with preventive actions to reduce the likelihood of reoccurrence. The idea is that these are blameless, with the sole goal being that of improving systems and processes and spreading the knowledge across the entire team.&lt;/p&gt;
&lt;h2&gt;End of the Line&lt;/h2&gt;
&lt;p&gt;Overall, ensuring a good standard of what we publish to our users is a product of many factors. Not only do we need, use, and build tooling and automation to facilitate our daily work, but more importantly, at SoundCloud, we are always attentive to supporting the processes the team puts around building a quality-driven culture.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to Reindex One Billion Documents in One Hour at SoundCloud]]></title><description><![CDATA[In the past, the Search Team at SoundCloud had high lead times for making updates to Elasticsearch clusters, either during the implementation of a new feature or simply while fixing a bug. This was because both tasks require us to reindex our catalog from scratch, which means reindexing more than 720 million users, tracks, playlists, and albums. Altogether, this process took up to one week, though there was even one scenario where it almost took one month to roll out a bug fix. In this post, I would like to share the concrete Elasticsearch tweaks we made so that we can now reindex our entire catalog in one hour.]]></description><link>https://developers.soundcloud.com/blog/how-to-reindex-1-billion-documents-in-1-hour-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-to-reindex-1-billion-documents-in-1-hour-at-soundcloud</guid><pubDate>Thu, 21 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the past, the Search Team at SoundCloud had high lead times for making updates to Elasticsearch clusters, either during the implementation of a new feature or simply while fixing a bug. This was because both tasks require us to reindex our catalog from scratch, which means reindexing more than 720 million users, tracks, playlists, and albums. Altogether, this process took up to one week, though there was even one scenario where it almost took one month to roll out a bug fix.&lt;/p&gt;
&lt;p&gt;In this post, I would like to share the concrete Elasticsearch tweaks we made so that we can now reindex our entire catalog in one hour.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Search at SoundCloud&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 593px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/17b23d599c14549fa785e6603e0f4344/fe994/indexing_pipeline.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.67116357504217%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsSAAALEgHS3X78AAAE2UlEQVQ4y5VUe2xTVRg/7RgZMcYsyiOYkCgh8UUMEQhRoiIQNUQDfzhIhJgYH4mJun8M26IuwtTwEJDxGA5h7MHoHl3XdX2svX3eR9vb29ve9d6+aNeybt06tjE2mOvYjufe8nBjgn7/3O/7ne/73d853/kOAMiUJgVQmZtlDg6TT8MJOYQQbN+xDaCvTFzX4u0v0hFHBRMjFd44UUXwpg/BXevEVVJOnepiDjhRfVQsBPPtHhkpWD8LpJg7fZPXYHI8AeM3emD0egR6rhItX3zz5WIxRxTzEMGZSyefxjz6tyzerl8V2vr3RczmwzYEB3yQ6YnDZqNzqqmLmsaDA1k6fjObGO9DpI5jYt5ANplXcuC7B2QmWvcx3m0O0VFyPHU7Bn0J92iTrmEfwk6nbvdAnI9nlWYWttu4WTo+CslQ8o6QDsHulOdaVcO5p0QOJCan8odfSlcyMSoYGuRgp0OFkhjIJtwT5Ye+X+ONuyrTUwkopH3Tds4FyaB31p/kZvl+diY2wsPuXiaDtrs8txtTjnDj6+sLqKC9EZHcwgMWxuDUnOiwKd8U14iAZU96Kgkj1wPZq8OBaX/SORPO+GciQ4HJweleyPa49HfPe9Hw6HCeWq3ObXnre1uWNGpq1yJXPv9s6SjRGMqkoMnDQiVmhh12HLLJJPTGPUN6l3LNQ80Yhv1z4thoUG50a+WNmrp7nZNZWctXdJTSk7ydd0cIkhTw368YFM8SOuqJRDJRlk6nj8ViseL6+vonpYqq+lPAQGnkNS3VoKxi/33ydqtZUlzV6C1UWT2f/tncXqI0U982GZjXRNxN07vGx8fh2NgYHBkZgTiObwAL2e5PPgIOzgra7d2SSr2Tf1VpZg6UVpwovtBq/FFLBvZKjbDZixIJ1DRBkEhZlt0EHmcavPv+pVUayR1tOtOSe3FQEJ5PpVIKtGUTIr6IYdjyR5Kp7T6w/6cj6JxzU4Nia7vNt03yTUQe+L9Wp7aA6iYt2Pv511JcWdPSirA3RP9Cm0WeyWRk6MpIsy9+g8HgowmpSAaUHzl9f64NrjCmpYR3RL/N4pXrCT9wu92A53ngdDr/m0o0ag+6bvNtNLjD0oNAhTOyxxYXFhaCZ5Yuk/znVq8Gl7WOnDJ3+BUNETh5+Gzt4St68pTJG9sn4l109N9Jz9QoABPulfwOs1MWTd9cJI6UGJ9v1q+ouqzeWVx2cM+h05eKGnXEOhHvn4Byh4fPM+KM7O4IPkxM+hf86+L5QP7igoJ/xp02z9yxPVldB7hYWiIzUb6XuuMDP5O+cKXa6NgiYlYn+wKG07stpGezNP9bt+Yr2jq3U15+p9ZoWSViQu/oAzG1KgzYuWRe7lnnSsNDWRgcmIRGOjTz29mad0m+1xbN/AW55Chs1jsOtGGuzb6eYcinxqDNF7tReb7h7TkK6ztswOi5KhGqHVwpLqShPdA3zCTG4BUdcVTnCjk6KQESoUGoo4QhhZ4sNtBRqHOFb/n7bkOFDlfNIUSHDvSukCS5FaNfNjGxQZEskJ6CLSZ3SZc7cjw0NAN9vRMQ5WUbNPYPMDae5vonYWBgCqqsbOeC3UYFEunB4+dWIWV7lGZPUYeDW7psxcp8hYEqR3exrgPnDq5bv0mORKxD63+g9Vq13b9LrPsbi2gSFnHNxacAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Simplified view of the search indexing pipeline&quot;
        title=&quot;Simplified view of the search indexing pipeline&quot;
        src=&quot;/blog/static/17b23d599c14549fa785e6603e0f4344/fe994/indexing_pipeline.png&quot;
        srcset=&quot;/blog/static/17b23d599c14549fa785e6603e0f4344/9ec3c/indexing_pipeline.png 200w,
/blog/static/17b23d599c14549fa785e6603e0f4344/c7805/indexing_pipeline.png 400w,
/blog/static/17b23d599c14549fa785e6603e0f4344/fe994/indexing_pipeline.png 593w&quot;
        sizes=&quot;(max-width: 593px) 100vw, 593px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;At SoundCloud, we use what is canonically known as &lt;a href=&quot;http://milinda.pathirage.org/kappa-architecture.com/&quot;&gt;Kappa Architecture&lt;/a&gt; for our search indexing pipeline. The main idea here is: have an immutable data store and stream data to auxiliary stores.&lt;/p&gt;
&lt;p&gt;So, how does this work? Once a user, for example, uploads a track or a playlist on SoundCloud, we receive a live update&lt;sup&gt;&lt;a href=&quot;#f1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; in the &lt;code class=&quot;language-text&quot;&gt;eventfeeder&lt;/code&gt;. Then the &lt;code class=&quot;language-text&quot;&gt;indexer&lt;/code&gt; fetches the uploaded data&lt;sup&gt;&lt;a href=&quot;#f2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
from our primary data source — MySQL — and creates a JSON document, which is published to Kafka. The Kafka topics have &lt;a href=&quot;https://kafka.apache.org/0100/documentation.html#compaction&quot;&gt;log compaction&lt;/a&gt; enabled, which means we will only retain the latest version of the documents. Finally, the &lt;code class=&quot;language-text&quot;&gt;shipper&lt;/code&gt; reads the JSON documents from Kafka and sends them to Elasticsearch via the &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html&quot;&gt;bulk API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since we use Kafka to back the document storage, we can reindex a new cluster from scratch by simply resetting the Kafka consumer offset of the &lt;code class=&quot;language-text&quot;&gt;shipper&lt;/code&gt; component, i.e. replaying all stored messages in Kafka.&lt;/p&gt;
&lt;h2&gt;Elasticsearch Cluster Setup&lt;/h2&gt;
&lt;p&gt;As you can see in the diagram above, in order to have high availability and isolation of failures, we have three clusters running in production, all with the same dataset. Each cluster consists of 30 Elasticsearch nodes running on 30 boxes. As a general rule of thumb, we try to scale Elasticsearch horizontally instead of vertically, i.e. adding more boxes instead of running multiple Elasticsearch nodes on the same box. This &lt;a href=&quot;https://www.elastic.co/de/elasticon/2015/sf/scaling-elasticsearch-for-production-at-verizon&quot;&gt;Elastic talk&lt;/a&gt; provides a good explanation of why this is the case.&lt;/p&gt;
&lt;p&gt;Coming back to our setup, each box consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;150GB SSD&lt;sup&gt;&lt;a href=&quot;#f3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;64GB RAM&lt;/li&gt;
&lt;li&gt;32 cores&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So in total, we have approximately 2TB RAM and 1,000 cores available to serve more than 1,000 search queries per second at peak time.&lt;/p&gt;
&lt;h2&gt;Our Secret Sauce for Fast Indexing&lt;/h2&gt;
&lt;p&gt;Now that you’re somewhat familiar with SoundCloud’s indexing pipeline, in this section, I’ll share the concrete Elasticsearch tweaks we implemented that contributed to our performance improvements.&lt;/p&gt;
&lt;h3&gt;Ingredients&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Concurrent bulk API calls and large bulk size&lt;br&gt;&lt;/li&gt;
&lt;li&gt;Optimal primary shards count&lt;br&gt;&lt;/li&gt;
&lt;li&gt;Async translog setting&lt;br&gt;&lt;/li&gt;
&lt;li&gt;Turned off Elasticsearch refresh&lt;br&gt;&lt;/li&gt;
&lt;li&gt;Merging of Lucene segments&lt;br&gt;&lt;/li&gt;
&lt;li&gt;Faster replication settings&lt;br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Directions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Bulk API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Make sure that the sending part (in our case, &lt;code class=&quot;language-text&quot;&gt;shipper&lt;/code&gt;) utilizes the maximum possible combination of bulk size and concurrent requests against the Elasticsearch bulk API. To find this exact sweet spot, try to &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/guide/current/bulk.html#_how_big_is_too_big&quot;&gt;incrementally increase the batch size&lt;/a&gt; until the indexing throughput no longer improves. Since finding the exact sweet spot is a process of trial and error, rinse and repeat!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Primary Shards&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Note: The primary shard configuration is a static setting and must be specified during the index creation time.&lt;/p&gt;
&lt;p&gt;As a rule of thumb, the count of primary shards defines how well the indexing load is distributed on the available nodes.&lt;sup&gt;&lt;a href=&quot;#f4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; Before we started our spike, we had 10 primary shards distributed on 30 Elasticsearch nodes. Yes, this doesn’t sound like a configuration, because effectively, only 10 machines would be utilized and the rest would be idling. But after changing the primary shard size to 30, we saw lots of improvement in the indexing throughput.&lt;/p&gt;
&lt;p&gt;…And do not forget to turn off shard replication during the actual reindex!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Translog&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The translog in Elasticsearch is a write-ahead/action log which is needed because the actual internal Lucene commit operation (writing to disk) is an expensive operation due to it occurring synchronously &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html#index-modules-translog&quot;&gt;after every write request&lt;/a&gt;. The log stores all indexing and delete requests made to Elasticsearch. Furthermore, it also helps with recovery from hardware crashes by replaying all recent acknowledged operations that were not part of the previous Lucene commit.&lt;/p&gt;
&lt;p&gt;By default, there is a synchronous &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt; that takes place for each indexing request, and the client receives an acknowledgment afterward. But during the reindexing, we &lt;em&gt;usually&lt;/em&gt; don’t need this guarantee and could simply restart the process in the case of a hardware failure.&lt;/p&gt;
&lt;p&gt;By setting &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html#_translog_settings&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;index.translog.durability&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;async&lt;/code&gt;&lt;/a&gt;, both the &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt; and the Lucene commit will now occur in the background, thereby making the reindexing a lot faster. Remember to set the durability back to the default &lt;code class=&quot;language-text&quot;&gt;value(request)&lt;/code&gt; once the bulk indexing is done. You can also enhance the flush threshold by changing &lt;code class=&quot;language-text&quot;&gt;translog.flush_threshold_size&lt;/code&gt;. In our setup, we’re using 2GB. The default value is 512MB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Refresh Interval&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As you may know, the &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html#_disable_refresh_and_replicas_for_initial_loads&quot;&gt;refresh interval&lt;/a&gt; in Elasticsearch directly controls when an indexed document will be searchable. Since there are no searches during the reindex, you can turn the refresh process off completely by setting the &lt;code class=&quot;language-text&quot;&gt;refresh_interval&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note: The default refresh interval is one second.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segment Merging&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the primary shards have been indexed, you may want to turn on shard replication next, but before doing so, allow Elasticsearch to merge Lucene segments via &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-forcemerge.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;_forcemerge?max_num_segments&lt;/code&gt;&lt;/a&gt;. In doing so, the index will be compactified before it is replicated, i.e. saving disk space. Currently, we’re investigating the ideal merging strategy in our setup. If you are curious how Lucene’s segment merging works, you can find a great visualization of it &lt;a href=&quot;http://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now there’s only one last thing to do: Turn on shard replication! But before doing so, consider increasing the concurrent shard recoveries per node (default: 2) and the recovering network throttle (default: 20MB) by tweaking &lt;code class=&quot;language-text&quot;&gt;cluster.routing.allocation.node_concurrent_recoveries&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;indices.recovery.max_bytes_per_sec&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In our setup, we are allowing 100 concurrent shard recoveries, and the &lt;code class=&quot;language-text&quot;&gt;indices.recovery.max_bytes_per_sec&lt;/code&gt; rate is set to 2GB. Make sure you revert it to the previous value before going live!&lt;/p&gt;
&lt;h2&gt;Nutrition Facts&lt;/h2&gt;
&lt;h3&gt;Indexing Throughput&lt;/h3&gt;
&lt;h3&gt;Before&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7c78f554adcf65b3c43aa5f7de99538a/473b4/indexing_before.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.473736868434216%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABKUlEQVQoz3WR6XKDMAyEeZBOMYdPjDEBkh5JJ+//UlvJhkDb9Mc3EtJ6tZMUrdQoqwbiCaWoUTcSIUT4fkDdqjQT/+hFVaMwxkK2EqJuUT2Bha9lBUFGTdMmtgDbPpP7InQOTiu8lPRQNMn4KOpox/XcG9wnh9toYaR8GNaEVRKKZiW9Lzrn8BYsotMYrEoChpPwgZHm/HDqNC5kysZfo0sGPL9Rfz/lUKwvtLZYvCFTg/ch95/R4oP7Ps/YhHs2Za605+Ocno8wVqmcUGsWZvGJ0rAhMxOXkCsbzn5nS8uHZ6/TLNj852bDg3Dy24E9UWbVHLWH+TU6GPot14R0JS1Vqg+8/vn9m8P+zCmNQqHIcBkHIvxhGnrCr5WI/d6v8znmfhl7hM7iG/+5Cu0wmYNLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Indexing throughput before the performance tuning&quot;
        title=&quot;Indexing throughput before the performance tuning&quot;
        src=&quot;/blog/static/7c78f554adcf65b3c43aa5f7de99538a/8ff1e/indexing_before.png&quot;
        srcset=&quot;/blog/static/7c78f554adcf65b3c43aa5f7de99538a/9ec3c/indexing_before.png 200w,
/blog/static/7c78f554adcf65b3c43aa5f7de99538a/c7805/indexing_before.png 400w,
/blog/static/7c78f554adcf65b3c43aa5f7de99538a/8ff1e/indexing_before.png 800w,
/blog/static/7c78f554adcf65b3c43aa5f7de99538a/6ff5e/indexing_before.png 1200w,
/blog/static/7c78f554adcf65b3c43aa5f7de99538a/2f950/indexing_before.png 1600w,
/blog/static/7c78f554adcf65b3c43aa5f7de99538a/473b4/indexing_before.png 1999w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;After&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ef09ec4a064d6973f1ad877baff77f7e/2f950/indexing_after.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA4UlEQVQoz32RC27DIBBEuUnMZ5c1YOxUidIaJ/c/VgfjpFbdRhqtBos34EFpbY22nbanrs6ua94QSQjJOcaG/6Qi0zz4LNyEZfY8CbNzSDHGWUuYf0pdAt+zLIO/5yqYZZ0ly0fAsU6besiacoCvkbG7DFIADCu2GUEWLsJE7on94tUt+T38oywta8mC/8IFdQVoz6uvt3AziHiMMvWV31dQ4ZL8nBBfT5jTpvLym2F88UQn9PyCPyOvPT07G6U1dzSPOuXc4yG2FtQY+imGMYZzirmX5PmouPNZ0KInS3iCbwpeV5NVBr1jAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Indexing throughput after the performance tuning&quot;
        title=&quot;Indexing throughput after the performance tuning&quot;
        src=&quot;/blog/static/ef09ec4a064d6973f1ad877baff77f7e/8ff1e/indexing_after.png&quot;
        srcset=&quot;/blog/static/ef09ec4a064d6973f1ad877baff77f7e/9ec3c/indexing_after.png 200w,
/blog/static/ef09ec4a064d6973f1ad877baff77f7e/c7805/indexing_after.png 400w,
/blog/static/ef09ec4a064d6973f1ad877baff77f7e/8ff1e/indexing_after.png 800w,
/blog/static/ef09ec4a064d6973f1ad877baff77f7e/6ff5e/indexing_after.png 1200w,
/blog/static/ef09ec4a064d6973f1ad877baff77f7e/2f950/indexing_after.png 1600w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Including the long tail, the primary shard indexing takes around 30 minutes for more than 720 million documents, whereas it previously took three hours.&lt;/p&gt;
&lt;h3&gt;Search Latency&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/73e66afa598fa581f386392d5d026075/d5c82/search_latency.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63.13823163138231%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAACQ0lEQVQoz22S246bMBCGeYZdwMQYbGyDw8mQcAjkyGaXZjdZbaV2te0r9KKHq970tg/eSSpFvag0+gU/2PPrmzFshNHERQ62odDEth3bQlCW7VDGkzg9O+D/rwzGxQQ51o1l35oYE8JDFqdqVvFA+MRXqZ7qUojIJdQlPnb9CSbQzMEuqJGkWu1HMYzevOWb+/mXH92v3/23n0lZ8XyWHt/r1Tbi0nU9KLjdgYC2gy4ZDTVN5ttFe1g3D/1iXK6Om83TZv/2snh9rT99Xn/9Xo0HvRtk3fCiFLM50yVNM5ppmmQGpTwu4tmy0G3aDU29nm/G1XgaTh/G3dg9vuye306PH0/tvuvul81dV+9a3ZfVtknb3CCUqSjMdMkCSWmAEPb9wJkQzw9MG2LSs0PBcX2fXXx2fqbMtJBxBuBg0wZ6+MZEt9bkrKYNB5IkT5IsyzSAhV/NywiueqYdpzmjnk+w72HOPMm9SFIuQhUnYagiFWe5DiMFqKEpKDSDzn/VyPKirmW34E3F+oUchngc8/GuAJCexxgTkUqVSi6johj7yCGWjRFyISmMqmiW8WqIu7Va7ZLVPtuOxdNzfXiqHg758aS7Xuc6CaNARkyGjEs/4IQLuJgYQqok5brgwL+cy3kV9X0+7NuHsVtu9P1jc3xeje8Wiz7u+rjtpnUbwQbVbVjMAoP4VIZhlueQraoaFggBLQKhVEwIhd0KghA+YZcGgYTksLPAnDJhWo6BXYDPAD3nkgsJr/9StWx05Xyt627/AcKNdjm1DDAhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Search latency after the performance tuning&quot;
        title=&quot;Search latency after the performance tuning&quot;
        src=&quot;/blog/static/73e66afa598fa581f386392d5d026075/8ff1e/search_latency.png&quot;
        srcset=&quot;/blog/static/73e66afa598fa581f386392d5d026075/9ec3c/search_latency.png 200w,
/blog/static/73e66afa598fa581f386392d5d026075/c7805/search_latency.png 400w,
/blog/static/73e66afa598fa581f386392d5d026075/8ff1e/search_latency.png 800w,
/blog/static/73e66afa598fa581f386392d5d026075/d5c82/search_latency.png 803w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Our search latency for 95 percent of all requests dropped to roughly 90ms&lt;sup&gt;&lt;a href=&quot;#f5&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; after rolling out the 30 primary shards configuration.&lt;/p&gt;
&lt;p&gt;Note that the listed tweaks above were used in Elasticsearch 5.6.10. Depending on your used version of Elasticsearch, the shown tweaks might not work or could end with slightly different results.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;When we first made the above improvements, we only had two engineers working on the Search Team, and as such, the new lead time for bringing up new clusters enabled us to be much more productive: We could now start an A/B test, prepare a second one, mitigate a recent incident, and reindex a development cluster in one sprint, whereas previously we hardly had room for more than two indexing runs in the same time span!&lt;/p&gt;
&lt;p&gt;The shown performance improvements helped to cut down the reindexing time for new clusters from one week to one hour, thereby enabling the Search Team to perform more effectively. In addition, we simultaneously improved the search latency by four times for 95 percent of all search requests.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If our search problems sound interesting to you, then feel free to apply for the currently &lt;a href=&quot;https://soundcloud.com/jobs/2019-03-12-backend-engineer-f-m-d-berlin&quot;&gt;vacant backend engineer position&lt;/a&gt; on the Search Team! We would also &lt;strong&gt;love&lt;/strong&gt; to hear your stories about managing and working with Elasticsearch in production or, in general, any other search-related topic. Don’t hesitate to get in touch via &lt;a href=&quot;https://twitter.com/bashlog&quot;&gt;@bashlog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Future Work&lt;/h2&gt;
&lt;p&gt;Our work doesn’t stop here. There are, of course, more improvement areas beyond what’s detailed above. In the near future, we plan to further investigate the following topics:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;60 Primary Shards:&lt;/strong&gt;&lt;br&gt;
During a small test, we could reach up to even 1 million indexing op/s with 60 primary shards. A load test is currently pending to verify that the search latency won’t be degraded by having 60 primary shards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segment Optimization:&lt;/strong&gt;&lt;br&gt;
As explained in the segment merging section, we want to verify if having only one segment in the indexing process could eventually result in Lucene segments that are too large.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Smaller Clusters:&lt;/strong&gt;&lt;br&gt;
Currently, we are running clusters with 30 nodes. Here we want to find out the minimum number of nodes necessary that will allow us to handle the same amount of requests, in addition to having the same indexing performance and search latency for users. Smaller clusters would drastically decrease our operational efforts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimized Schema:&lt;/strong&gt;&lt;br&gt;
We also want to investigate whether our current schema could be further optimized, e.g. removing unused fields or analyzers. This would make the indexing process faster, and it could also save space.&lt;/p&gt;
&lt;h2&gt;Bonus Material&lt;/h2&gt;
&lt;h4&gt;&lt;a href=&quot;http://www.safetydifferently.com/why-do-things-go-right&quot;&gt;Identify and Enhance the Capacities That Make Things Go Right&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;So, the bonus is the simple idea above, which I thought was worth sharing with you. Instead of the usual focus on &lt;em&gt;why do things go wrong?&lt;/em&gt;, one should also ask the opposite question: &lt;em&gt;Why do things go right?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By increasing the capacity of things that go right, we can automatically lower the chance that things &lt;em&gt;go wrong&lt;/em&gt; in the first place. In terms of our situation described above, we proactively planned time to improve what was already working, and as a result, we made it a lot better.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;In the past, the Search Team at SoundCloud had high lead times for reindexing Elasticsearch clusters. Recently, we optimized the time required for this task, bringing it down from one week to one hour.&lt;/p&gt;
&lt;p&gt;This post is a follow-up to a &lt;a href=&quot;https://speakerdeck.com/qabbasi/how-to-reindex-1b-documents-in-1-hour&quot;&gt;Meetup talk&lt;/a&gt; that Rene Treffer (Production Engineer at SoundCloud) and I gave last December at the Berlin Elasticsearch Meetup on the topic of the latest improvements in search.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;f1&quot;&gt;1&lt;/a&gt;: Our average live update rate is 50 documents per second.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;f2&quot;&gt;2&lt;/a&gt;: The data that we actually index in Elasticsearch is a small subset of what is available in MySQL, and it consists of basic fields such as title, description, and user.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;f3&quot;&gt;3&lt;/a&gt;: If you are also using SSDs, make sure your I/O scheduler is configured correctly. See &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_disks&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;f4&quot;&gt;4&lt;/a&gt;: Likewise, you can increase the search throughput by increasing the replication. This, of course, makes it necessary to add additional nodes to the cluster.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;f5&quot;&gt;5&lt;/a&gt;: During peak time, the latency for the 95th quantile reaches up to 150ms.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Off-Platform Validation]]></title><description><![CDATA[Last month we launched SoundCloud Premier Distribution, which allows creators to distribute their music from SoundCloud to many other…]]></description><link>https://developers.soundcloud.com/blog/off-platform-validation</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/off-platform-validation</guid><pubDate>Wed, 06 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last month we launched SoundCloud Premier Distribution, which allows creators to distribute their music from SoundCloud to many other streaming platforms and stores. The result? Artists can now get paid for plays on other platforms just like they can on SoundCloud.&lt;/p&gt;
&lt;p&gt;For many of our users, this will be their first experience with the conventions and requirements of the music industry supply chain. The barriers to entry to this world are very different than those to a creator uploading to SoundCloud. At SoundCloud, we’re proud of how easy it is and how few clicks it takes to publish your work. In contrast, the process of getting your content onto Spotify, iTunes, or any of the other platforms fed by distributors has many more strict requirements regarding metadata and media.&lt;/p&gt;
&lt;p&gt;The aim of SoundCloud Premier Distribution is to make the path from SoundCloud upload to off-platform plays as frictionless as possible. Here we’ll look at how a system of automatic and manual validations allows users to get fast feedback as they prepare a release.&lt;/p&gt;
&lt;h2&gt;What Are These Extra Requirements?&lt;/h2&gt;
&lt;p&gt;To upload to SoundCloud, the minimum metadata required is the track title, and even that gets auto-completed based on the name of the file you upload. Easy. There’s a maximum length restriction, but that’s about all.&lt;/p&gt;
&lt;p&gt;To send content to other platforms, the title has many more strict requirements, as do other fields. There are syntactic, structural, and semantic rules that have to be observed. Some examples of these are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The metadata has a fixed character set that, in particular, does not include emojis. 😢&lt;/li&gt;
&lt;li&gt;Words in the title should be capitalized, with the exception of a list of short prepositions.&lt;/li&gt;
&lt;li&gt;Some words — such as references to physical formats or other streaming platforms — are not allowed.&lt;/li&gt;
&lt;li&gt;The artist or label name shouldn’t go in the title, as there are other fields for that.&lt;/li&gt;
&lt;li&gt;You shouldn’t specify in the title that it is a live recording or a remastering — there’s another field for that.&lt;/li&gt;
&lt;li&gt;The audio format has minimum quality requirements, as does the artwork image format.&lt;/li&gt;
&lt;li&gt;The title in the album artwork has to match the title.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are so many rules and style guides with frightening levels of detail about what will pass quality control for the various streaming services.&lt;/p&gt;
&lt;p&gt;Many of these rules are &lt;strong&gt;difficult to automate completely&lt;/strong&gt;. We can’t just block the word “live” in the title, because while “My Hit Song (Live Version)” isn’t allowed, “Live and Let Live” is fine.&lt;/p&gt;
&lt;p&gt;Not only are the rules difficult to navigate, but they are also &lt;strong&gt;spread out in time&lt;/strong&gt;. While we can check some rules in real time whenever a user makes a change on soundcloud.com, some take too long to run every time, or they require manual input from our QC team. These checks have to happen asynchronously, with results shown to the user later.&lt;/p&gt;
&lt;p&gt;What’s worse is we can also receive validation failures from streaming services much later — in some cases, even after a track has been live for a while.&lt;/p&gt;
&lt;p&gt;All that said, how do we help the user through this maze of rules that can fail immediately or even weeks later?&lt;/p&gt;
&lt;h2&gt;Automatic Checks with Supervision&lt;/h2&gt;
&lt;p&gt;We’ve built a system of validation rules that can be applied to all of the different fields of metadata. This system checks that data is present and follows the rules, and it reports errors to users. Some rules are easy and can be entirely automatic — for example, image dimensions. Some are almost impossible to check automatically — for example whether or not a track is explicit. And some would be expensive to automate and hard to make correct — for example, genre checking or parsing text from an image. Whenever a change is made to the metadata of a track or a release, it needs to be checked before the update is shipped off-platform.&lt;/p&gt;
&lt;p&gt;So we’ve settled on a mixture of rules that can be applied automatically, those that require human eyes, and those that can defer to a human if there’s doubt. Everything can be overridden manually later.&lt;/p&gt;
&lt;p&gt;The thing is, we need to cope with a lot of volume, and we only have a finite number of humans checking these releases. And while computer time is cheap, theirs is precious.&lt;/p&gt;
&lt;p&gt;As such, there are two lifecycles here that we can model separately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast automatic feedback&lt;/strong&gt; — We want to know whether each product is valid each time we look at it, and so we run the validations every time a user looks at a page. This is fine for simple validations about the presence of data or many of the rules about text fields.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asynchronous feedback&lt;/strong&gt; — There are expensive validations (e.g. manual) that we want to run at most once for each value of the content (whether text, audio, or image), and not repeatedly if it doesn’t change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consider that a release might change over time as more metadata is added or changed. Each time there’s a change, the fields that were edited should be checked by the QC team, but the team shouldn’t have to, for example, approve the same artwork every time. Unless the image changes, we should store the pass/fail result and use that whenever we want to check the validity of the release. We call that stored pass/fail result &lt;strong&gt;evidence&lt;/strong&gt; for the validity of the release.&lt;/p&gt;
&lt;p&gt;To understand these two lifecycles, we can think of a validation result as being the output and having evidence for the validity of the content as an input. When we check a release, we look for indications that the value satisfies the validation rule — either as stored evidence applicable to that value, or by actually checking the content.&lt;/p&gt;
&lt;p&gt;This distinction between slow and fast and synchronous and asynchronous implementations of validation rules has allowed us to run them a lot — every time the user looks at the page, every time we build the rendering of a release that we send to a partner, and every time the release is shown in the QC process. This means that the results are both fast and as up-to-date as possible.&lt;/p&gt;
&lt;h2&gt;Simple Patterns&lt;/h2&gt;
&lt;p&gt;Caching values so they don’t have to be recomputed is not a novel idea. Here we’re treating evidence of validity as a cached result of a validation rule. This has allowed us to model changes to data and the various stages of quality control in a way that combines these very different lifecycles. As a result, we can give fast and consistent feedback to users distributing their content, all without overwhelming our QC team.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Radical Candor: An Experience Report]]></title><description><![CDATA[Although it can be easy to know if you’ve messed up badly as a manager, it’s not always as easy to know if you’re doing a good job. In…]]></description><link>https://developers.soundcloud.com/blog/radical-candor-an-experience-report</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/radical-candor-an-experience-report</guid><pubDate>Fri, 15 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Although it can be easy to know if you’ve messed up badly as a manager, it’s not always as easy to know if you’re doing a good job. In particular, the power dynamics at play can make it hard for people on your team to feel confident letting you know what’s working well and what’s working not so well.&lt;/p&gt;
&lt;p&gt;I’ve been managing managers for a while now, and through the years, I’ve tried a few ways to help close this gap. In this article, I’m going to talk about an approach I started using in the last few years that seems to strike the best balance of getting the input managers need while still promoting a healthy culture of direct feedback.&lt;/p&gt;
&lt;h2&gt;The Approach&lt;/h2&gt;
&lt;p&gt;Back when I was working at Sky, I came across a post by Kim Scott entitled “&lt;a href=&quot;https://firstround.com/review/radical-candor-the-surprising-secret-to-being-a-good-boss/&quot;&gt;Radical Candor — The Surprising Secret to Being a Good Boss&lt;/a&gt;.” The title was intriguing enough to get me reading, but what I discovered was an approach to management that was genuinely refreshing. The following diagram is a great summary of the core concept and some of the terms she uses:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 640px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1e36bb105d9a2786ae2026815a271763/e2288/radical-candor.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.84375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAAB9iawHVC7hUCf/8QAGhAAAwADAQAAAAAAAAAAAAAAAAERAhASIf/aAAgBAQABBQJ5R9ejLBEJr//EABURAQEAAAAAAAAAAAAAAAAAADEg/9oACAEDAQE/ARj/xAAVEQEBAAAAAAAAAAAAAAAAAAAxIP/aAAgBAgEBPwFI/8QAGBAAAgMAAAAAAAAAAAAAAAAAABEQIDH/2gAIAQEABj8CQp2v/8QAGhAAAgMBAQAAAAAAAAAAAAAAAAERITEQof/aAAgBAQABPyFAKSaI1o0a/BKOK8//2gAMAwEAAgADAAAAEOvPAP/EABkRAAEFAAAAAAAAAAAAAAAAABEAARAhMf/aAAgBAwEBPxBrHElf/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAEREP/aAAgBAgEBPxBVpqH/xAAbEAEAAwADAQAAAAAAAAAAAAABABEhMUFRYf/aAAgBAQABPxAinXjGUsNOYxWXMaZfVRIIK3GW6Zf2ZLf2Cabk/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Radical Candor Chart&quot;
        title=&quot;Radical Candor Chart&quot;
        src=&quot;/blog/static/1e36bb105d9a2786ae2026815a271763/e2288/radical-candor.jpg&quot;
        srcset=&quot;/blog/static/1e36bb105d9a2786ae2026815a271763/f544b/radical-candor.jpg 200w,
/blog/static/1e36bb105d9a2786ae2026815a271763/41689/radical-candor.jpg 400w,
/blog/static/1e36bb105d9a2786ae2026815a271763/e2288/radical-candor.jpg 640w&quot;
        sizes=&quot;(max-width: 640px) 100vw, 640px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I loved the concept, especially since it challenged some of my own tendencies toward “Ruinous Empathy” — caring personally for my coworkers but failing to challenge them directly — and gave me a path to provide more meaningful support for my team.&lt;/p&gt;
&lt;p&gt;The book that followed (see &lt;a href=&quot;https://www.radicalcandor.com/&quot;&gt;radicalcandor.com&lt;/a&gt;) is a treasure trove of tools on how to put these ideas into practice, and it contains a section dedicated to the Manager Guidance concept introduced in Kim’s original post.&lt;/p&gt;
&lt;p&gt;One aspect of this concept is a skip-level meeting, which is generally defined as a meeting held between a manager’s manager and their direct reports, thereby skipping the level between them. Generally speaking, Kim’s Manager Guidance process follows these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A skip-level meeting is held specifically to ask people how their boss could become a better manager. During this meeting, notes are collected and then shared with the manager after the session.&lt;/li&gt;
&lt;li&gt;A follow-up discussion takes place between the manager and their manager to provide additional context about the feedback and answer any questions.&lt;/li&gt;
&lt;li&gt;The manager then speaks to their team about what they learned from the feedback and what changes they intend to take in addressing it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The specific topics people raise in the skip-level meeting are interesting and useful, but most importantly, we want to learn whether people feel good about the feedback culture between themselves and their manager.&lt;/p&gt;
&lt;p&gt;My first attempts in using the process at Sky revealed a lot, but for this article, I’m going to focus on how it worked here at SoundCloud, as I was able to put into practice some of what I learned from the first time around. I’ll explain more about how I prepared for the sessions, how I ran them, and how they were received by the teams and their managers. I’ll also cover some of the ways in which they could go wrong and how to address these potential issues.&lt;/p&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;First off, any manager having this process run on their behalf needs to be bought in. They should at least read Kim’s blog post, but better yet, they should read the relevant chapter in &lt;em&gt;Radical Candor&lt;/em&gt;, and ideally, the entire book! If, after all of that, they aren’t excited, then it’s better to find a different approach for their guidance.&lt;/p&gt;
&lt;p&gt;When teams are invited to a manager guidance session, I provide them with key information. An example invite might read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This session is to gather feedback to help [your manager] be a better manager. It’s based on a framework Kim Scott talks about in her book &lt;em&gt;&lt;a href=&quot;https://www.radicalcandor.com/&quot;&gt;Radical Candor&lt;/a&gt;&lt;/em&gt;, and when I used it (both for me and by me) in a previous company, I found it to be a really helpful exercise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I recommend reading the entire article here, but the TL;DR about this session can be found in the section starting “&lt;a href=&quot;https://firstround.com/review/radical-candor-the-surprising-secret-to-being-a-good-boss/&quot;&gt;Make it easier to speak truth to power&lt;/a&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;There’s no need for you to prepare anything; I’ll capture notes and ask questions to help keep the conversation on track. [xxx] and I want you to feel confident about being completely open.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Running the Session&lt;/h2&gt;
&lt;h3&gt;Structure and Process&lt;/h3&gt;
&lt;p&gt;In the &lt;em&gt;Radical Candor&lt;/em&gt; book, Kim recommends a very specific structure for the meetings, which I mostly followed, as it works well. Here’s how I interpreted it for my use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short introduction (c. 5 minutes)&lt;/li&gt;
&lt;li&gt;Open discussion (c. 40 minutes)&lt;/li&gt;
&lt;li&gt;Reviewing notes together (c. 15 minutes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the moderator, I capture notes and I display them on a screen behind me so people can see my terrible note-taking skills in real time and tell me when I capture their points inaccurately.&lt;/p&gt;
&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;On rereading Kim’s book recently, I realized I had forgotten to apply one piece of advice, which was to get the managers to explain the purpose of the meeting to their teams ahead of time. This meant that a very small percentage of people attending the meeting knew what to expect.&lt;/p&gt;
&lt;p&gt;I was anticipating this, so I included time at the top of the session to run through the purpose and set some ground rules. Some of the key points I called out are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is about helping the manager learn and grow.&lt;/li&gt;
&lt;li&gt;The session shouldn’t focus exclusively on either positive or negative points, as it’s all about getting balanced feedback.&lt;/li&gt;
&lt;li&gt;It’s also about checking in on the flow of feedback between the team and their manager.&lt;/li&gt;
&lt;li&gt;Everyone’s voice needs to be heard.&lt;/li&gt;
&lt;li&gt;The notes are not attributed to individuals, but people still have to be comfortable with them being shared directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Topics&lt;/h3&gt;
&lt;p&gt;I wanted to have some consistency in approach across the sessions (I have five managers reporting to me), so I came up with a loose structure as follows: For Me, For My Team, For the Company. In each section, I explained the overall topic and then used simple starter questions to get the conversation going.&lt;/p&gt;
&lt;p&gt;I tend to go with somewhat neutral and uncontroversial questions so as not to push the conversation in a particular direction, but rather see where it gets taken by the group, and I then dig deeper when something interesting emerges. However, sometimes if I know there’s been a particular theme that’s affected a team (e.g. confusing priorities or a visible conflict), I might ask specific questions about how their manager handled it.&lt;/p&gt;
&lt;p&gt;The questions you ask can reveal a lot about what you expect from managers on your team, and that’s not a bad thing! At SoundCloud, we recently completed an exercise to define expectations for Engineering Managers, and I expect I’ll start using some of these expectations as a basis for sessions in the future.&lt;/p&gt;
&lt;h4&gt;For Me&lt;/h4&gt;
&lt;p&gt;This section gives the attendees the opportunity to talk about how their manager supports their specific needs as an individual. Some example starter questions I used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How often do you get together 1 on 1? Is that the right cadence for you?&lt;/li&gt;
&lt;li&gt;Who controls the agenda for those sessions? Do you get to talk about everything you want to? Do you ever talk about your career plans/aspirations?&lt;/li&gt;
&lt;li&gt;How available is your manager to you outside of those sessions?&lt;/li&gt;
&lt;li&gt;Does your manager actively seek out opportunities for you that align with your aspirations?&lt;/li&gt;
&lt;li&gt;Does your manager ask you for feedback? When did you last give them critical feedback? How did they take it?&lt;/li&gt;
&lt;li&gt;Do you feel that you’re learning from your manager? Do you have any specifics?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;For My Team&lt;/h4&gt;
&lt;p&gt;This section is all about the health of the team as a unit and the manager’s contribution to that. Some example questions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do your team meetings run smoothly? E.g. clear purpose, kept on track, actions logged, everyone is heard.&lt;/li&gt;
&lt;li&gt;Is collaboration encouraged? Does your manager seek to avoid silo-ing of knowledge/skills?&lt;/li&gt;
&lt;li&gt;Do you have social events as a team that everyone can engage with?&lt;/li&gt;
&lt;li&gt;Do conflicts within the team get resolved?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;For the Company&lt;/h4&gt;
&lt;p&gt;This section is about how the manager contributes to the success of the relationship between their team and the rest of the business. Here is an example of some of the questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is it clear the role your team plays in the company’s success? Do you think you’re working on things that contribute to that?&lt;/li&gt;
&lt;li&gt;Do you feel that the successes of your team are known about in the rest of the company? Are you well represented in key meetings, e.g. All Hands?&lt;/li&gt;
&lt;li&gt;Does your manager advocate for you as a team when you raise topics that need assistance from outside of the team?&lt;/li&gt;
&lt;li&gt;Do you get clear information about changes to company priorities or policies from your manager? Do they manage to answer all your questions, or at least point you in the right direction?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;After the Session&lt;/h2&gt;
&lt;p&gt;Once the session is complete, I plan a note review session with each manager, and I share the notes with them the evening before. This gives them enough time to digest the information and have questions prepared, but not so long that they stew on any negative points.&lt;/p&gt;
&lt;p&gt;The session itself is around 30 minutes long, and I set expectations around the next steps, which start with the manager summarizing the key learnings from the notes and what changes they plan to make. They should then talk through these ideas with their team to reinforce the message that the feedback is being heard and acted upon.&lt;/p&gt;
&lt;h2&gt;How Did It Go?&lt;/h2&gt;
&lt;h3&gt;How Was It for the Teams?&lt;/h3&gt;
&lt;p&gt;Probably the most common piece of feedback I received was along the lines of: “That wasn’t as bad as I was expecting!” It can be stressful to think about giving feedback to your manager, so a process that normalizes and encourages it can bring relief to many people.&lt;/p&gt;
&lt;p&gt;Here is an explicit piece of written feedback I received:&lt;/p&gt;
&lt;p&gt;“…I found that session really really nice. I was very apprehensive going into it, but in the end I think it was one of the most comfortable feedback-collecting formats I’ve been a part of. It was so nice to be able to talk and have someone else write it down, to be able to hear what other people had to say and triangulate, and to frame the whole thing as guidance and direction rather than feedback…”&lt;/p&gt;
&lt;p&gt;The other realization I often see dawning on people during the sessions is how difficult it is for a manager to tailor their approach to a group of diverse individuals.&lt;/p&gt;
&lt;h3&gt;What Feedback Was Given? How Did the Managers React?&lt;/h3&gt;
&lt;p&gt;Thankfully, there were no major surprises in the feedback for any of the managers in my team, but there were plenty of items they were able to take forward and work with. I had universally positive responses from them, and when we talked about the approach with other managers outside of my group, it was great to hear them advocating for the process and the value it brought them.&lt;/p&gt;
&lt;p&gt;For some managers, people on their teams were asking for more critical feedback on their work, as they wanted to understand more deeply how they could grow. Other managers who had focused attention on that already heard that they should also apply the same rigor to giving positive feedback, which is a theme in Kim’s book too.&lt;/p&gt;
&lt;h3&gt;What Did I Learn?&lt;/h3&gt;
&lt;p&gt;One piece of advice I didn’t follow from Kim’s book, as mentioned above, is to get the managers to explain the process to their team in advance, which would definitely have saved some time in the sessions. Some people weren’t sure whether their manager would be there and even whether they would be on their own in the session.&lt;/p&gt;
&lt;p&gt;The other thing that is clear is the time in the feedback sessions passes very quickly, so it’s important to be very structured about moving through the topics and try to avoid getting stuck on any particular point for too long.&lt;/p&gt;
&lt;h2&gt;What Could Go Wrong and How to Address It&lt;/h2&gt;
&lt;h3&gt;Gripe Session&lt;/h3&gt;
&lt;p&gt;If one or more people in the group choose to use the session to moan about their boss, it can be quite detrimental to team morale. Also, as Kim points out in her book, there’s a danger that the moderator can appear to presume that the boss is guilty, or — just as bad — try to defend them against the criticism. Having said that, the complaints are a window into a problem within the team that benefits from being revealed.&lt;/p&gt;
&lt;p&gt;The moderator can steer the conversation to avoid getting stuck on a topic that might only have relevance for a small subset of the group, e.g. “[xxx], does this affect you as much as it affects [yyy]?”&lt;/p&gt;
&lt;h3&gt;No One Talks&lt;/h3&gt;
&lt;p&gt;One of the reasons I like to be prepared with a set of questions is that sometimes it can be hard to get the feedback flowing with certain teams. I tend to think that’s not a great sign about the feedback culture, but it can also just mean people are reserved and need help exploring the topic. This is also why I like to start with neutral/factual questions that everyone can feel good about answering, like “How often do you have 1:1s?”&lt;/p&gt;
&lt;h3&gt;Only a Subset of People Talk&lt;/h3&gt;
&lt;p&gt;Often, teams contain natural leaders aside from their manager, which can lead to interesting dynamics wherein those people can try to steer the conversation on the moderator’s behalf. Reminding the group that the guidance needs to be based on feedback from the whole team can help keep these people from dominating the conversation.&lt;/p&gt;
&lt;h3&gt;The Group Is Too Big/Too Small&lt;/h3&gt;
&lt;p&gt;So far at SoundCloud, I’ve run this process for five managers, and the groups attending the sessions have varied in size from three people to seven people. That’s probably a good range, as with only two people, it’s very obvious when collecting the feedback that it’s going to be directly attributable. I would consider 1:1 interviews in such a case.&lt;/p&gt;
&lt;p&gt;With a larger group, it’s more difficult to manage the collection of the feedback and make sure every voice is heard. Running multiple sessions to cover the full group is a good alternative in this case. This can happen naturally if the team structure already implies a way to split the group — for example, backend and frontend engineers.&lt;/p&gt;
&lt;h3&gt;The Process Blocks Direct Feedback&lt;/h3&gt;
&lt;p&gt;One risk that a previous team member of mine called out is the possibility that people lean on this process as a way to avoid giving direct feedback to their manager. This is valid, and it’s one of the reasons why Kim recommends not running the process too frequently. In the &lt;em&gt;Radical Candor&lt;/em&gt; book, she suggests once a year, but I personally think twice a year will be fine for us, since we have rather dynamic teams and the managers on my team are keen to have more frequent check-ins.&lt;/p&gt;
&lt;p&gt;I haven’t personally run a follow-up session with a team before, but a key part will be to discuss how successful the manager’s attempts to address the previous feedback have been. Additionally, I would want to find out if there is any change in how confident people feel about giving direct feedback to their manager.&lt;/p&gt;
&lt;h3&gt;Some People Can’t Make It&lt;/h3&gt;
&lt;p&gt;It can be difficult to schedule the sessions so everyone can make it along, and there are always going to be situations, like sick days, that mean the whole team can’t get together at the same time. When this has happened for sessions that I’ve run, I have shared the notes with the people who couldn’t make it and asked them if they had anything to add. If they did, I asked them if they either wanted to provide those as written notes, or if we should meet face to face to discuss. I also make sure this is all completed ahead of the review session with the manager.&lt;/p&gt;
&lt;h3&gt;Bad Notes&lt;/h3&gt;
&lt;p&gt;The notes are a key output of the process, so the time taken during the session to review them is critical. Whenever I’ve run these sessions, we’ve found ambiguities or confusing language that we’ve been able to correct as a group. It really makes a big difference to how much the manager can get from the process if they can refer back to these notes and get a clear reminder of what they need to address.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A few years ago, Google published some amazing research on the key dynamics that set successful teams apart from other teams at Google. By far, the most important element of this was &lt;a href=&quot;https://medium.com/@marcvollebregt/why-psychological-safety-is-essential-for-your-teams-success-fa9d43903a39&quot;&gt;Psychological Safety&lt;/a&gt;, which they defined as: “Team members feel safe to take risks and be vulnerable in front of each other.”&lt;/p&gt;
&lt;p&gt;A healthy feedback culture is fundamental to establishing this Psychological Safety, and my experience with using Kim Scott’s Manager Guidance approach is that it can really make a positive difference in that culture. If you’re a manager, why not ask your manager to run this process for you? I’m really looking forward to hearing the feedback that comes from mine!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Garbage Collection in Redux Applications]]></title><description><![CDATA[The SoundCloud Xbox application is a regular React Redux application that runs in a native web view wrapper on Microsoft’s UWP framework. In…]]></description><link>https://developers.soundcloud.com/blog/garbage-collection-in-redux-applications</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/garbage-collection-in-redux-applications</guid><pubDate>Thu, 24 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The SoundCloud &lt;a href=&quot;https://blog.soundcloud.com/2017/05/31/get-in-the-game/&quot;&gt;Xbox application&lt;/a&gt; is a regular React Redux application that runs in a native web view wrapper on &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/uwp/xbox-apps/&quot;&gt;Microsoft’s UWP framework&lt;/a&gt;. In order to allow users to play music in the background, we also ship a native C++ playback layer with the app. A thin API wrapper allows for communication between the web layer and the native layer.&lt;/p&gt;
&lt;p&gt;Background audio is not the only reason for us to ship a native player; we also have to make sure that our application doesn’t use more than &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/uwp/xbox-apps/system-resource-allocation&quot;&gt;128 MB&lt;/a&gt; when put into background mode. This is because applications with more than 128 MB can get killed by the Xbox application scheduler when it notices that the system or a game needs more memory. Our application shares the 128 MB with the web browser process, so the actual amount of memory someone is allowed to use is much lower than 128 MB.&lt;/p&gt;
&lt;p&gt;The native player we’re using is the same one used in our mobile applications, which means it’s optimized for low memory consumption. Our initial tests, which we ran before we released the application, showed a low-enough memory usage of the application, and we were confident that we would not hit the maximum memory limit anytime soon.&lt;/p&gt;
&lt;h2&gt;The Memory Leak That Was Just a Cache&lt;/h2&gt;
&lt;p&gt;When we ran more memory tests later on, we noticed that our memory consumption for sessions with a use time of more than one hour was steadily growing and never shrinking. As a result, our hunt for a memory leak began. We quickly identified our Redux store as the part of our application that just kept growing, which makes complete sense, since Redux does not define explicit ways to remove parts of the state in order to save memory.&lt;/p&gt;
&lt;p&gt;Just to be clear, I’m not blaming Redux here for clogging up our users’ memory! Memory consumption should not be your concern when you are building a web application, unless you have to work in an environment that is enforcing strict memory limits (like the Xbox application scheduler) or you are building an application that users are spending a lot of time in without reloading the page or closing the app (e.g. in the case of Electron apps).&lt;/p&gt;
&lt;h2&gt;Normalized State&lt;/h2&gt;
&lt;p&gt;Our Redux store is normalized, which means that we organize incoming data into its different type entities instead of keeping nested structures. Working with nested structures can be very tricky — e.g. when you want to propagate changes of an entity. Let’s look at an example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;playlists&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;tracks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t1&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t2&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;tracks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t1&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t3&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above code represents a user’s playlists. They have two playlists (&lt;code class=&quot;language-text&quot;&gt;p1&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;p2&lt;/code&gt;) that each have two tracks. &lt;code class=&quot;language-text&quot;&gt;p1&lt;/code&gt; contains tracks &lt;code class=&quot;language-text&quot;&gt;t1&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;t2&lt;/code&gt;, and &lt;code class=&quot;language-text&quot;&gt;p2&lt;/code&gt; contains tracks &lt;code class=&quot;language-text&quot;&gt;t1&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;t3&lt;/code&gt;. If the user now wants to update the title of track &lt;code class=&quot;language-text&quot;&gt;t1&lt;/code&gt;, we have to iterate over all playlists and all tracks in those playlists, because track &lt;code class=&quot;language-text&quot;&gt;t1&lt;/code&gt; might be in other playlists as well. This problem becomes even more complicated if you have other collections of tracks in your Redux store (e.g. a user’s liked tracks), because then you will have to iterate over all track collections in your store to make sure you’re updating the track in all places:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;entities&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;playlists&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;p1&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;p2&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;tracks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;t1&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;t2&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;t3&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A normalized state like the one above allows for better change propagation because it a) removes duplicates, so you only ever have to change a single object, and b) merges entities from different requests into a single “entities” store so that you don’t have to worry about updating tracks from other collections. In this case, we only have to update the title of the object at &lt;code class=&quot;language-text&quot;&gt;entities.tracks.t1&lt;/code&gt; and it will propagate correctly throughout our entire application.&lt;/p&gt;
&lt;p&gt;Another great property of normalized state is that it becomes trivial to check if you already have a locally cached instance of, for example, a track you want to render. This can decrease the number of requests your application makes by a lot:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;entities&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;playlists&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;p1&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;p2&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;tracks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;playlists&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;p1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the example above, you can see that our playlist representation now only consists of an array of IDs that “point” to playlist entities.&lt;/p&gt;
&lt;p&gt;If you are interested in learning more about normalization and how to add it to your application, I recommend checking out &lt;a href=&quot;https://github.com/paularmstrong/normalizr&quot;&gt;normalizr&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Garbage Collection in JavaScript&lt;/h2&gt;
&lt;p&gt;In the case of our problem, with every request we made, we were increasing the number of objects in our entities store, and we never removed objects, even if they were no longer needed. Usually in JavaScript, your data gets garbage collected once you remove all references to it (e.g. &lt;code class=&quot;language-text&quot;&gt;myState = null&lt;/code&gt;). In the case where all your data is encapsulated in your React components, garbage collection happens automatically when React unmounts your component. However, when you use Redux, the Redux store will always have a reference to all your data, so it will never get released.&lt;/p&gt;
&lt;p&gt;Another way to free memory in JavaScript is to just reload the page every now and then when you detect that the session is already very long or when a lot of objects have accumulated in your entities store. Of course, this requires that your app be able to reload quickly so that users don’t get frustrated with your site’s performance. Additionally, you should make sure your users are not playing any media, because you really don’t want to interrupt them.&lt;/p&gt;
&lt;p&gt;For our use case, we wanted to save memory for a background-listening experience, so we were not able to just reload the page. We came to the conclusion that we had to build a garbage collector on top of our Redux store to free memory efficiently. However, this is not the first time we’ve had to build a garbage collection algorithm into one of our applications. The main website at &lt;a href=&quot;https://soundcloud.com&quot;&gt;soundcloud.com&lt;/a&gt; has had a garbage collection system built into its model layer from the very beginning. But since the main site is built with Backbone.js, we could not reuse the garbage collecting implementation for the Xbox application.&lt;/p&gt;
&lt;h2&gt;Tracing Garbage Collection&lt;/h2&gt;
&lt;p&gt;When we looked at the different approaches to garbage collection, we saw a striking similarity between how &lt;a href=&quot;https://en.wikipedia.org/wiki/Tracing_garbage_collection&quot;&gt;tracing garbage collection&lt;/a&gt; works and the way Redux works in general.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/4/4a/Animation_of_the_Naive_Mark_and_Sweep_Garbage_Collector_Algorithm.gif&quot; alt=&quot;Mark and sweep in a tracing garbage collector&quot;&gt;&lt;/p&gt;
&lt;p&gt;A tracing garbage collector works in two steps (mark and sweep):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mark — This marks objects that still have references.&lt;/li&gt;
&lt;li&gt;Sweep — This scans all objects in memory and removes all the ones that are not marked.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we were to implement a garbage collector in Redux this way, we would never be able to remove objects, because there would always be references to all objects. In order to make this concept work, we needed to be able to remove references, and as such, find a way to determine which parts of the state were no longer needed. If we could identify unused state, we could remove the state and all its referenced entities.&lt;/p&gt;
&lt;p&gt;Redux provides, by design, all the tools necessary to identify used and unused state. Each component’s selector function (&lt;code class=&quot;language-text&quot;&gt;mapStateToProps&lt;/code&gt;) tells you which part of the state it needs, so we could use those to keep track of state that was no longer used. The only problem is that we didn’t have a list of all selectors currently onscreen, but that’s exactly where our garbage collector library comes in.&lt;/p&gt;
&lt;h2&gt;Our Redux Garbage Collector&lt;/h2&gt;
&lt;p&gt;The first thing our library does is keep track of selector functions that are currently onscreen. We do this by wrapping our component with a higher-order component that mimics React Redux’s &lt;code class=&quot;language-text&quot;&gt;connect&lt;/code&gt; API:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;mapStateToProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapStateToProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;componentDidMount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Register onscreen selector&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;registerSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapStateToProps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;componentWillUnmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Remove onscreen selector&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;removeSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(I left the other parameters of &lt;code class=&quot;language-text&quot;&gt;connect&lt;/code&gt; out to shorten the example code.)&lt;/p&gt;
&lt;p&gt;We are using &lt;a href=&quot;https://github.com/acdlite/recompose&quot;&gt;Recompose&lt;/a&gt; to create a composed component that a) connects to the Redux store, and b) registers and removes the selector when the component mounts/unmounts:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; selectors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;selectorFn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  selectors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; selectorFn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; instance &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;code class=&quot;language-text&quot;&gt;registerSelector&lt;/code&gt;, we add the selector to our list of onscreen selectors, along with a reference to the passed props and a reference to the component’s instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  selectors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; selectors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We need the reference to the instance of the component because we use it to remove a selector from the list. It’s important to mention that we need to check against the reference of the component and not against a reference of the selector itself. This is because we want to prevent removing a selector of the component that is going to unmount, regardless of the selector function. Another component — one that is still onscreen — might use the same selector function (by reference), but we want to keep its selector function. If we were to filter by the reference of the component’s selector function, we’d run the risk of removing selectors we still wanted to keep around.&lt;/p&gt;
&lt;p&gt;Right now, we are keeping track of selectors of components that are onscreen. We can use this information to calculate the part of our component’s state that we can remove, but first we have to change our selectors slightly so that we can make sense of the data they return.&lt;/p&gt;
&lt;p&gt;As described before, the bulk of our data is organized in the &lt;code class=&quot;language-text&quot;&gt;entities&lt;/code&gt; part of our Redux store, which is the part of the store we should remove unused objects from. All other parts of the store are only ID references or arrays of ID references, and they are not consuming that much data, so we can keep them.&lt;/p&gt;
&lt;p&gt;However, one problem we’re facing now is that selector functions can return arbitrary shapes of data, and they might aggregate data in a way that does not represent the pure data of the Redux store. One example would be a component that renders the title of a track:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;store&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trackId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;entities&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;trackId&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result of that function would be something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Redbone&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just looking at this result, the garbage collector will not be able to determine which parts of the state are used to compute the &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;. In order to make sense of a selector’s result, we need it to somehow return its dependencies in addition to its regular result. This is why all garbage collectable selectors have to return data in the following shape:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Redbone&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;entity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;track&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t1&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The data returned in &lt;code class=&quot;language-text&quot;&gt;dependencies&lt;/code&gt; gives us clear instructions on which type of entity and which instance of this entity this selector depends on. Now, returning this extra metadata from all of your components is pretty tedious, which is why we mainly use these selectors in specialized container components that abstract this extra overhead away for us:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WithTrack&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;trackId&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  (track) =&gt; &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TrackComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WithTrack&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the above example, &lt;code class=&quot;language-text&quot;&gt;WithTrack&lt;/code&gt; is connected to the Redux store, and it makes sure to return the proper metadata. This removes a lot of the extra overhead created by the garbage collection library.&lt;/p&gt;
&lt;h2&gt;Taking Out the Garbage&lt;/h2&gt;
&lt;p&gt;The actual garbage collection is initiated by a regular Redux action. Let’s call it the &lt;code class=&quot;language-text&quot;&gt;sweep&lt;/code&gt; action since we are now in the sweep phase of the original algorithm:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SWEEP_ACTION_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sweep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;selectors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;combinedReducers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;sweep&lt;/code&gt; action is “intercepted” in our top-level reducer, which passes the action to our sweep reducer:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sweep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;oldState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; selectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; selectors
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dependencies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        dependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dependencies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;newState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;entities&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oldState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;entities&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;oldState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    entities
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are essentially three things happening in our sweep reducer.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We create a list of all dependencies by iterating over and evaluating all selectors. Here we make use of the stored props as well, because we need to pass them to each selector function in order to get the correct dependencies. When we’re done, we end up with an array that contains the dependencies of all components that are onscreen right now.&lt;/li&gt;
&lt;li&gt;We start off with an empty object and then copy over each required entity from the &lt;code class=&quot;language-text&quot;&gt;oldState&lt;/code&gt; to the &lt;code class=&quot;language-text&quot;&gt;newState&lt;/code&gt; by iterating over the dependencies. The result of this is a fresh entities object that only consists of entities that are required by mounted components. This essentially performs the &lt;code class=&quot;language-text&quot;&gt;sweep&lt;/code&gt; we know from the algorithm, but instead of removing objects from the state, we actually started with a clean state and then added objects to it. Instead of &lt;code class=&quot;language-text&quot;&gt;mark &amp;amp; sweep&lt;/code&gt;, we basically execute a &lt;code class=&quot;language-text&quot;&gt;mark &amp;amp; keep&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We return the garbage collected state with the slimmed down &lt;code class=&quot;language-text&quot;&gt;entities&lt;/code&gt; object. Our store now only contains the objects that are necessary to render the current screen.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Determining when is best to start the garbage collection depends on the nature of your application. Some applications might just do it at a certain interval, some might want to check if they are using a lot of memory, and others might just execute every time the user is moved to a different major screen.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;We have not observed a slowdown of our application due to our garbage collection process. Our initial thoughts were that components might rerender after garbage collection, but because the state references in the components are the same before and after garbage collection, components do not rerender. The only thing that differentiates garbage collection from any other Redux action is that it will evaluate all the selector functions on top of all reducers. If you keep your &lt;a href=&quot;https://egghead.io/lessons/react-redux-pure-and-impure-functions&quot;&gt;selector functions pure&lt;/a&gt;, you should not see a performance hit.&lt;/p&gt;
&lt;p&gt;You might be thinking to yourself: “Wow, this would really make my application so much more efficient. I will add it right away!!!” Please, before you consider adding a garbage collection process to your application, check if you actually have problems with too-high memory usage. I would argue that 99 percent of Redux applications don’t require a custom garbage collection process, as they’re either too shortlived or they don’t have strict memory restrictions.&lt;/p&gt;
&lt;p&gt;This system is working very well for us and we will keep using it on new applications when needed. We would love to know what you think about our approach if you have dealt with similar problems :)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Integration Testing for Memory Leaks]]></title><description><![CDATA[We write a lot of unit tests while working on the SoundCloud iOS application. Unit tests are obviously great. They’re short, they’re…]]></description><link>https://developers.soundcloud.com/blog/integration-testing-for-memory-leaks</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/integration-testing-for-memory-leaks</guid><pubDate>Fri, 23 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We write a lot of unit tests while working on the SoundCloud iOS application. Unit tests are obviously great. They’re short, they’re (hopefully) easy to read, and they give us confidence that the code we ship works as expected. But unit tests — as their name suggests — only tend to cover a single unit of code, most often a function or class. So how do we catch the bugs that exist in the interactions between classes — bugs like memory leaks?&lt;/p&gt;
&lt;h2&gt;Memory Leaks&lt;/h2&gt;
&lt;p&gt;Memory leaks in Swift can be tricky to catch. There’s the more obvious case of a non-weak delegate reference, but then there are those that are slightly more difficult to spot. Is it obvious, for example, that the following code could contain a memory leak?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UseCase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;weak&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UseCaseDelegate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Service&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; service
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;makeRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handleResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ServiceResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// some business logic and then...&lt;/span&gt;
        delegate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;operationDidComplete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since the &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt; is injected, there are no guarantees about how it behaves. By passing in the private &lt;code class=&quot;language-text&quot;&gt;handleResponse&lt;/code&gt; function, which, although it isn’t necessarily immediately obvious, captures &lt;code class=&quot;language-text&quot;&gt;self&lt;/code&gt;, we are providing the &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt; with a strong reference to the &lt;code class=&quot;language-text&quot;&gt;UseCase&lt;/code&gt;. If the &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt; chooses to hold that reference — and we have no guarantee that it won’t — then we have a memory leak. But it’s not obvious from scanning the code that this could happen.&lt;/p&gt;
&lt;p&gt;There’s already &lt;a href=&quot;https://www.swiftbysundell.com/posts/using-unit-tests-to-identify-avoid-memory-leaks-in-swift&quot;&gt;a great post by John Sundell&lt;/a&gt; on using unit tests to catch memory leaks in a single class. But with examples like the one above, which are so easy to miss, it’s not always clear that you need to write such a unit test. (We’re certainly not speaking from experience here. 😬)&lt;/p&gt;
&lt;p&gt;Moreover, one of the signs of a good test suite is achieving the most valuable amount of coverage with as few – short! it doesn’t count if you put all your asserts in one giant test – tests as possible.&lt;/p&gt;
&lt;p&gt;As Guilherme wrote in &lt;a href=&quot;https://developers.soundcloud.com/blog/how-we-develop-new-features-using-offsites-and-clean-architecture&quot;&gt;his recent post&lt;/a&gt;, new features in the SoundCloud iOS application are written following “clean architectural patterns” — most commonly some variation of VIPER. Most of these VIPER modules are constructed using what we call a &lt;code class=&quot;language-text&quot;&gt;ModuleFactory&lt;/code&gt;. This &lt;code class=&quot;language-text&quot;&gt;ModuleFactory&lt;/code&gt; takes some inputs — injected dependencies and configuration — and produces a &lt;code class=&quot;language-text&quot;&gt;UIViewController&lt;/code&gt;, which is already hooked up to the rest of the module and can be put onto the navigation stack.&lt;/p&gt;
&lt;p&gt;Within a given VIPER module, there can be multiple delegates, observers, and escaping closures, each of which has the potential to cause the entire screen to stick around in memory after it’s been removed from the navigation stack. As this happens, the memory footprint will grow, and the operating system may well decide to terminate the application.&lt;/p&gt;
&lt;p&gt;So is it possible to cover as many of these potential leaks as we can while writing as few unit tests as we can? If not, this entire setup has been a huge waste of time.&lt;/p&gt;
&lt;h2&gt;Integration Tests&lt;/h2&gt;
&lt;p&gt;The answer, as the title of this post might have suggested already, is yes. And we do this with integration testing. The goal of an integration test is to check how objects within a group interact with each other. Certainly our VIPER modules are groups of objects, and memory leaks are one form of interaction we definitely want to avoid.&lt;/p&gt;
&lt;p&gt;Our plan is simple: We’re going to use our &lt;code class=&quot;language-text&quot;&gt;ModuleFactory&lt;/code&gt; to instantiate the entire VIPER module. We’ll then drop the reference to the &lt;code class=&quot;language-text&quot;&gt;UIViewController&lt;/code&gt; and make sure that all of the important parts of the module are destroyed alongside it.&lt;/p&gt;
&lt;p&gt;The first problem we have is that, by design, we can’t easily access any part of our VIPER module aside from the &lt;code class=&quot;language-text&quot;&gt;UIViewController&lt;/code&gt;. The only &lt;code class=&quot;language-text&quot;&gt;public&lt;/code&gt; function on our &lt;code class=&quot;language-text&quot;&gt;ModuleFactory&lt;/code&gt; is &lt;code class=&quot;language-text&quot;&gt;func make() -&amp;gt; UIViewController&lt;/code&gt;. But what if we added another entry point just for our tests? This new method would be declared &lt;code class=&quot;language-text&quot;&gt;internal&lt;/code&gt;, so we’d only be able to access it by &lt;code class=&quot;language-text&quot;&gt;@testable import&lt;/code&gt;-ing the framework our &lt;code class=&quot;language-text&quot;&gt;ModuleFactory&lt;/code&gt; lives in. It would return references to all of the most important parts of the VIPER module, which we could then hold weak references to in our test. That ends up looking like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModuleFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Some properties &amp;amp; init code, and then...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;makeAndExpose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;view
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ModuleComponents&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        view&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        presenter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Presenter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;Interactor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Interactor&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeAndExpose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ModuleComponents&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Set up code, and then...&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          view&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; viewController&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          presenter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; presenter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          interactor&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; interactor
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This solves the problem of not being able to directly access these objects. Obviously, this might not be perfect, but it suits our needs, so let’s move on to actually writing the test. Something like the following should work:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModuleMemoryLeakTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We need a strong reference to the view. Otherwise, the entire&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// module will be destroyed immediately.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; view&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// However, we want to hold weak references to the&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// other parts of the stack, so as to mimic the behavior&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// of UIKit presenting our UIViewController onscreen.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;weak&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; presenter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Presenter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;weak&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; interactor&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Interactor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// In the setUp method, we instantiate the ModuleFactory and&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// call our makeAndExpose method. We need to make sure we&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// don&apos;t accidentally hold on to the returned ModuleComponents&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// tuple, as it contains strong references to all parts of the stack.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Doing so would clearly interfere negatively with the test.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; moduleFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ModuleFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* mocked dependencies &amp;amp; config */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; components &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; moduleFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;makeAndExpose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; components&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;view
        presenter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; components&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;presenter
        interactor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; components&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interactor
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// If the test passes, this tearDown step won&apos;t actually be necessary,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// but we should definitely be covered in case the test fails, in order to stop&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// the tests themselves from leaking memory.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tearDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
        presenter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
        interactor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tearDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_module_doesNotLeakMemory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// We start by ensuring all of the references are non-nil.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// This is required to diagnose false positives, e.g.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// if we failed to correctly assign the variables in the setUp stage.&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;XCTAssertNotNil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;presenter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;XCTAssertNotNil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interactor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Now we remove our strong reference to the view.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// If everything is set up correctly, this is the only&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// strong reference to the entire stack, so everything&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// else should disappear alongside it.&lt;/span&gt;
        view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Finally, we check that the weak references to the&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Presenter and Interactor instances are now nil.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// This ensures that these components, and any of their&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// own subcomponents, are free of memory leaks.&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;XCTAssertNil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;presenter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;XCTAssertNil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interactor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So there we have it, a simple way to catch memory leaks throughout an entire VIPER module. It’s by no means perfect, and it requires some custom work for each new module we want to test, but it’s certainly a lot less work than writing individual unit tests for every possible memory leak. It also helps catch memory leaks you might not even realize are possible. In fact, after writing a few of these tests, we found we had one that we just couldn’t make pass, and after some investigation, we discovered a small memory leak in the module. (It bears repeating: 😬.)&lt;/p&gt;
&lt;p&gt;This also gives us a starting point for writing a more general suite of integration tests for the module. After all, if we simply keep a strong reference to the &lt;code class=&quot;language-text&quot;&gt;Presenter&lt;/code&gt; and replace the &lt;code class=&quot;language-text&quot;&gt;UIViewController&lt;/code&gt; with a mock, then we can fake user input by calling methods on the presenter and make assertions on output by checking the mocked &lt;code class=&quot;language-text&quot;&gt;View&lt;/code&gt;. Maybe we’ll have more on that in a future post.&lt;/p&gt;
&lt;p&gt;For now, let’s all celebrate this step toward a memory-leak-free future. 🙌&lt;/p&gt;
&lt;p&gt;(n.b. This post was written while listening to &lt;em&gt;Whatever You Love, You Are&lt;/em&gt; by Dirty Three, &lt;a href=&quot;https://soundcloud.com/dirty-three/sets/whatever-you-love-you-are-1&quot;&gt;which you can listen to on SoundCloud&lt;/a&gt;.)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A Pragmatic Approach to Tech Debt Reduction]]></title><description><![CDATA[Almost every company accumulates tech debt as time goes on. Tight deadlines, changing requirements, scaling issues, poor or short-sighted system designs, knowledge silos, inconsistent coding practices, turnover of key staff — these things all happen and can contribute to tech debt. So what can be done about it once it’s there?]]></description><link>https://developers.soundcloud.com/blog/a-pragmatic-approach-to-tech-debt-reduction</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/a-pragmatic-approach-to-tech-debt-reduction</guid><pubDate>Fri, 23 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Almost every company accumulates tech debt as time goes on. Tight deadlines, changing requirements, scaling issues, poor or short-sighted system designs, knowledge silos, inconsistent coding practices, turnover of key staff — these things all happen and can contribute to tech debt. So what can be done about it once it’s there?&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;My SoundCloud team, the Content Team, looks after a large number of legacy systems, along with a number of large systems that are difficult to understand and work with. Topics such as “Tech debt constantly increases development time” and “We have too much tech debt” come up frequently at our team retrospectives.&lt;/p&gt;
&lt;p&gt;We have just completed a project that was almost exclusively about tech debt reduction. This post is a series of lessons learned about how to engage in this process (and when to decide not to).&lt;/p&gt;
&lt;h2&gt;How Much Tech Debt is Too Much?&lt;/h2&gt;
&lt;h3&gt;The Tech Debt Tipping Point&lt;/h3&gt;
&lt;p&gt;A system with too much tech debt eventually becomes unmaintainable. Changes either take too long, are too difficult to test, or carry an unacceptably high risk of unintended consequences. The only way to extend the system is to add more tech debt. New feature development stops because the value gained is not proportional to the effort required.&lt;/p&gt;
&lt;p&gt;Bad experiences with the system breed reluctance to work with the system at all, institutional knowledge of it gradually erodes, upgrades are postponed or simply don’t happen, and eventually someone puts a comment in the system’s README file that says something like “Never change this system again.”&lt;/p&gt;
&lt;h3&gt;Everyday Productivity Losses&lt;/h3&gt;
&lt;p&gt;Even for systems that haven’t reached the maintainability tipping point, tech debt can cause constant issues and increase development time, developer stress, and potential bugs.&lt;/p&gt;
&lt;p&gt;A classic example of this problem is a test suite with one or more intermittently failing tests. Each individual case of the suite failing but then succeeding on rerun is minor, but the cumulative effect over weeks or months is far from minor. The time lost to reruns is an obvious consequence, but the erosion of confidence in the test suite can be even more damaging. True test failures will be hard to distinguish from the intermittent, “expected” failures, and the temptation to deploy with failing tests, or disable the test suite entirely, will grow.&lt;/p&gt;
&lt;h2&gt;Paying Down Tech Debt&lt;/h2&gt;
&lt;p&gt;We’ve seen above why tech debt is undesirable, but deciding what to do about it is not always easy. Here are a few possibilities.&lt;/p&gt;
&lt;h3&gt;Radical Action&lt;/h3&gt;
&lt;h4&gt;Full Rewrite&lt;/h4&gt;
&lt;p&gt;Much has been said about “the allure of the full rewrite.” It’s tempting because it solves every problem the current system has — but at the expense of introducing every problem the new system will have. Note that the two lists of problems do not have to be mutually exclusive!&lt;/p&gt;
&lt;p&gt;Starting over may well be the only option for systems that have passed the tech debt tipping point or for systems that are no longer fit for purpose because of fundamental design or scaling problems.&lt;/p&gt;
&lt;p&gt;Starting over is also a large, all-or-nothing commitment to launching a new system. It will bring no value at all if the project fails, stalls, or is canceled.&lt;/p&gt;
&lt;h4&gt;Full Deletion&lt;/h4&gt;
&lt;p&gt;This is a common option of last resort for unreliable tests. Tests that don’t reliably report on correctness and run in a reasonable amount of time are not useful and may in fact be a hindrance. If rewriting the tests is not possible, not having the tests at all may well be preferable.&lt;/p&gt;
&lt;p&gt;Needless to say, not having tests is generally not a good state of affairs to be in either.&lt;/p&gt;
&lt;h3&gt;Radical Inaction&lt;/h3&gt;
&lt;p&gt;A depressingly common approach is to do nothing and ignore the problem. This can stem from inexperience, a business strategy that demands new feature development at all costs, or simple wishful thinking. Regardless of the reason, persistent inaction tends to lead to the tech debt tipping point.&lt;/p&gt;
&lt;h3&gt;A Pragmatic Middle Way&lt;/h3&gt;
&lt;p&gt;First…&lt;/p&gt;
&lt;h4&gt;You Will Never Remove All the Tech Debt&lt;/h4&gt;
&lt;p&gt;This is an important thing to accept. No system is perfect, and attempts at perfection eventually reach a point where massive effort is necessary for very little gain. The goal should be a system that’s “good enough.”&lt;/p&gt;
&lt;p&gt;But…&lt;/p&gt;
&lt;h4&gt;You Should Remove the Important Tech Debt&lt;/h4&gt;
&lt;p&gt;The definition of “good enough” will vary from system to system (see below), but tech debt that prevents the system from fulfilling this definition is important tech debt that needs paying down.&lt;/p&gt;
&lt;h4&gt;How To Identify the Important Tech Debt&lt;/h4&gt;
&lt;p&gt;It’s helpful to ask yourself the following questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How important is this system? What’s the business and operational impact of bugs, outages, or long development cycles?&lt;/li&gt;
&lt;li&gt;Is this system still fundamentally fit for purpose? Do we foresee (or are we currently experiencing) significant scaling problems that this system cannot surmount in its current form?&lt;/li&gt;
&lt;li&gt;Is this system easy to understand? Is it well documented?&lt;/li&gt;
&lt;li&gt;How often do we need to change or maintain this system? How much staff time do we lose per week/month/quarter because this system is hard to work with?&lt;/li&gt;
&lt;li&gt;Is this system reliant on external libraries or underlying technologies that are deprecated?&lt;/li&gt;
&lt;li&gt;How many people are comfortable working with this system? Is the company overly reliant on a small number of key people, without whom this system might hit the tech debt tipping point?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Case Study: Converting a Microservice to a Batch Pipeline&lt;/h2&gt;
&lt;p&gt;One of my team’s services had a long history of tech debt-related problems. When we got a request for several new features, we believed adding these features would push the system perilously close to the tech debt tipping point. The system was designed in a way that put heavy, ever-increasing load on its database. We knew this load would eventually become unmanageable and the proposed new features would only hasten this process. The system needed either a substantial design change or a wholesale replacement. Our ideas for design changes were risky and complex, and we felt more confident of success with a wholesale replacement system. In other words, we felt radical action was justified in this case.&lt;/p&gt;
&lt;p&gt;The replacement system is a batch pipeline that we built as a new component in a related system I’ll call Gulper. While the new component was essentially a greenfield project, it still had to integrate with other parts of Gulper. Gulper has not hit the tech debt tipping point, but as one of the team’s oldest and most complex systems, it is tech debt heavy and often hard to work with. In addition to adding the new component, we wanted to pay down some of Gulper’s tech debt in other areas, but we needed to balance that against our project deadlines.&lt;/p&gt;
&lt;p&gt;Gulper’s already existing acceptance tests proved to be a particular pain point. They often failed spuriously, they were written in a language different than that in the main code base, they used external resources in a way that meant only one instance of the tests could run at a time, and they ran incredibly slowly. These tests run before every Gulper deploy. At times during our project, there were three people changing and deploying Gulper multiple times a day. The acceptance tests were costing us as much as ten or fifteen staff hours a week. The idea of full deletion of these tests did cross our minds at one point, but instead we committed to heavily refactoring the tests, using a contract-driven approach to uncouple test execution from shared external resources and mitigate spurious failures. Given the huge amounts of staff time the tests’ tech debt was causing, it was clear these tests were nowhere near “good enough.” So spending the staff time to make them “good enough” made perfect sense.&lt;/p&gt;
&lt;p&gt;In contrast, we encountered a number of issues with Gulper’s data model that we decided to live with. Refactoring these would have been a major undertaking, and we didn’t believe the time spent would produce sufficient benefit, especially when weighed against our other priorities.&lt;/p&gt;
&lt;p&gt;In a nutshell, this project involved some radical action in the form of a full rewrite of one system, some less radical action in the form of a heavy test suite refactor in another system, and some calculated inaction in the form of choosing not to refactor the data model issues.&lt;/p&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Identifying important tech debt is not always easy — it takes practice and can be heavily subjective and situation-dependent as well — but paying down important tech debt now may very well keep your systems from hitting the tech debt tipping point later. Conversely, spending too much time paying down unimportant tech debt impedes your ability to deliver features in a timely manner and can be immensely frustrating. As with so many things in life, the key is finding balance.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How We Develop New Features Using Offsites and Clean Architecture]]></title><description><![CDATA[The current SoundCloud iOS App was built back in 2014, resulting in a huge codebase consisting of both Objective-C and Swift, with multiple…]]></description><link>https://developers.soundcloud.com/blog/how-we-develop-new-features-using-offsites-and-clean-architecture</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-we-develop-new-features-using-offsites-and-clean-architecture</guid><pubDate>Fri, 02 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The current SoundCloud iOS App was built back in 2014, resulting in a huge codebase consisting of both Objective-C and Swift, with multiple architectural patterns and lots of legacy code. With time, the team changed and the platform evolved, so for the current iOS Team, maintenance, bug fixing, and implementing new features are all challenges.&lt;/p&gt;
&lt;p&gt;In 2017, our team of six engineers wanted to try out a clean architectural pattern and decided to use VIPER.&lt;/p&gt;
&lt;p&gt;I first read about VIPER four years ago in a post published by &lt;a href=&quot;https://www.objc.io/issues/13-architecture/viper/&quot;&gt;objc.io&lt;/a&gt;. This post is probably the most well-known guide to VIPER. At the time, most iOS applications were using Apple’s Model View Controller (MVC) pattern. This pattern is well suited for relatively small teams where there is only one developer working on one of the components (the Model, the View, or the Controller) at a time. VIPER, on the other hand, is good for larger teams and for parallelizing work among them, particularly when an app is big and complex, which the SoundCloud app is.&lt;/p&gt;
&lt;p&gt;In May 2017 when I joined SoundCloud, I was excited to improve the massive codebase with my previous learnings. Prior to starting at SoundCloud, I’d used VIPER on a project written in Swift in hopes of avoiding the obstacles of MVC. Overall, I had a really positive experience and I was looking forward to exploring it more, and my work at SoundCloud provided the perfect opportunity to do this.&lt;/p&gt;
&lt;p&gt;Because I was the only one on the team familiar with VIPER, we organized a one-day workshop where I explained the VIPER pattern to the team and we rewrote an existing screen (the System Playlist) with it. The feedback from the team was quite positive and everyone was excited to start using it. As a result, we went on to write the new Mini Player feature using VIPER, and because it was successful, we’ve since used VIPER to write additional features.&lt;/p&gt;
&lt;h2&gt;Offsites&lt;/h2&gt;
&lt;p&gt;Every time we set out to write a new feature, we begin with a project kickoff, which is where we define what we are doing and why. Only two engineers, the project’s point people, are present for the kickoff. Part of their responsibility is to organize an offsite to show the feature to the team and brainstorm on the implementation.&lt;/p&gt;
&lt;p&gt;I always liked how hackathons are done. The fast-paced environment, the chaos, the freedom, the focus on the problem without any interruption, and the brainstorming with the people in the room are all things that enable productivity. We can quickly solve a problem and accomplish a shared goal in a much shorter period, all while having fun at the same time.&lt;/p&gt;
&lt;p&gt;Offsites remind me of hackathons because they consist of a full day at another location, without any meetings and with food and drinks. In other words, they’re different than the usual work day. Working toward a goal together, all while having fun, helps with knowledge sharing and team bonding.&lt;/p&gt;
&lt;p&gt;The day starts with an explanation of the feature in question and the UX, followed up by questions to clarify the functionalities. The intention here is to have everyone on the same page without any misunderstandings. Once we have enough information, we start on the &lt;code class=&quot;language-text&quot;&gt;Contract&lt;/code&gt; for the new feature.&lt;/p&gt;
&lt;p&gt;For us, the &lt;code class=&quot;language-text&quot;&gt;Contract&lt;/code&gt; is the most important file, and it’s where all protocols of the VIPER module are defined. This provides a quick and easy overview of the behavior of the feature — &lt;a href=&quot;https://github.com/gfendres/viper/blob/master/SampleViper/SampleViper/Modules/Tracks/TracksContract.swift&quot;&gt;as you can see here&lt;/a&gt; — and helps us organize our ideas.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;As an example, here is how we might build a generic feature. It assumes some familiarity with VIPER, but for more details on the way we at SoundCloud use VIPER, &lt;a href=&quot;https://github.com/gfendres/viper&quot;&gt;see here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We start with a simple application that shows a list of tracks, as seen in the image below.&lt;/p&gt;
&lt;p&gt;As we can see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The user sees a list of tracks.&lt;/li&gt;
&lt;li&gt;The user can add a track.&lt;/li&gt;
&lt;li&gt;The user can delete a track.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0d6cb4d2bc96d07a757427300c43775a/1f853/trackscreen.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAd6N1FgE/8QAGBAAAwEBAAAAAAAAAAAAAAAAAQIQETH/2gAIAQEAAQUCPFY7Mn//xAAWEQADAAAAAAAAAAAAAAAAAAABEBH/2gAIAQMBAT8BEX//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAZEAEAAwEBAAAAAAAAAAAAAAABABARIbH/2gAIAQEAAT8hWJiiPtPSBHcK/9oADAMBAAIAAwAAABDnz//EABYRAQEBAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxBAdJ//xAAXEQEAAwAAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/EEbxj//EABoQAQACAwEAAAAAAAAAAAAAAAEAEBExQcH/2gAIAQEAAT8QYbYKQHgHxQyD2HQadCv/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;trackscreen&quot;
        title=&quot;trackscreen&quot;
        src=&quot;/blog/static/0d6cb4d2bc96d07a757427300c43775a/a296c/trackscreen.jpg&quot;
        srcset=&quot;/blog/static/0d6cb4d2bc96d07a757427300c43775a/f544b/trackscreen.jpg 200w,
/blog/static/0d6cb4d2bc96d07a757427300c43775a/41689/trackscreen.jpg 400w,
/blog/static/0d6cb4d2bc96d07a757427300c43775a/a296c/trackscreen.jpg 800w,
/blog/static/0d6cb4d2bc96d07a757427300c43775a/1f853/trackscreen.jpg 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Contract&lt;/h2&gt;
&lt;p&gt;We tend to follow a specific order to create the &lt;code class=&quot;language-text&quot;&gt;Contract&lt;/code&gt; (Protocols). This doesn’t mean that a different order would be worse; this is just the way the conversation usually flows.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c5f5aa4717c62570e3f546578d9e3a88/1f853/contract.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwAF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAgP/2gAMAwEAAhADEAAAAd0GNzeoU//EABkQAAIDAQAAAAAAAAAAAAAAAAECABExEP/aAAgBAQABBQJsphxsLWJ//8QAFREBAQAAAAAAAAAAAAAAAAAAAhD/2gAIAQMBAT8BU//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/ASf/xAAYEAEBAAMAAAAAAAAAAAAAAAABEAAREv/aAAgBAQAGPwLF7bon/8QAGxABAAICAwAAAAAAAAAAAAAAAQAQIVERQWH/2gAIAQEAAT8haY7IZKeJXfpISA8tf//aAAwDAQACAAMAAAAQN8//xAAWEQEBAQAAAAAAAAAAAAAAAAARECH/2gAIAQMBAT8QDs//xAAWEQEBAQAAAAAAAAAAAAAAAAARECH/2gAIAQIBAT8QZk//xAAcEAEBAAEFAQAAAAAAAAAAAAABEQAQITFh0eH/2gAIAQEAAT8QqnFCyyudgFBPmm3YohOhM4eI4PdP/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;contract&quot;
        title=&quot;contract&quot;
        src=&quot;/blog/static/c5f5aa4717c62570e3f546578d9e3a88/a296c/contract.jpg&quot;
        srcset=&quot;/blog/static/c5f5aa4717c62570e3f546578d9e3a88/f544b/contract.jpg 200w,
/blog/static/c5f5aa4717c62570e3f546578d9e3a88/41689/contract.jpg 400w,
/blog/static/c5f5aa4717c62570e3f546578d9e3a88/a296c/contract.jpg 800w,
/blog/static/c5f5aa4717c62570e3f546578d9e3a88/1f853/contract.jpg 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0cc85866837a1b7cfdaedc06ac43776f/1f853/viper.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMCBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAADAv/aAAwDAQACEAMQAAAB1FXkIUh5Cf/EABsQAAIBBQAAAAAAAAAAAAAAAAECAwAQERIT/9oACAEBAAEFAtz0WRyIyxXFRoy2/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERAv/aAAgBAwEBPwGaoj//xAAWEQADAAAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8Bqgz/xAAaEAACAgMAAAAAAAAAAAAAAAAAIQEQERIi/9oACAEBAAY/AsbEsddOv//EABsQAQACAgMAAAAAAAAAAAAAAAEAESExQVFh/9oACAEBAAE/IS574qc7gK1HC83E5oL7jjcCQA0T/9oADAMBAAIAAwAAABBoD//EABcRAQEBAQAAAAAAAAAAAAAAAAERAFH/2gAIAQMBAT8QZC9wQi7/xAAXEQEBAQEAAAAAAAAAAAAAAAABEQBR/9oACAECAQE/ECpnMhaG/8QAHBABAAICAwEAAAAAAAAAAAAAAQARITFBUXFh/9oACAEBAAE/EEUjTr8DzEcC9Q2vybESGDWOo0QmOmb9jmIADOYJQB8J/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;viper&quot;
        title=&quot;viper&quot;
        src=&quot;/blog/static/0cc85866837a1b7cfdaedc06ac43776f/a296c/viper.jpg&quot;
        srcset=&quot;/blog/static/0cc85866837a1b7cfdaedc06ac43776f/f544b/viper.jpg 200w,
/blog/static/0cc85866837a1b7cfdaedc06ac43776f/41689/viper.jpg 400w,
/blog/static/0cc85866837a1b7cfdaedc06ac43776f/a296c/viper.jpg 800w,
/blog/static/0cc85866837a1b7cfdaedc06ac43776f/1f853/viper.jpg 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Presenting&lt;/h3&gt;
&lt;p&gt;We start with the &lt;code class=&quot;language-text&quot;&gt;Presenting&lt;/code&gt; protocol, which handles UI events as follows.&lt;/p&gt;
&lt;p&gt;This will probably call the &lt;code class=&quot;language-text&quot;&gt;Interactor&lt;/code&gt; to fetch the data:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will probably go to the &lt;code class=&quot;language-text&quot;&gt;Router&lt;/code&gt; to show another screen or call the &lt;code class=&quot;language-text&quot;&gt;Interactor&lt;/code&gt; to immediately add a new value:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;didTapAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will receive the index of the row and find which track should be deleted. It will probably call the &lt;code class=&quot;language-text&quot;&gt;Interactor&lt;/code&gt; to delete the track:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;didSwipeToDelete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;at row&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Interacting&lt;/h3&gt;
&lt;p&gt;Next, we develop the &lt;code class=&quot;language-text&quot;&gt;Interacting&lt;/code&gt; protocol because the UI interactions are already defined in the &lt;code class=&quot;language-text&quot;&gt;Presenting&lt;/code&gt; protocol.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We found that using &lt;code class=&quot;language-text&quot;&gt;delegate&lt;/code&gt; over &lt;code class=&quot;language-text&quot;&gt;blocks&lt;/code&gt; between the components is clearer and more organized, given we often call the same &lt;code class=&quot;language-text&quot;&gt;delegate&lt;/code&gt; in multiple methods.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is how the &lt;code class=&quot;language-text&quot;&gt;Interacting&lt;/code&gt; protocol will look:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fetchTracks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;addTrack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;InteractorDelegate&lt;/h3&gt;
&lt;p&gt;It is time to go back to the View. So next is the &lt;code class=&quot;language-text&quot;&gt;InteractorDelegate&lt;/code&gt;, which is implemented by the &lt;code class=&quot;language-text&quot;&gt;Presenter&lt;/code&gt;. You can expect something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;didFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ServiceError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Viewing&lt;/h3&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Viewing&lt;/code&gt; protocol comes next, with the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewModels&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;TrackViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above receives an array of &lt;code class=&quot;language-text&quot;&gt;ViewModels&lt;/code&gt; to be displayed in the &lt;code class=&quot;language-text&quot;&gt;TableView&lt;/code&gt;. Usually, when we delete something, we call the same method with the items updated. If necessary, we can create a method to update a specific item at the index.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above receives a &lt;code class=&quot;language-text&quot;&gt;ViewModel&lt;/code&gt; or a &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt; to be shown.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;showLoadingIndicator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;hideLoadingIndicator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Routing&lt;/h3&gt;
&lt;p&gt;Let’s assume that tapping on a track will direct to another screen with more detail.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Routing&lt;/code&gt; protocol will just present the detail screen, like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token function&quot;&gt;presentDetailScreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It will get another &lt;code class=&quot;language-text&quot;&gt;Builder&lt;/code&gt; to retrieve the module and to use the &lt;code class=&quot;language-text&quot;&gt;weak&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;ViewController&lt;/code&gt; to show the next screen.&lt;/p&gt;
&lt;h2&gt;Hands On&lt;/h2&gt;
&lt;p&gt;Before we start coding, we select the most challenging parts to work on. Depending on the project, it could be a UI interaction, or it could be the decoupling of a class, a service, or even some of the VIPER components.&lt;/p&gt;
&lt;p&gt;We split the team in groups, with each group trying to solve the problem that is given to them. Sometimes it is just a spike that will be performed on a different project, but sometimes we will work on the main application together.&lt;/p&gt;
&lt;p&gt;If the feature is straightforward, each group gets one of the VIPER parts. Then we start branching from a feature branch in which the &lt;code class=&quot;language-text&quot;&gt;Contract&lt;/code&gt; will be updated.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1ea986f39dad63df30336c9283c35bda/1f853/branching.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAUAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAHuIqwgooCv/8QAGBAAAwEBAAAAAAAAAAAAAAAAAQIRABD/2gAIAQEAAQUCBNS1XaYKBz//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPwFJ/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8Bp//EABsQAAEEAwAAAAAAAAAAAAAAAAABAhAhMTKh/9oACAEBAAY/AtVFz0tjoqP/xAAcEAABBAMBAAAAAAAAAAAAAAABABARgSFBYfD/2gAIAQEAAT8hKgQdKqHmVsBTF5CG/9oADAMBAAIAAwAAABD3/wD/xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQMBAT8QVRgy/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQAR/9oACAECAQE/EAglt//EAB0QAQABBQADAAAAAAAAAAAAAAERABAhQVGBodH/2gAIAQEAAT8QVAc5CPTSDZQoF68K6DbPqz2Sdytv/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;contract&quot;
        title=&quot;contract&quot;
        src=&quot;/blog/static/1ea986f39dad63df30336c9283c35bda/a296c/branching.jpg&quot;
        srcset=&quot;/blog/static/1ea986f39dad63df30336c9283c35bda/f544b/branching.jpg 200w,
/blog/static/1ea986f39dad63df30336c9283c35bda/41689/branching.jpg 400w,
/blog/static/1ea986f39dad63df30336c9283c35bda/a296c/branching.jpg 800w,
/blog/static/1ea986f39dad63df30336c9283c35bda/1f853/branching.jpg 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As time goes on, we continue to communicate with one another, helping with the most difficult parts and explaining architectural decisions, which makes the team more aware of the entire development process. Thanks to a different environment without interruptions, we can have these discussions without disturbing other colleagues.&lt;/p&gt;
&lt;p&gt;By the end of the day, we have a rough idea of how our feature is going to behave. Through this process, we are essentially creating a bridge, wherein each team is constructing from a different place and hoping that in the end, all the pieces fit together.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/1f853/bridge.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAQAF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQD/2gAMAwEAAhADEAAAAd4VCYv/xAAXEAADAQAAAAAAAAAAAAAAAAAAAREQ/9oACAEBAAEFAqiopFv/xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAwEBPwGn/8QAFREBAQAAAAAAAAAAAAAAAAAAECH/2gAIAQIBAT8Bh//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABkQAQADAQEAAAAAAAAAAAAAAAEAEWEQUf/aAAgBAQABPyHaJNLKekyJRz//2gAMAwEAAgADAAAAEEP/AP/EABURAQEAAAAAAAAAAAAAAAAAAAAh/9oACAEDAQE/ECv/xAAVEQEBAAAAAAAAAAAAAAAAAAAAIf/aAAgBAgEBPxAj/8QAGxAAAwADAQEAAAAAAAAAAAAAAAERITHxUWH/2gAIAQEAAT8Q2Y4+jUgmpTrHOG61bhF4f//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;contract&quot;
        title=&quot;contract&quot;
        src=&quot;/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/a296c/bridge.jpg&quot;
        srcset=&quot;/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/f544b/bridge.jpg 200w,
/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/41689/bridge.jpg 400w,
/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/a296c/bridge.jpg 800w,
/blog/static/0f5bb2a4ad6e67e9e5844995acc4daaa/1f853/bridge.jpg 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;What we want to achieve on this day is to identify the possible problems we might have and try to solve or propose solutions while we are together and focused. As a bonus, we may have some of the components done by the end of the day, like a &lt;code class=&quot;language-text&quot;&gt;Presenter&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;Interactor&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;After the Offsite&lt;/h2&gt;
&lt;p&gt;After having an offsite, it’s easier to create user stories and estimate and define what needs to be done. To date, the iOS team at SoundCloud has had four offsites. Each one had different outputs and sizes, but all of them helped us share knowledge and brainstorm problems that could have been difficult to solve later.&lt;/p&gt;
&lt;p&gt;Having a clean architecture has given us the possibility of splitting the work, which makes it easier to have six people working on the same feature. It also helps improve awareness of the areas that needed to be covered, thereby reducing unexpected issues.&lt;/p&gt;
&lt;p&gt;As a bonus, we discovered that having everybody in a different working environment with one goal in mind helped us discover problems earlier, improved our shared knowledge and ownership of a feature, and bonded the team.&lt;/p&gt;
&lt;p&gt;We are looking forward to building more features in this format and seeing what we’ll learn from it. We’ve found that every time we do it, our process evolves, we learn new things, and we’re able to refine our workflow. We hope that over time we can decrease assumptions, have more consistent estimations, reduce project risk, and be able to ship features with more confidence.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Kubernetes Pod Metadata to Improve Zipkin Traces]]></title><description><![CDATA[SoundCloud is built on hundreds of microservices. This creates many challenges, among them debugging latency issues across the services…]]></description><link>https://developers.soundcloud.com/blog/using-kubernetes-pod-metadata-to-improve-zipkin-traces</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/using-kubernetes-pod-metadata-to-improve-zipkin-traces</guid><pubDate>Wed, 19 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 791px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/acad98c939f74b77be82c164bfe3ab9d/bf224/1-dependency-graph.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.776232616940575%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABFElEQVQoz1WSCW7FIAxEuf9Js5csELK6PLfz1UayggE/jycJVp/zPG0cR4++761pGluWxWPbNiul2HEcfu+6Lg9yIudsz/PYfd+gLJAA2PfdL6R6oW1bG4bBYoy2rqtfVhEBkCYEa86o5SzQAaCUsInS+BU9B/r3ed/X39yjVnnaUmWkH4WooBAoY/ZdZ10NcprllP8pJACllHyfR6MHEpTMFUgX4EBQiQ14ChiIPNTYQNgDBJwmQWNM0/TpCEz+sAZIIc2YgGnKXnyfNXXscz/IAw4pAIQq1hjN6Lz1IWQPucN/vf58ZQHneXY1UgUEtQCllgBIQ+zgT9Co8jboP6SbDhkff8gpllc00QSoXKoI1cjbb9znuzpaVoVYAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud microservice dependency graph zoomed out&quot;
        title=&quot;SoundCloud microservice dependency graph zoomed out&quot;
        src=&quot;/blog/static/acad98c939f74b77be82c164bfe3ab9d/bf224/1-dependency-graph.png&quot;
        srcset=&quot;/blog/static/acad98c939f74b77be82c164bfe3ab9d/9ec3c/1-dependency-graph.png 200w,
/blog/static/acad98c939f74b77be82c164bfe3ab9d/c7805/1-dependency-graph.png 400w,
/blog/static/acad98c939f74b77be82c164bfe3ab9d/bf224/1-dependency-graph.png 791w&quot;
        sizes=&quot;(max-width: 791px) 100vw, 791px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;SoundCloud is built on hundreds of &lt;a href=&quot;https://developers.soundcloud.com/blog/category/microservices&quot;&gt;microservices&lt;/a&gt;. This creates many challenges, among them debugging latency issues across the services engaged in the completion of a single request. As such, it’s natural that we were early adopters of &lt;a href=&quot;https://zipkin.io/&quot;&gt;Zipkin&lt;/a&gt;, a distributed tracing tool that emerged from Twitter, which was built in part to help engineers tackle these sorts of problems.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.slideshare.net/grobie/moving-to-kubernetes-tales-from-soundcloud&quot;&gt;In recent years&lt;/a&gt;, we’ve switched to using &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; as our container deployment platform. Kubernetes makes it easy to break a microservice down into narrowly focused operational responsibilities, which often correspond to different workloads the service must handle and are typically expressed in Kubernetes as distinct &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/#pod-templates&quot;&gt;pod templates&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because each &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/pod/&quot;&gt;Kubernetes pod&lt;/a&gt; is assigned its own IP address, and because each pod has a well-defined responsibility, it’s possible to make higher-level assumptions about an IP address given the easy access to data that Kubernetes accumulates across deploys. This is in contrast to the pre-Kubernetes era at SoundCloud, when an IP address referring to a “bare-metal” machine would likely have hosted several very different services.&lt;/p&gt;
&lt;p&gt;The BEEP team&lt;sup&gt;&lt;a href=&quot;#f1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; here at SoundCloud recently revisited our Zipkin setup, primarily to make two improvements: to &lt;strong&gt;increase the quality and accuracy of service names in Zipkin&lt;/strong&gt;, and to &lt;strong&gt;enhance Zipkin span data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Updating Zipkin service names across our hundreds of services in concert with a constantly evolving backend architecture has been a particular challenge. This is because Zipkin uses service names to represent how one service relates to another, and without &lt;strong&gt;good, consistent service names&lt;/strong&gt;, the value of Zipkin as a visualization or debugging tool is greatly reduced.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3fb52692a62f8b4268450e8a9320ca5f/6e3c3/2-zipkin-trace-overview.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.17422434367542%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABJklEQVQoz21RW07DMBDMneB6nKScgX++kCohwU+lilcolaqUtiGUNGndJI5je5jYDiqIsUb78o686wiEtbY3MLTaWBc7nh5/Bbak/+RzLnYMRSIaxAb7C3vyi8x7yzYSW/ofPme31uVdPaNtgqAJYtPlJ65nKTqGRmsUmcBBNFAKqGuFfSGgtEELAyk18vIA2bBoKK7YJHtB+DF7XL1tcD4aYzRJXCyaBvNVhsdFivfdEbu6RUnhVhl0nYGaGuhhwjC2e6EOwf1DgbPLMS5uYoi2+9nMLEnxuso5kUGz5O07324XtCbI+WUicmFFb86VxR1u1znyqsWRgrLTTrCSCrmQXp37sxOu6ZkPSSz+/kFk13Ri6xf8D04/a/h5hyX5Qqowbkh/A+kXH3sP5F18AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Overview of a Zipkin trace&quot;
        title=&quot;Overview of a Zipkin trace&quot;
        src=&quot;/blog/static/3fb52692a62f8b4268450e8a9320ca5f/8ff1e/2-zipkin-trace-overview.png&quot;
        srcset=&quot;/blog/static/3fb52692a62f8b4268450e8a9320ca5f/9ec3c/2-zipkin-trace-overview.png 200w,
/blog/static/3fb52692a62f8b4268450e8a9320ca5f/c7805/2-zipkin-trace-overview.png 400w,
/blog/static/3fb52692a62f8b4268450e8a9320ca5f/8ff1e/2-zipkin-trace-overview.png 800w,
/blog/static/3fb52692a62f8b4268450e8a9320ca5f/6e3c3/2-zipkin-trace-overview.png 838w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;div style=&quot;text-align: center;&quot;&gt;As microservices combine to process a single overall request, they may (on a sampling basis) also report trace data to Zipkin. A trace identifier is established for an overall request and propagated as one microservice calls out to others. The caller microservice sends the remote service name, endpoint name, call timing, and trace ID to Zipkin, which Zipkin turns into a span. Spans with the same trace ID are woven together to form a Zipkin trace. See also &lt;a href=&quot;https://zipkin.io/pages/instrumenting.html&quot;&gt;Zipkin: Instrumenting a library&lt;/a&gt;.&lt;/div&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Defining Zipkin Service Names in One Central Place&lt;/h2&gt;
&lt;p&gt;In a service-to-service interaction, the caller service reports the IP address of the callee service’s pod as part of the &lt;a href=&quot;https://zipkin.io/pages/instrumenting.html&quot;&gt;span&lt;/a&gt; data it sends to Zipkin. After brainstorming, we realized we could customize the Zipkin &lt;a href=&quot;https://zipkin.io/pages/architecture.html&quot;&gt;collector&lt;/a&gt; to transform the span on the fly, performing a quick reverse IP lookup of the Kubernetes pod metadata and &lt;strong&gt;replacing the caller’s version of the Zipkin service name with one assembled from the callee’s &lt;a href=&quot;https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/&quot;&gt;pod labeling&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/6e3c3/3-zipkin-span-transformation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.65632458233891%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACYUlEQVQ4y42U6XKiUBCFff+3mZr5MUvpJE5Ks4yJJkENihAXQIlRWS/rNxcTjaZma+pCA5fTfbpPU0GauXRp9Qxu+gat7ojBxGG9XqPrOkNNI/A9eqMZl4M5X5v3fDxpUe/o3D0+E4mkhKAoiu21Il0WK5+OOqanm7QUDXVsE8cJvufhuh5RGKJPFzTvDU6uuny/VKhfP9AdL4mT9Bhw55SWJTGB57JZrQiDkH/a67cHEG+Atm3T7nToyHX58wp9bBDIzPI830b/7dqDFXvQPeBAVamfnqLcdxGOgA3EIiZN3yj9be32VHbO6vkZyzJZWHNiJyJ1EhIJ+JLH/7B/V8MsL4iSnPHMRptMiCTVMIqwTAtDMZipU8a6fC4EQRSz8UM2QYQfCvLDDHc3bhBz1hlSvVD4XG9x1taY2g7OfMFclas/x7EcdMPg4nZA7VyhKiV0fqeRZvkx5SzLSJJkK5VE1my1WrNx3T/SE17M0/0S/zEgCgSvbXmh7MkPRyMdRVFo37TRhhrmzJRi9kmlxsrGiEhs/X15ZAI9ub+n9lg7awqjIDMysjCjMlAHNBpNHvp9TNsiiMNttPzgSIsUIcRR8TvtNtVvJ6jXQ+JuDLoM5EjAEqhWrdG8aDDtz1hcO+RTqT0TUhk1n+TbjSIWe8pltqZlEZoRhV8cU/YltTCM8OS8JqGMJGXnPW3wn2QNhaTiS9qyYSXNfQ2FeC8aJJGXppSnchpK2/gB9sqVPwKDB32M4wZ4kTjS2qGIi/xwaopjYZf2aC359KPNl8YtX6UsPtSuGM2c7bv8YGDfAx/aL5iNfHbf6q1SAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Adding information to a Zipkin span at the point of collection&quot;
        title=&quot;Adding information to a Zipkin span at the point of collection&quot;
        src=&quot;/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/8ff1e/3-zipkin-span-transformation.png&quot;
        srcset=&quot;/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/9ec3c/3-zipkin-span-transformation.png 200w,
/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/c7805/3-zipkin-span-transformation.png 400w,
/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/8ff1e/3-zipkin-span-transformation.png 800w,
/blog/static/d15f81a1f16e5ac189ee6c73559cd9d2/6e3c3/3-zipkin-span-transformation.png 838w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;div style=&quot;text-align: center;&quot;&gt;Assembling a Zipkin service name based on Kubernetes pod metadata.&lt;/div&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The result was an across-the-board improvement in both Zipkin service names and dependency graph quality.&lt;/p&gt;
&lt;p&gt;As a side effect of this approach, control over how a node is represented in Zipkin switched from the caller services to the callee service:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b6d66454c23a905f52ebc848fcfefd0f/6e3c3/4-divergent-service-names.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.861575178997615%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAAA6klEQVQY04WRyW7CQBBE/f+/lBMSUhZFIcDB2IkwXhLG+w7xeHkZJ/HBRIKSWtWXej2l0VAahoFbahqJEIKtcWC5MnjYvHH6kkz5iaFNS98PfEQFO1tgOr5yHxGX1HVFGIbEcUxVlfhxjr73OHxGNLK9AlRuHROeNiYrfc+rYeP6KWVRIP+CtzSytMsLbdcTJDkiSkmLiq7rZiEpJZZl4XkejjuOi207ZFk2f+Hkx7hguX5n8WKq6gFZmhAEoape/wLbjiTLqU5ntbc/B8fp+34OvPY5rQrmeY7rOjyud9zdb1k861Tn5l/lb70czvKyUXTuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Service names in Zipkin that are unfortunately divergent&quot;
        title=&quot;Service names in Zipkin that are unfortunately divergent&quot;
        src=&quot;/blog/static/b6d66454c23a905f52ebc848fcfefd0f/8ff1e/4-divergent-service-names.png&quot;
        srcset=&quot;/blog/static/b6d66454c23a905f52ebc848fcfefd0f/9ec3c/4-divergent-service-names.png 200w,
/blog/static/b6d66454c23a905f52ebc848fcfefd0f/c7805/4-divergent-service-names.png 400w,
/blog/static/b6d66454c23a905f52ebc848fcfefd0f/8ff1e/4-divergent-service-names.png 800w,
/blog/static/b6d66454c23a905f52ebc848fcfefd0f/6e3c3/4-divergent-service-names.png 838w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;div style=&quot;text-align: center;&quot;&gt;When callers control naming, the same callee may end up represented as several different services.&lt;/div&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ef773003a9241036a26a9c57fb301aff/6e3c3/5-improved-service-names.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.37708830548926%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAxElEQVQY042Q0W6CUAxA+f9PcntwmOHmdAFkCtvVCOqFO5HAnSh7OuvuF9jk5DRN2rT1Jos1vjCeJfjzFZNFytPbCv89JYi+0JXh2vfcG9728E1RncnLmp0+Oee6drV91XC9DXRti9aaoshRe8NycyQRPrbasVQH4Sg9Dd7/VNNYdN1S1p0gPrXO5twxDL9Ya91ApRRxtmMapswSxUuU8Rp/OoIwYyPLeba/MZ6vGQUhD0HEo3j0LPk0dq/QpeFy+bn75D+4lif3bJzkdQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Service names in Zipkin improved using Kubernetes data&quot;
        title=&quot;Service names in Zipkin improved using Kubernetes data&quot;
        src=&quot;/blog/static/ef773003a9241036a26a9c57fb301aff/8ff1e/5-improved-service-names.png&quot;
        srcset=&quot;/blog/static/ef773003a9241036a26a9c57fb301aff/9ec3c/5-improved-service-names.png 200w,
/blog/static/ef773003a9241036a26a9c57fb301aff/c7805/5-improved-service-names.png 400w,
/blog/static/ef773003a9241036a26a9c57fb301aff/8ff1e/5-improved-service-names.png 800w,
/blog/static/ef773003a9241036a26a9c57fb301aff/6e3c3/5-improved-service-names.png 838w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;div style=&quot;text-align: center;&quot;&gt;An alternative: all names come from reverse IP lookup against a centralized index.&lt;/div&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The team that owns the locale-info service makes the decision, for all teams, how that service is named, thus removing the need for multiple teams to coordinate on establishing or changing the service name. And the original Zipkin service name given by the caller is not lost — we add it as a &lt;a href=&quot;https://zipkin.io/pages/instrumenting.html&quot;&gt;span annotation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Useful Span Annotations from Pod Metadata&lt;/h2&gt;
&lt;p&gt;Zipkin span collection generates significant reverse-lookup load, but our integration hits a simple, very fast Kubernetes metadata caching service written by the SoundCloud Production Engineering team. Responses from this service contain a &lt;strong&gt;rich set of pod metadata&lt;/strong&gt; that goes beyond what’s merely necessary to assemble a good Zipkin service name, and we add all of this data as &lt;strong&gt;extra annotations on the Zipkin span&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The result is that when looking into a latency problem, we can not only see basic Zipkin information about how long a service took to respond, but we also know details like the Git revision of the service code that was running on the particular pod, because we consistently set that as a Kubernetes label on all pods. This is a level up in an engineer’s ability to efficiently pinpoint production problems.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 593px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0f57e386c97d73671a034e0ab0f2b9ad/fe994/6-zipkin-span-annotations-k8s-enhanced.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.55986509274874%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAB1ElEQVQ4y3WU15LqQAxE9/8/jyfwXZxwzoGctDqiTPmuWaqE7ZFG6ulu+0v093g8JIoi8f1APM+T7fdW2raVcRwlyzKJo1jCMLR1x/lnteT6vrcYhkGezyet5Is/HsqilDRJpet6ORwOcrlcdNBd6rqWsqzkfD7Lfr+3OJ1Ocrvd5Hq9vuO/hiCkGZMpPh6PtpFrrgjLspTT+bVOMJD8/LpomCSJxHEsRVFYUxAReZ4bau5pRu5TLBqCznM92Ww2sttF72OnaWoIb/e7Nf3UjPVFQ9AlcSJt0yjaVMkeDFGWZoYeLruuWxz7zyMjCkhMjOfDiIb4Xo9b5IWp3TatormYwjT9U5S7HicMQnE2jqxWK9vMJqaD3HEcCYKXpYIA+3yrjXaWxzLULhBCPpt3WlipTRjCeqMUxMpvluUyDqNMNiPHCagjFghp5LquNHVjE+GL6YgSqOFBTXOMTI6Y7rkuEEJ6qgLUVWWTWaOIYoZB/DSc3O9YIIQfd+vKer02cUBHYCe4JQfaCf302n189YxD5Yh3Fq4mhSkCOXbiec7Z71ggRBACtXudyBHHcTAP+r7/VnNCNI+Fyq+PQ2EbURtjN/q1YVClnCIKXpy8h1fnMffhD2i1f+WXfzDhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Additional span annotations using Kubernetes pod metadata&quot;
        title=&quot;Additional span annotations using Kubernetes pod metadata&quot;
        src=&quot;/blog/static/0f57e386c97d73671a034e0ab0f2b9ad/fe994/6-zipkin-span-annotations-k8s-enhanced.png&quot;
        srcset=&quot;/blog/static/0f57e386c97d73671a034e0ab0f2b9ad/9ec3c/6-zipkin-span-annotations-k8s-enhanced.png 200w,
/blog/static/0f57e386c97d73671a034e0ab0f2b9ad/c7805/6-zipkin-span-annotations-k8s-enhanced.png 400w,
/blog/static/0f57e386c97d73671a034e0ab0f2b9ad/fe994/6-zipkin-span-annotations-k8s-enhanced.png 593w&quot;
        sizes=&quot;(max-width: 593px) 100vw, 593px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;div style=&quot;text-align: center;&quot;&gt;An example Zipkin span with Kubernetes-derived annotations.&lt;/div&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Our approach involves inserting a small amount of logic into the Zipkin collector and then leveraging our internal Kubernetes pod labeling scheme to improve span data. The result is normalized and modernized service names, along with greatly enhanced details embedded in Zipkin traces that engineers can use to troubleshoot production issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Value-added span transformation&lt;/strong&gt; ideas are getting more attention in the Zipkin community. The &lt;a href=&quot;https://github.com/openzipkin&quot;&gt;OpenZipkin team&lt;/a&gt; has a &lt;a href=&quot;https://github.com/openzipkin/zipkin/pull/2072&quot;&gt;collector extension framework in the works&lt;/a&gt;, and we’re excited to see what other Zipkin “mashups” come out of the Zipkin community. If you’re interested, please join the &lt;a href=&quot;https://gitter.im/openzipkin/zipkin&quot;&gt;OpenZipkin gitter discussion&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Thank You&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Benjamin Debeerst, SoundCloud Backend Engineering Productivity Team&lt;/li&gt;
&lt;li&gt;Kristof Adriaenssens, Engineering Manager, SoundCloud Content Team&lt;/li&gt;
&lt;li&gt;René Treffer, SoundCloud Production Engineering Team&lt;/li&gt;
&lt;li&gt;Tobias Schmidt, SoundCloud Production Engineering Team&lt;/li&gt;
&lt;li&gt;Adrian Cole, Pivotal Spring Cloud Team and prominent Zipkin contributor&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Additional Reading&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/honeycombio/examples/tree/master/kubernetes-envoy-tracing&quot;&gt;Kubernetes Envoy Tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/blog/2017/05/managing-microservices-with-istio-service-mesh/&quot;&gt;Istio service mesh + Zipkin integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linkerd.io/1/features/distributed-tracing-and-instrumentation/&quot;&gt;Linkerd Zipkin integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name=&quot;f1&quot;&gt;1&lt;/a&gt;: The &lt;strong&gt;Backend Engineering Productivity team&lt;/strong&gt;’s mission is to help solve common, cross-cutting engineering problems that our product-focused engineers face. We also build and maintain many of SoundCloud’s “core” services.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hands-Off Deployment with Canary]]></title><description><![CDATA[At SoundCloud, we follow best practices around continuous delivery, i.e. deploying small incremental changes often (many times a day). In…]]></description><link>https://developers.soundcloud.com/blog/hands-off-deployment-with-canary</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/hands-off-deployment-with-canary</guid><pubDate>Wed, 29 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At SoundCloud, we follow best practices around continuous delivery, i.e. deploying small incremental changes often (many times a day). In order to do this confidently and reliably, we verify these changes before releasing, using different measures including, among others, unit, integration, and contract tests.&lt;/p&gt;
&lt;p&gt;This process is automated as part of a continuous delivery (CD) pipeline. Historically, a typical CD pipeline here has contained at least the following steps:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b051382f9344df09700c36a57750a801/3eaf4/pipeline-original.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.10266159695817%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsSAAALEgHS3X78AAABl0lEQVQoz41TOUsDQRR+ENCttU0sFS218/g1If9AGzsbDVqsoBHSSGJU0CaYVu3ECG4jkRAQEYMHhqyY3cwec+zzbeJ97jfDG+aD710zD/A7ArR8q8mbJjfJtv02MT8CvtwFLYXTrZmedm/CHuhhvZlWhhhJK6I4xVJQB+1Sg2tYZIskDPkfxeT1bXfF8/bCoDc07k+Q3bALUSMTlFKc87BOFdYvuCDmpRtB8FkcYNEr6nI5g2sr3mpd1ok5YIc66lnM6oFuMIMYn/tChJlLKUPXL2KFk3ISHiFWj4GAPaeEHiYfk3AG2okGF5A209hC5rFqtVo+Lj80GiTuptAVT4EJsasYSCi5JXQxyZLkTjvVwIL0UxoZNpoPlcq5YRi1Ws1xnPe09/l+TuW3cLvAN+/UPTHHbjkX5HfULvEV9zysXAqpKGUpOvivYa/gPn9rWKSnmrPm4s3EaGMsbibW7fXOQ0X/JE4KbkA70uAWlpylvz7JR4QRFM7as32if/h+pI/3Z61s1MiduQh8l/uMig0tHcEvk/EMcAvhqnqUi9AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Pipeline without canary step&quot;
        title=&quot;Pipeline without canary step&quot;
        src=&quot;/blog/static/b051382f9344df09700c36a57750a801/8ff1e/pipeline-original.png&quot;
        srcset=&quot;/blog/static/b051382f9344df09700c36a57750a801/9ec3c/pipeline-original.png 200w,
/blog/static/b051382f9344df09700c36a57750a801/c7805/pipeline-original.png 400w,
/blog/static/b051382f9344df09700c36a57750a801/8ff1e/pipeline-original.png 800w,
/blog/static/b051382f9344df09700c36a57750a801/3eaf4/pipeline-original.png 1052w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After building and packaging the code, a battery of automated tests was run in order to verify the expected behavior. Once all the tests passed, deployment to production could be manually triggered.&lt;/p&gt;
&lt;p&gt;This automated process, together with code reviews and other internal measures, greatly reduced the risk of introducing regressions.&lt;/p&gt;
&lt;p&gt;However, even with precautions like those mentioned above in place, things eventually can (and will) go wrong. In order to detect production issues, we use monitoring and alerting, so when a problem is found in production, we can react by reverting to a well-known previous state. Even so, since the change has already been fully rolled out, the user experience has already been severely impacted.&lt;/p&gt;
&lt;p&gt;In order to improve the user experience, we have been exploring different ways of reducing the &lt;strong&gt;impact&lt;/strong&gt; and the &lt;strong&gt;Mean Time to Recovery (MTTR)&lt;/strong&gt; of faulty deployments. Enter canary releases…&lt;/p&gt;
&lt;h2&gt;Iteration 1: Manual Canary Release&lt;/h2&gt;
&lt;p&gt;The term “canary” &lt;a href=&quot;https://www.smithsonianmag.com/smart-news/story-real-canary-coal-mine-180961570/&quot;&gt;originates&lt;/a&gt; from a mining tradition from the early 20th century, consisting of canaries being brought down to coal mines in order to detect toxic gases. When a canary died, it alerted workers to carbon monoxide or other poisons in the air, allowing them to evacuate before they were affected.&lt;/p&gt;
&lt;p&gt;In the context of software development, &lt;a href=&quot;https://martinfowler.com/bliki/CanaryRelease.html&quot;&gt;canary release&lt;/a&gt; is a technique that consists of making release candidates available to a small subset of users in order to detect regressions before the changes are available to all users.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we initially implemented this technique by building tooling to allow packaging and publishing release candidates into a canary environment and configuring load balancers to divert a small amount of production traffic to it. By comparing metrics between production and canary environments, our developers are able to identify faulty release candidates before they make it to production.&lt;/p&gt;
&lt;p&gt;This was very convenient, but it came with some disadvantages. As it was a manual process and not integrated in the CD pipelines, it was easy for developers to forget to turn off old release candidates. Multiple versions receiving production traffic sometimes caused confusion and hard-to-debug issues. And although the process was documented, not everyone in the company was aware of the possibility of using canary environments. As a tedious manual process needed to be followed, it was only used for a very small number of releases. Finally, both the lack of traceability and allowing any developer to put arbitrary code into production without following standard quality checks came with a set of security and safety concerns.&lt;/p&gt;
&lt;h2&gt;Iteration 2: Canary Pipeline Integration&lt;/h2&gt;
&lt;p&gt;A natural next step was to integrate canaries into our deployment pipeline, thereby automating the process and ensuring it was used for every release. This also ensured that any code receiving production traffic had passed our standard quality processes and that the release candidate running in the canary environment was up to date.&lt;/p&gt;
&lt;p&gt;With these changes in effect, the pipeline looked like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4f182e4d6e605f9894f241f2813e6bb2/23749/pipeline-manual-canary.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.973013493253376%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABc0lEQVQoz41ST0sCURCfD7DgQSOjPkRRn6UOLdR1oQzBS976LuKxDitmJ+2S3YJFEUWIMGO12NX1re/frq9ZV8ygrJmB997vzcybmd8DNZcZqlJE+Nf0piiLBV54Zf0lHm3mor4LxEuIqlRP9pJuCvoAb1DiJUQC1DXBeI0mlcSDze39ycH2ZGdznK7Re0QQx7xov7wcqoWhq5BLDyFEXFJUeKhmGK4W8ctEYHBDD3Rd6C3Z8oWfcS9OnNNjR2/S5jSYGsTQHf2QHDVYA7NQTl3XJYQEQdQOaEyDZwACJi0NpkPN1uAREDGn5sAfJEYJeAL4gLJXVlTZ7/ZDvW5ZFqU0Cs6zfIZl0LphFxNfyasczWVFtsM7LGCXJH82Pjc8o83bER0+8bzJyB0xyr6mvexThFJg76HknMccrpt2PMyYElvYu85e+mUrNdyosto/pr3KM+8lJ0mwAGwwhfk3z6s/jEpapbWKvLtllSEfrv6wH+UTzBVClne7yoIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Pipeline with manual canary step&quot;
        title=&quot;Pipeline with manual canary step&quot;
        src=&quot;/blog/static/4f182e4d6e605f9894f241f2813e6bb2/8ff1e/pipeline-manual-canary.png&quot;
        srcset=&quot;/blog/static/4f182e4d6e605f9894f241f2813e6bb2/9ec3c/pipeline-manual-canary.png 200w,
/blog/static/4f182e4d6e605f9894f241f2813e6bb2/c7805/pipeline-manual-canary.png 400w,
/blog/static/4f182e4d6e605f9894f241f2813e6bb2/8ff1e/pipeline-manual-canary.png 800w,
/blog/static/4f182e4d6e605f9894f241f2813e6bb2/6ff5e/pipeline-manual-canary.png 1200w,
/blog/static/4f182e4d6e605f9894f241f2813e6bb2/23749/pipeline-manual-canary.png 1334w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Every change went through a canary step in which developers could manually compare metrics and identify issues before proceeding to a full production deploy.&lt;/p&gt;
&lt;p&gt;This was a clear improvement over the previous situation, but it was still a manual process that relied on developers carefully looking at relevant graphs and spotting potential problems, which was time-consuming and error prone.&lt;/p&gt;
&lt;h2&gt;Iteration 3: Automated Canary Verification&lt;/h2&gt;
&lt;p&gt;In order to fully automate our deployments, we had to solve the problem of automatic canary verification. We soon realized we could leverage our existing monitoring and alerting infrastructure to accomplish just that.&lt;/p&gt;
&lt;p&gt;Every service at SoundCloud has a set of defined rules used to detect anomalies and undesired behaviors. With small modifications to the rule expressions, we were able to detect these conditions in each of our defined environments.&lt;/p&gt;
&lt;p&gt;The missing piece was to query our monitoring system from the CD pipelines to automatically detect and roll back any change introducing regressions. This allowed us to remove manual intervention while ensuring the same standards of quality used for our production environments.&lt;/p&gt;
&lt;p&gt;The resulting pipeline looked like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/cfcb22261ad68e86a1464a577e9cb957/f5ca1/pipeline-automatic-canary.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.295224312590456%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABrklEQVQoz4VSS0sjQRBuBJfNQdSTD9CzHtabv8cFiQePrv4A16Crh1VBD4vgkoMPvAgedtGTB09hFWJ84MZZY3xhZjKZ6Zmxp7v6YXeChgWJ1R9NdddXXdX9NVL/G3DwwPOE54MvhFANDb16QhmqRayOoLPVbfuEBzzq6R2pR4NkrrgGU0z7eZpvcpvQHep2u11wzaEcJOcajSrXjDBSEIVrVSjyIgP2TttSylX2c1J+nYaZW3GHKV7Ai9+D+RV/hSru/7v68232cG7uLJ02PQJET5EGAJhkKmk/7UdXCAVoN94r4iK6QSiDuh46HBVcbO8sJxKrLa1rg4OaHmCczWaPDo98H5tkIUWKpobI52SctLjlUjfJRoYrwxPxOFFQPjndHx09GPui62s2JaRsl+zHxzDAL3euPWd1jllsc9tRjgNOrbe6igBGPCkNXh9Mi6QBylAvqdVmt3+8SPSV+7TgJiq4Zsuq5jQMwl9reHOJu6U3dM7H+ebwA/qLeqLeCq3Uda4ln2S8H1PBxlL0e/0tqYDkaO6YHZ/H5/q31QPVViWJot2tcCfNS/d6+QwnBkLtCYfm3AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Pipeline with automatic canary step&quot;
        title=&quot;Pipeline with automatic canary step&quot;
        src=&quot;/blog/static/cfcb22261ad68e86a1464a577e9cb957/8ff1e/pipeline-automatic-canary.png&quot;
        srcset=&quot;/blog/static/cfcb22261ad68e86a1464a577e9cb957/9ec3c/pipeline-automatic-canary.png 200w,
/blog/static/cfcb22261ad68e86a1464a577e9cb957/c7805/pipeline-automatic-canary.png 400w,
/blog/static/cfcb22261ad68e86a1464a577e9cb957/8ff1e/pipeline-automatic-canary.png 800w,
/blog/static/cfcb22261ad68e86a1464a577e9cb957/6ff5e/pipeline-automatic-canary.png 1200w,
/blog/static/cfcb22261ad68e86a1464a577e9cb957/f5ca1/pipeline-automatic-canary.png 1382w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The addition of automatic canary deployments and verifications at SoundCloud was a massive success and has greatly improved our productivity and reliability. The confidence we have in automatically detecting any regression has removed the manual toil around deployment and enables our developers to focus on adding value without spending time babysitting deployments. When things inevitably go wrong, we now have yet another safety net that protects us from large-scale outages. Using automatic processes and removing human interaction to verify and roll back faulty deployments has greatly reduced our Mean Time to Detect (MTTD) and Mean Time to Recovery (MTTR).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Creating Readable Spark Jobs]]></title><description><![CDATA[Nowadays, it’s rather common to encounter Apache Spark being utilized in a lot of companies that need to process huge amounts of data, and things aren’t any different here at SoundCloud — as one can imagine, we have lots of data to process all the time.]]></description><link>https://developers.soundcloud.com/blog/creating-readable-spark-jobs</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/creating-readable-spark-jobs</guid><pubDate>Mon, 06 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Nowadays, it’s rather common to encounter &lt;a href=&quot;https://spark.apache.org/&quot;&gt;Apache Spark&lt;/a&gt; being utilized in a lot of companies that need to process huge amounts of data, and things aren’t any different here at SoundCloud — as one can imagine, we have lots of data to process all the time.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;While Spark does offer a &lt;a href=&quot;https://spark.apache.org/streaming/&quot;&gt;streaming API&lt;/a&gt;, along with other tools like &lt;a href=&quot;https://kafka.apache.org/documentation/streams/&quot;&gt;Kafka Streams&lt;/a&gt; and &lt;a href=&quot;https://flink.apache.org/&quot;&gt;Apache Flink&lt;/a&gt;, we mainly (but not exclusively) use batch processing with Spark to solve a variety of problems, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The creation of some partners’ reports&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/pagerank-in-spark&quot;&gt;Machine learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spark.apache.org/docs/2.3.0/api/java/index.html?org/apache/spark/sql/Dataset.html&quot;&gt;Datasets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Data (re)processing pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Spark’s Scala API could be more &lt;a href=&quot;https://github.com/typelevel/frameless&quot;&gt;idiomatic and type safe&lt;/a&gt;, it does offer a nice &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain-specific_language&quot;&gt;DSL&lt;/a&gt; that allows one to create expressive pipelines. However, it’s also not uncommon to see these jobs getting a little bit out of control in terms of readability, with many nested operations. This can lead to situations where one needs to spend some time reasoning about what’s going on and what types are being passed through all of those intermediate operators.&lt;/p&gt;
&lt;p&gt;In this article, we’ll see how to break down the aforementioned types of jobs into smaller chunks and how to compose them using &lt;a href=&quot;https://en.wikipedia.org/wiki/Functional_programming&quot;&gt;functional programming techniques&lt;/a&gt;. In order to illustrate this approach, let’s go through a simplified use case of a real system our team is responsible for. Even though the example illustrated here is implemented using Spark, these techniques could be employed with other technologies as well, like &lt;a href=&quot;http://crunch.apache.org/scrunch.html&quot;&gt;Scrunch&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Practical Example&lt;/h2&gt;
&lt;p&gt;One of the things the &lt;a href=&quot;https://developers.soundcloud.com/blog/managing-unplanned-and-support-tasks&quot;&gt;Content Team&lt;/a&gt; here at SoundCloud is responsible for is receiving content from labels and making it available to users. As part of this process, our systems need to decide which SoundCloud profile each track should be uploaded to.&lt;/p&gt;
&lt;p&gt;The way this decision process works is based on rules that are matched against specific parts of the metadata of each track. For our simplified use case, we have two rule types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One matching both artist and label names&lt;/li&gt;
&lt;li&gt;One based only on the artist name&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll assume there are no conflicting rules, i.e. there will be no two rules matching the same label/artist or artist, and the rules matching both artist and label names have higher priority than the ones matching only the artist name. Of course, things are more complex in the real world, but this should be good enough for our example. Before getting into the job itself, let’s take a look at the model:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labelId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; RuleType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; Artist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ArtistAndLabel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist-and-label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; MappingRule &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; ruleType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RuleType
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; MappingRule &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; ruleType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RuleType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ArtistAndLabel
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; MappingRule &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; ruleType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RuleType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Artist
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ruleType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeAppliedRule&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A few things could be better in this model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We could use &lt;a href=&quot;https://docs.scala-lang.org/overviews/core/value-classes.html&quot;&gt;value classes&lt;/a&gt; to wrap the primitive types like IDs and names.&lt;/li&gt;
&lt;li&gt;The &lt;code class=&quot;language-text&quot;&gt;RuleType&lt;/code&gt; could be defined as a sealed trait with case objects implementing it.&lt;/li&gt;
&lt;li&gt;The &lt;code class=&quot;language-text&quot;&gt;TrackWithAppliedRule&lt;/code&gt; could have a field of type &lt;code class=&quot;language-text&quot;&gt;MappingRule&lt;/code&gt; instead, thereby removing the need for the &lt;code class=&quot;language-text&quot;&gt;AppliedRule&lt;/code&gt; class.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, some of these ideas are rather difficult to implement with vanilla Spark. One approach to overcome these issues is by using &lt;a href=&quot;https://github.com/typelevel/frameless&quot;&gt;Frameless&lt;/a&gt;, but that would be a topic for another blog post, so let’s leave the model as it is.&lt;/p&gt;
&lt;p&gt;Moving forward, the input to the job will be five &lt;a href=&quot;https://spark.apache.org/docs/2.3.0/api/java/index.html?org/apache/spark/sql/Dataset.html&quot;&gt;datasets&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One with the tracks&lt;/li&gt;
&lt;li&gt;One with the artists&lt;/li&gt;
&lt;li&gt;One with the labels&lt;/li&gt;
&lt;li&gt;One with the artists’/labels’ rules&lt;/li&gt;
&lt;li&gt;Another one with the artists’ rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The job basically needs to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Join tracks with artists and labels&lt;/li&gt;
&lt;li&gt;Apply the artists’/labels’ rules&lt;/li&gt;
&lt;li&gt;Apply the artists’ rules&lt;/li&gt;
&lt;li&gt;Select the best rule and return a dataset of &lt;code class=&quot;language-text&quot;&gt;TrackWithAppliedRule&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Starting with the Tests&lt;/h3&gt;
&lt;p&gt;Below is a single test case that can help with better understanding the logic and that will also help with the development and refactoring of the job:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SparkSession
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;specs2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mutable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Specification
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;specs2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Scope

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RuleApplierJobSpec &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; Specification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; Context &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; Scope &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; spark &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SparkSession
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;appName&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tracks-rules-applier&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;master&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spark.sql.shuffle.partitions&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getOrCreate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; job &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; RuleApplierJob&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; label1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Label 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artist1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artist2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artist3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ruleArtist1AndLabel1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Label 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ruleArtist1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ruleArtist2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Artist 2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; track1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labelId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; track2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labelId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; track3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labelId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token string&quot;&gt;&quot;it should match rules against artist and label of each track&quot;&lt;/span&gt; in &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; Context &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; labels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toDS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artist1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toDS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artistsAndLabelsRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ruleArtist1AndLabel1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toDS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; artistsRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ruleArtist1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ruleArtist2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toDS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; tracks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toDS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistsRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collect&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toSeq

  result should containTheSameElementsAs&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Seq&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Some&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Some&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; RuleType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Title Track 3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist3&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; None&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Working and Iterating on the Job&lt;/h3&gt;
&lt;p&gt;The first version of the job works and gets the tests passing, but it’s not that readable — especially if you consider people joining a team and having to understand and maintain the job:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Dataset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RuleApplierJobV1&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          labels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistsAndLabelsRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  tracks
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracks&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artistId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;labels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token string&quot;&gt;&quot;_1.labelId&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; labels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token string&quot;&gt;&quot;_2.name&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      $&lt;span class=&quot;token string&quot;&gt;&quot;_3.name&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;left_outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token string&quot;&gt;&quot;_2.name&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;left_outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ruleArtist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ruleArtist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
      TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orElse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maybeArtistRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ruleType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A first approach to make it better is a very basic technique in programming: breaking a big problem into smaller pieces. We can do that by creating smaller functions, each one doing one part of the work:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Dataset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RuleApplierJob&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; RuleApplierJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          labels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistsAndLabelsRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; tracksWithArtists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; joinTracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artists&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; tracksWithArtistsAndLabels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; joinTracksAndArtistsWithLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; withArtistsAndLabelsRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applyArtistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtistsAndLabels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; withArtistsRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applyArtistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;withArtistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  convertToTrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;withArtistsRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; RuleApplierJob &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; joinTracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  tracks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracks&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artistId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; joinTracksAndArtistsWithLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_
  tracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;labels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token string&quot;&gt;&quot;_1.labelId&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; labels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; applyArtistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtistsAndLabels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistAndLabelRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_
  tracksWithArtistsAndLabels
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistAndLabelRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracksWithArtistsAndLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;_2.name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistAndLabelRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      tracksWithArtistsAndLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;_3.name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistAndLabelRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;left_outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;empty&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; applyArtistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_
  tracksWithRules
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;joinWith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artistRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;_2.name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; artistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;left_outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ruleArtist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ruleArtist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; convertToTrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_
  tracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maybeArtistRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        maybeArtistAndLabelRule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orElse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maybeArtistRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; AppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ruleType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you want to avoid passing around these gigantic dataset types with lots of tuples, there are basically two approaches to deal with these types of intermediate state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Defining basic alias types&lt;/li&gt;
&lt;li&gt;Wrapping them in case classes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our case, let’s go with the first approach and define some alias types to pass around:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithArtists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithArtistsAndLabels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are still some nuances in this code that are worth elaborating on.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All of these small functions need to be defined in the companion object instead of in the class itself. The reason is simple: If they are defined in the class, it will cause Spark to try to serialize the entire class, and that will lead to the infamous &lt;a href=&quot;https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/TaskNotSerializableException.scala&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;TaskNotSerializableException&lt;/code&gt;&lt;/a&gt; once the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; object is not serializable.&lt;/li&gt;
&lt;li&gt;Because Spark needs default encoders provided as implicit parameters by the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; instance, the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; needs to be passed to pretty much all of the internal functions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another point that plays on our side to help with the refactoring is the fact that the types of each one of the functions align perfectly (with exception of the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt;), i.e. the output of one function is the input of the next one:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/f3ae4/creating-readable-spark-jobs-function-pipeline.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.689991142604065%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsklEQVQoz2VSi26jMBDk/7+tUtUmKRBI0kR58KZAeNnYc7PucVepjkZjdsN4drBXVRWyLEOSJI6LsnCcpgnKMkdZ5XxO8HhcyTVuN+B4Au53/FrWWng/C8YYmMXCGss9oBWhAUUMg3VYpG9n9mdM04SREBYxWd4wDAgCnwgRHyPs4neHbXTGyybBuy+OLR0C5zPwoLOyqFEUJfKM7tOMnPGg5VtQlDVtyCmalmbNE+cRahkwmw6jbrkfoc3Avy+wUDCOjfvR7++Rm6aB7/vY7XbkAAGx/dhgu38j6DjaOAQHH/tP9qI3bNiLjlscw1fkpwhDXTlpb81uzeBfnnKisP0LSJYLFkKrb9iFVcIqBRf6OrKstm1xuVxwvV5RkZswxNd+j5H59Pcb6jhGx5uQpinzK5DnubsZKfs580xYr+v6v6CMLWJyZQY2u/iA9nDA3LTQ84yFMAxeMW9NN0r2dCaQb7Cyt4YpS76UCH/Rbd11qMgl72lPR0+6mbjXrI90NxE9+8++R0fuWJf3ncNVUEaJogifpxNijhhybIlh4ihTWTpWfFmEBR2fu+fTxSUQh38AKE385P8ty0EAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;creating readable spark jobs function pipeline&quot;
        title=&quot;creating readable spark jobs function pipeline&quot;
        src=&quot;/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/8ff1e/creating-readable-spark-jobs-function-pipeline.png&quot;
        srcset=&quot;/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/9ec3c/creating-readable-spark-jobs-function-pipeline.png 200w,
/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/c7805/creating-readable-spark-jobs-function-pipeline.png 400w,
/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/8ff1e/creating-readable-spark-jobs-function-pipeline.png 800w,
/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/6ff5e/creating-readable-spark-jobs-function-pipeline.png 1200w,
/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/2f950/creating-readable-spark-jobs-function-pipeline.png 1600w,
/blog/static/0a1fb7b19fc40cf238d9b5ea58eb19ec/f3ae4/creating-readable-spark-jobs-function-pipeline.png 2258w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So we have a scenario that function composition is almost 100 percent a good fit, except for the annoyance of also having to pass the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; around to almost all the functions. Having said that, it looks like this situation would allow the creation of a pipeline of functions if we could somehow inject the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; dependency when we want to execute it instead of providing it to each function call. This seems to be a good fit for the &lt;a href=&quot;http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html&quot;&gt;Reader Monad&lt;/a&gt; pattern:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  &amp;quot;The Reader monad represents a computation, which can read values from a
  shared environment, pass values from function to function, and execute
  sub-computations in a modified environment.&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is also a perfect fit for our scenario, as each function needs to read a value (&lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt;) from a shared environment in order to perform its computation and return its value. Both &lt;a href=&quot;http://eed3si9n.com/learning-scalaz/Reader.html&quot;&gt;Scalaz&lt;/a&gt; and &lt;a href=&quot;https://typelevel.org/cats/datatypes/kleisli.html&quot;&gt;Cats&lt;/a&gt; provide implementations of the Reader Monad. In our case, we’ll be using Cats, as it’s already part of the project we were working on, but the implementation would be quite similar with Scalaz. Here’s how the final version would look:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;cats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Reader
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Dataset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RuleApplierJob&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SparkSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; RuleApplierJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          labels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistsAndLabelsRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          artistRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  joinTracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artists&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;flatMap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;joinTracksAndArtistsWithLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;flatMap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;applyArtistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;flatMap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;applyArtistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;flatMap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;convertToTrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; RuleApplierJob &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithArtists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithArtistsAndLabels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; TracksWithRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Artist&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Option&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; joinTracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Artist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Reader&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SparkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _ &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// same as previous listing&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; joinTracksAndArtistsWithLabels&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtists&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TracksWithArtists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; labels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Reader&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SparkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TracksWithArtistsAndLabels&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spark &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// same as previous listing&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; applyArtistsAndLabelsRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithArtistsAndLabels&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TracksWithArtistsAndLabels&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistAndLabelRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtistAndLabel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Reader&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SparkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spark &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// same as previous listing&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; applyArtistRules&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; artistRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MappingRuleArtist&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Reader&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SparkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spark &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// same as previous listing&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; convertToTrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracksWithRules&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TracksWithRules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Reader&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SparkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Dataset&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithAppliedRule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spark &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// same as previous listing&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As seen above, instead of each function returning only its desired type, it returns a &lt;code class=&quot;language-text&quot;&gt;Reader&lt;/code&gt; that produces a type given a &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt;. Reader Monads are composable, so the functions can be aligned after each other, thereby creating a pipeline of functions, and only when this pipeline is triggered does the external dependency (&lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; in this case) need to be provided. The code above is much cleaner and easier to reason about! Even a newcomer to this codebase would be able to easily understand what’s going on here.&lt;/p&gt;
&lt;p&gt;Another approach to compose the functions would be using a &lt;a href=&quot;https://docs.scala-lang.org/tour/for-comprehensions.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;for comprehension&lt;/code&gt;&lt;/a&gt; instead of &lt;code class=&quot;language-text&quot;&gt;flatMap&lt;/code&gt;. Both approaches work and are quite similar, with the disadvantage that in this specific use case, one will need to give names to the intermediate values in the &lt;code class=&quot;language-text&quot;&gt;for comprehension&lt;/code&gt; solution.&lt;/p&gt;
&lt;p&gt;For completeness, it’s worth mentioning that &lt;a href=&quot;https://docs.scala-lang.org/tour/implicit-parameters.html&quot;&gt;implicit parameters&lt;/a&gt; could also be used to pass the &lt;code class=&quot;language-text&quot;&gt;SparkSession&lt;/code&gt; around, but I find that less readable and a little bit harder to reason about, as the dependencies are not very clear. Having said that, it might be a better approach in some scenarios, such as that of an external library, and it’s also a matter of preference/style.&lt;/p&gt;
&lt;p&gt;For the sake of simplicity, I decided to keep all the functions inside the same companion object, but you could also create a dedicated object for each of them instead, and the solution would end up being pretty much the same.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As companies write more and more Spark code in Scala, it’s important that we strive to bring more functional aspects to this codebase. In addition to looking for opportunities to apply these concepts as shown in this article, frameworks like &lt;a href=&quot;https://github.com/typelevel/frameless&quot;&gt;Frameless&lt;/a&gt; are also working toward creating more expressive DSLs to work with Spark, so it’s worth keeping an eye on those as well.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Getting a Team Back on Track]]></title><description><![CDATA[Sometimes, an important team that’s part of an otherwise healthy company culture starts tanking and the people on the team get frustrated and even quit. In this article, I want to share what I learned when I started to manage a team — referred to as the R Team from here on out — that had huge problems when I took over as Engineering Manager, as well as explain how I got it back on track.]]></description><link>https://developers.soundcloud.com/blog/getting-a-team-back-on-track</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/getting-a-team-back-on-track</guid><pubDate>Wed, 25 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes, an important team that’s part of an otherwise healthy company culture starts tanking and the people on the team get frustrated and even quit.&lt;/p&gt;
&lt;p&gt;In this article, I want to share what I learned when I started to manage a team — referred to as the R Team from here on out — that had huge problems when I took over as Engineering Manager, as well as explain how I got it back on track.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;I hope this post raises your awareness of some of the issues that can affect a team and that you will be able to apply them too — whether you’re a manager, an individual contributor, or just observing a team from the outside.&lt;/p&gt;
&lt;h2&gt;The TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Operating at the throughput limit of a team will tear it apart.&lt;/li&gt;
&lt;li&gt;Technical challenges and business pressure are not the problem. The problem is a lack of focus and collaboration.&lt;/li&gt;
&lt;li&gt;Limiting parallel work streams will increase productivity, team happiness, and stakeholder trust.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Beginning&lt;/h2&gt;
&lt;p&gt;The R Team was always under a lot of pressure but somehow coped with it. But then, all of a sudden, the team seriously tanked. In a very short time, one of the engineers quit and the team’s Engineering Manager went on a break and later left the company. In the meantime, two of the team’s five engineers handed in their notices. This left the team with only two engineers, and one of them was also entertaining the thought of moving on.&lt;/p&gt;
&lt;p&gt;On the product management side, it was similar. One Product Manager left, and the next one stayed for only one month. The team was behind on everything, and the backlog was infinite. Work was no fun.&lt;/p&gt;
&lt;h2&gt;What Was Broken?&lt;/h2&gt;
&lt;p&gt;Most of the time, issues piling up is what can turn a team of great and motivated engineers into a frustrated bunch of individuals. But in order to understand the full picture and to come up with a plan to recover, it is important to put everything on the table. Within the R Team, there were a few very obvious problems that we identified right away:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A huge amount of tech debt&lt;/li&gt;
&lt;li&gt;A large group of stakeholders, each of them asking the team to work on equally high-priority tasks&lt;/li&gt;
&lt;li&gt;A high amount of domain complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To put this into perspective, the amount of tech debt and domain complexity made onboarding new team members — a process that should normally take no longer than a month — a long and intense three-month journey. And because of being pressed for time, there was never time to clean up tech debt. For instance, the oldest of the team’s data pipelines was still in production alongside two generations of rewrites. The same was true for other systems that had multiple generations of rewrites in production. The backlog stretched for years.&lt;/p&gt;
&lt;p&gt;It was a lot to deal with, but even under such arduous circumstances, a motivated team could take all this as a challenge and work hard to catch up. So why didn’t this team do this? Why did the members instead feel like giving up? To find out the answer to this, I had to do some digging.&lt;/p&gt;
&lt;p&gt;After continued talks and investigation, mostly in one-on-ones, more and more issues became clear. One was that in order to cope with the competing high-priority deliverables, the team parallelized workstreams, which made the members function more like a group of specialists working on separate things as opposed to a &lt;a href=&quot;https://hbr.org/2005/07/the-discipline-of-teams&quot;&gt;team with a shared goal&lt;/a&gt;. For example, the team often had more than one epic per person in the sprint as a way of keeping all high-priority tasks going. As a side effect, this pretty much prevented effective knowledge sharing. Indeed, in order to get a complete overview of the tech stack, all team members, each with individual special knowledge, were needed to give their input.&lt;/p&gt;
&lt;p&gt;The parallelization also delayed shipping, which then eroded trust from stakeholders. To make matters worse, the business expert who was needed to clarify technical specifications during implementation was overwhelmed by repetitive, manual work. This not only made his job tedious and labor-intensive, but it also made the feedback cycle on technical questions very long.&lt;/p&gt;
&lt;p&gt;In the middle of all of this, there was a big change in the organizational structure sitting above the team, and the manager of the team’s new group started up a parallel workstream to review alternative approaches to reducing the tech debt, ultimately replacing the team’s existing technical vision.&lt;/p&gt;
&lt;p&gt;Although this was done in collaboration with the team and one member of the team was seconded into the workstream, it couldn’t help but feel like the team wasn’t trusted to solve its own problems.&lt;/p&gt;
&lt;p&gt;All these issues considered, it’s fair to say that the team members had the collective feeling of not being heard, understood, or supported by the rest of the company.&lt;/p&gt;
&lt;h2&gt;Fixing Things&lt;/h2&gt;
&lt;h3&gt;Call for Support&lt;/h3&gt;
&lt;p&gt;The R Team is absolutely critical for our business, but the team was falling apart. Engineers wanting to leave a team or even the company is a very clear signal that a fundamental change is needed and things can’t continue in the same way. It was also a signal that we failed to take action on earlier signs. To deal with this, we knew the situation needed to be escalated through the whole company. And we escalated the hell out of it: within stakeholders meetings, in executive updates, and with whoever asked about the team.&lt;/p&gt;
&lt;p&gt;For starters, we began to look for people internally, and thankfully, we found great people to join the team. Both senior and junior engineers stepped up and wanted to help. And in an effort to bring the team back to its old capacity, we opened a job ad.&lt;/p&gt;
&lt;p&gt;We also got first-hand support from a Senior Principal Engineer who joined the team for more than a quarter. He kick-started migrating to a new tech stack, helped with forming a new tech vision, and provided guidance. All of this aided in relieving some of the pressure on the team. In addition to that, we found an amazing Product Manager whose tasks were to clearly prioritize projects and chase down simplifications. The situation of the team was being addressed, and the members felt like they were being listened to by the rest of the company. Support had arrived.&lt;/p&gt;
&lt;p&gt;When scaling the team, we also made it more diverse. We now have a team in which three out of seven engineers are women and the ratio of junior engineers to senior engineers is also balanced. This change automatically brought in new, differing, and fresh perspectives. It also helped the team in discussing problems more holistically; no questions remained unasked. This increased knowledge sharing and collaboration, and this alone already began shifting the team’s mood.&lt;/p&gt;
&lt;h3&gt;New Trust&lt;/h3&gt;
&lt;p&gt;In my opinion, the most important asset of a team is a shared goal in form of team vision, and the vision must come from within the team. The team members are the experts, and only the experts will be able to come up with the right plan. And as a bonus, because the entire team is involved in forming the plan, each team member naturally buys into it from the very beginning.&lt;/p&gt;
&lt;p&gt;But before a team can start discussing great plans, it needs be able to discuss effectively. So let’s get back to forming the vision later and instead focus on communication.&lt;/p&gt;
&lt;p&gt;The foundation of team discussions in which everyone speaks up and all options are being brought up is &lt;a href=&quot;https://www.tablegroup.com/teamwork&quot;&gt;trust&lt;/a&gt;. This is not only a must in order to come up with a long-term team vision, but it’s also absolutely necessary to effectively discuss small design decisions ad hoc. The fascinating simple mechanic here is: Get people to open up and share their story, and see how mutual understanding and trust grows. There are plenty of exercises to foster trust within a group, and a lot of them work. For us, two exercises did the trick: “Paint a picture about yourself,” in which we explained where we’re coming from; and “Ways of working,” in which we discussed how we worked — more specifically when we’re the most productive, what’s most important to us, and what’s most frustrating to us individually.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/8179c/back-on-track-paint.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAGm1GJIf//EABcQAAMBAAAAAAAAAAAAAAAAAAIQMQP/2gAIAQEAAQUCqOrS/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAaEAACAgMAAAAAAAAAAAAAAAAAEQEhQXGx/9oACAEBAAE/IUFCd0SpowiDif/aAAwDAQACAAMAAAAQmx//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAcEAEAAgMAAwAAAAAAAAAAAAABABEhMUFRYcH/2gAIAQEAAT8QaOh6guppkthXDh+mAZfO47svTTBZrgJ//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;back on track paint&quot;
        title=&quot;back on track paint&quot;
        src=&quot;/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/a296c/back-on-track-paint.jpg&quot;
        srcset=&quot;/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/f544b/back-on-track-paint.jpg 200w,
/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/41689/back-on-track-paint.jpg 400w,
/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/a296c/back-on-track-paint.jpg 800w,
/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/c35de/back-on-track-paint.jpg 1200w,
/blog/static/2ae12e06542276a6c2d0a3c1d07c9a05/8179c/back-on-track-paint.jpg 1600w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And of course, offsites were instrumental in building togetherness within the team. Taking time away from project work to have full-day offsite meetings, which are primarily focused on vague trust building, might first seem like a big investment, but these paid off immediately. Everyone got to know their teammates better, and as a result, both communication and collaboration improved almost instantaneously.&lt;/p&gt;
&lt;h3&gt;Defining a Vision&lt;/h3&gt;
&lt;p&gt;In a couple of workshops, we discussed the biggest pain points the team was facing. We started to put together a “Most Wanted” list of the worst kind of tech debt. In separate sessions, we discussed an ideal solution to our domain challenges and came to similar conclusions, albeit from differing angles. We decided we would weigh work in planning sessions to determine if it would bring us closer to our tech vision or help eliminate painful tech debt like slow, flaky, or tedious manual processes.&lt;/p&gt;
&lt;p&gt;But how do we cover what we actually need to deliver to our business? It turned out that just two metrics needed to be visualized to measure our progress: First, a view of how well we were doing in hitting monthly deadlines to generate data outputs (pictured). And second, the number of completed deliverables versus the total number of deliverables. This made our progress and our targets very clear.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/39e8c349e19c63ba7707287e28c46895/92869/back-on-track-kpi.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.08733624454149%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACWklEQVQoz22SyU8UURDG5++TC2KEYYZhSQYE0YMBvHIgBq4cTEwwGi9EORCIzMosCJOJMS4RBkJA5GAMMWGZBbqZ7tfvZ/XCOBpfUl1VXfW+el9Vher1OicnDaoXdWzbwj1aa/fbEs8PpN32fe8GtnWN4ziEXHdiAqo1DwvL9nXjElZWoenXwNE+PH+V8o9taU5+nlGrVQm5ieOjFpVPBlybEja4Ojd5dN+k65bJYcX9Z6JNA3VlYl+KbRjiB1pYKQFpNAyazSYhpRyG4xAJKxo1xXpGcW9Y0XNXEYs69McUz57aOLYSYMWTGUX13Le1/NNCs/2EXNTZ2V903YGDAxga0nR2Qm9E0z+gPN3RAcfHmsNDh8FBzUXVJetgCVW3bzrorSteD8vld8zNfRMATSQqYL3yYgFaWvogBSymps4YEKCeHojF4PS0/U3BoIJhepS3NjfZKCQYHanTF7F58fwLI/FLEm83ePXyM9l0jvGxqheLRS0eT1qcn1ok15qYht0C8wBdypsCmM1kWV4uE48bJNY2ePP6PZlMQYqlyefWSaeK9PUpurshHHZ4+EDTdRsuvH7qYIsCyltbJdblUqGQZ0D6trJSplQSoEKBXK5ILp8nLzIz80NAhXoYolHF6JjJ5KTshUEL1AMsFoskk0lSqSzT099ZXc3L61Kk0xnRGdFpT3K5JIuLJRYWPkpOgUQiy/z8Dqap/vTQpby/v0+lUmF3d1fsHU//TyoVN74t2/CVvT03b4ejo22UstuHov4ZvR+wLFlYidm23bJ9cXBX7yb/Zg1v7v4GYmNKvbg3X9EAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;back on track kpi&quot;
        title=&quot;back on track kpi&quot;
        src=&quot;/blog/static/39e8c349e19c63ba7707287e28c46895/8ff1e/back-on-track-kpi.png&quot;
        srcset=&quot;/blog/static/39e8c349e19c63ba7707287e28c46895/9ec3c/back-on-track-kpi.png 200w,
/blog/static/39e8c349e19c63ba7707287e28c46895/c7805/back-on-track-kpi.png 400w,
/blog/static/39e8c349e19c63ba7707287e28c46895/8ff1e/back-on-track-kpi.png 800w,
/blog/static/39e8c349e19c63ba7707287e28c46895/6ff5e/back-on-track-kpi.png 1200w,
/blog/static/39e8c349e19c63ba7707287e28c46895/2f950/back-on-track-kpi.png 1600w,
/blog/static/39e8c349e19c63ba7707287e28c46895/92869/back-on-track-kpi.png 2290w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Another tool that turned out to be super useful was a complete system diagram, which functioned not only as a snapshot in time, but also as a living document we would actively maintain. We would even color components we wanted to get rid of in red. And in order to visualize progress we made, we cloned snapshots of the drawing every quarter. We also linked all the components to repositories or other resources. If anything was unclear or missing, we often discussed this in the comments of the diagram.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d151cac99d8b4f51d1e43e9726691bcd/7ac7b/back-on-track-system.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 79.20858484238767%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAADYUlEQVQ4yy2U527bSBSF+f4vtYuF7USyKpvY6wyb2NTclOTLlbE/LihQmHNP4xhtGaGzgM/K5Fr7zIXFkNvclMVchYyFy7Xccxo7uvmd/nRDtwPVcZLfbxznG/P5RlFWFNURoysD2tSSQzsuxY4ptzhlG07JkiY7UBcRU2Ezdpq4vVK0M+10ox0vqG7meJYlAu4fQvTxhKGyiCaxKeID53zLEP5kDp8Yvf+4JAumbMc5WzP3DdV4RjdH6uP4zbAZzt8sh9OFrNTCfMTQRUKduNxKk3dhWcc70t2/nKNnumjNFC/5XcqSoaUWgLYf5GCPkulkQSvTzRfsWGGG6iE5+pY8lQvmckmehFSxxb3aMJQubSJ25BuRXNOdPuTwlaaf6GRaYanEz0HepbkiSkuMXjwc0y06ekVFK+poQxcuOR2euIYv9JlJG23p6oK6P8mMNPNEPY6o41H8nBnO8r4TS7oWYy4dCWMroDsq16S0t7TBiqv/wk3kTtmWOd2QZzZ5qQhLH7+xCRsXV2wKGoewPRB3PtHxgPFLpH2pBfd8waf9zJf3wlf+gzlbiXSbOX7lLXlCiXxVNcQqIa9LyrqSp5aqaHJVUGhNVCcYb3otgK98KmEpbEepyDV85eS+MiQbeuVJN10B8LDyBL9KKKtSQBSlgGQyqpIONg1Rm2F8CCCVsBDAJC/w44xJPL3nP7mKj+/xgj+iQrc2G+noPjeljylm4mDJ+LlDUPnkTUncRBiThNA70j93y+RKTQ5bmt0zzeofqtULw/4Z1JKqtfD6PVZps04UyzBlEaRs7B2u3nKUgFSbYzz6N8YP43f/P/ffX8oU/KBxn9HOkjrzCFOPvbQgbA7oh3dNQaIikjLBUw6pjglrCeW9srlUB67a4SRzrgLetMlv8fVerFBZSJgpbMdjGyzxawf9CEKnRNoTP3MJJyWpYrzKwvis9hKEI32TRJO1fC1ruRhM8XQvYa25iIKHily7OM1eknRFvqIQEL+2pUIWByHlaBtbC+Bd9D/Kq0LzG/AzE99UyPC4ZdIFKg1EcoAXWFhqQ1zHxMIuFImWnLPyNbYstOWW2hemAEop77XJWV7+liS/ZOt74/EhXt1l+y+ZP/J/00txe09kJRJCiasyglTY9kpuGelllwlbl7/UM4w5cSPB9AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;back on track system&quot;
        title=&quot;back on track system&quot;
        src=&quot;/blog/static/d151cac99d8b4f51d1e43e9726691bcd/8ff1e/back-on-track-system.png&quot;
        srcset=&quot;/blog/static/d151cac99d8b4f51d1e43e9726691bcd/9ec3c/back-on-track-system.png 200w,
/blog/static/d151cac99d8b4f51d1e43e9726691bcd/c7805/back-on-track-system.png 400w,
/blog/static/d151cac99d8b4f51d1e43e9726691bcd/8ff1e/back-on-track-system.png 800w,
/blog/static/d151cac99d8b4f51d1e43e9726691bcd/6ff5e/back-on-track-system.png 1200w,
/blog/static/d151cac99d8b4f51d1e43e9726691bcd/7ac7b/back-on-track-system.png 1491w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now we had a go-to visualization that everyone could use to help in understanding the big picture. And we still use this diagram today — for example, during sprint planning, in order to discuss upcoming epics and their dependencies and changes in relation to the rest of our infrastructure.&lt;/p&gt;
&lt;h3&gt;Automate All the Things!&lt;/h3&gt;
&lt;p&gt;The productivity of the R Team relies upon a responsive business expert to clarify all kinds of questions, but as mentioned earlier, the expert was often too busy with manual tasks, like creating reports, to respond to requests. He was even trying to help the team by manually delivering tasks, but this almost led to a deadlock situation. He was a &lt;a href=&quot;https://en.wikipedia.org/wiki/Theory_of_constraints&quot;&gt;constraint in the team’s value chain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Automating the tasks on his plate unblocked us and freed up more than 30 hours of his time each month. In addition, it also increased quality and decreased the total time of the related business processes. Computers were invented for the purpose of automating repetitive tasks, and doing so in this situation made work on the business side much more meaningful. Together with business, we now tightly collaborate on the epic at hand. With the shorter feedback cycle, we’re much faster. Additionally, the business side now has more bandwidth to talk to external partners directly and manage their relationships.&lt;/p&gt;
&lt;h3&gt;Ruthless Prioritization&lt;/h3&gt;
&lt;p&gt;Even if determining top priorities may seem impossible at times, it can always be done. It must always be done. Depending upon your team size, you also need to limit the amount of work streams. As a rule of thumb, the number of team members divided by two is the maximum number of work streams that should be in progress.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/89046fde0dff8d7fb8593feec03e0863/04090/back-on-track-max.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.45138888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAi0lEQVQI111PQQrEIAzs/x9WihUKbVHBg0cPxQoWpIo6i2G7lB0YEjJJJhnwRWvtF0spqLXijafuvYe1luJ1XTjPE8dxwDmHlBKG+74xjiOklDDGQCkFxhj2fce6rqRprWmwL9m2DdM0QQiBeZ6JnHMsy0L60J1jjMg5E7vLk3ezR+sX/zOEQP3vrz6yGOL2UVf0JAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;back on track max&quot;
        title=&quot;back on track max&quot;
        src=&quot;/blog/static/89046fde0dff8d7fb8593feec03e0863/8ff1e/back-on-track-max.png&quot;
        srcset=&quot;/blog/static/89046fde0dff8d7fb8593feec03e0863/9ec3c/back-on-track-max.png 200w,
/blog/static/89046fde0dff8d7fb8593feec03e0863/c7805/back-on-track-max.png 400w,
/blog/static/89046fde0dff8d7fb8593feec03e0863/8ff1e/back-on-track-max.png 800w,
/blog/static/89046fde0dff8d7fb8593feec03e0863/04090/back-on-track-max.png 1152w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There is a long list of reasons why this is absolutely necessary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For individuals, working sequentially is faster because it reduces context switching.&lt;/li&gt;
&lt;li&gt;Collaboration is the most effective knowledge sharing. Ignoring that leads to self-imposed fires because any individual team member may eventually go on vacation, go on parental leave or sick leave, or leave the team altogether. Ideally, you actually want at least three engineers to have enough context to work on each task.&lt;/li&gt;
&lt;li&gt;It has a direct impact on team happiness. It allows people to support each other by discussing and solving problems together and to ultimately feel like they’re part of a great team.&lt;/li&gt;
&lt;li&gt;Wrapping up work-in-progress as quickly as possible helps with managing stakeholder expectations and gives the team a sense of accomplishment and progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, this all may sound like common sense. However, it’s really easy to fall into the trap of committing to too many things when we are under pressure, both individually and as teams. Taking on more work when there’s already too much is neither healthy nor effective.&lt;/p&gt;
&lt;p&gt;Stakeholder conversations can be tough because you need to push for the exact order of the top priorities and the team must only work on a few of them at a time. This might be quite a shock for the stakeholders. But by doing so, the team will actually start to ship efficiently again. And by showing you can complete work efficiently, you regain trust with your stakeholders. In short, building trust with stakeholders is fascinatingly simple: Be very clear with the progress of all your projects and deliver them one by one.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With the right approach, the R Team’s morale was recovered in a very short time. And after only four months, the team was back on track delivering on business requirements while chipping away at tech debt. And none of this was magic.&lt;/p&gt;
&lt;p&gt;As stated in the beginning, operating at the throughput limit of a team will tear it apart. A team with 99 percent utilization will actually be slower than a team with some breathing room. And as tech debt piles up and knowledge silos develop further, the team will start to struggle.&lt;/p&gt;
&lt;p&gt;Technical challenges and business pressure are also not the problem. We all want to work on important and challenging problems and make a great impact. The problem is a lack of focus and collaboration.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Defining a Role with a Retrospective Activity]]></title><description><![CDATA[Agile retrospectives are a widely used practice within engineering teams. They provide teams with a way to reflect on how they work and become better at what they do. One of the main benefits of retrospectives is that they empower teams to define and make changes by analyzing what happened in an iteration and by determining what can be improved moving forward. Here at SoundCloud, we hold retrospectives at the end of every iteration (every two weeks), and we often do them at the end of projects as well.]]></description><link>https://developers.soundcloud.com/blog/defining-a-role-with-a-retrospective-activity</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/defining-a-role-with-a-retrospective-activity</guid><pubDate>Fri, 22 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Agile retrospectives are a widely used practice within engineering teams. They provide teams with a way to reflect on how they work and become better at what they do. One of the main benefits of retrospectives is that they empower teams to define and make changes by analyzing what happened in an iteration and by determining what can be improved moving forward. Here at SoundCloud, we hold retrospectives at the end of every iteration (every two weeks), and we often do them at the end of projects as well.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;In this blog post, I’ll share how I used a well-known retrospective activity with my team to define the responsibility of a role.&lt;/p&gt;
&lt;p&gt;A few months ago, our iOS Tech Lead took on a new role, which left the team with an open position to fill. Usually, the person in a particular role is the one to define the tasks and expectations of that role as part of a handover. This can be done by writing down what they did and what they were supposed to do, but this process can be somewhat subjective and error-prone.&lt;/p&gt;
&lt;p&gt;In this case, because the iOS Tech Lead was involved in many things, some of which weren’t directly related to the team or the platform, there weren’t clear boundaries for the role. That said, both the team and the hiring manager agreed that the definition of the role needed to be updated.&lt;/p&gt;
&lt;p&gt;As the Engineering Manager of the Consumer iOS Team, I was asked to come up with a proposal on how to rewrite this role definition together with the iOS Collective. The iOS Collective consists of engineers from the Consumer Team, the Media Streaming Team, and the Core Clients Team. What a great challenge!&lt;/p&gt;
&lt;p&gt;I began by asking myself what a good format was for creating a safe space for gathering peoples’ opinions, facilitating discussion, and finding out what the team needed from a Tech Lead. I knew it was important to have the opportunity to bring up new ideas and responsibilities as well, since the team has changed and evolved over time.&lt;/p&gt;
&lt;p&gt;I invited everyone from the iOS Collective to come prepared for a one-hour meeting and give me their answers to the following questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is good in the role, and do you see a value in it?&lt;/li&gt;
&lt;li&gt;Is there something new you want to see in this role?&lt;/li&gt;
&lt;li&gt;Is there something that is being done that you would rather see less of?&lt;/li&gt;
&lt;li&gt;Is there something that is being done that you think would bring more value if it is done more?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a modified version of the &lt;a href=&quot;http://www.funretrospectives.com/kalm-keep-add-more-less/&quot;&gt;KALM&lt;/a&gt; — Keep, Add, More and Less retrospective format, which is used for having a conversation about current activities and their values as perceived by team members.&lt;/p&gt;
&lt;p&gt;For our meeting, we used Post-it Notes to give answers to each of the four questions. Everyone came with their notes, organized by color, and added them to the whiteboard. Since we had a limited amount of time and there were nine of us, each giving around 10 answers for the questions, we knew we couldn’t discuss everything. Instead, we focused on the answers that people did not agree on or that were not clear.&lt;/p&gt;
&lt;p&gt;We managed to discuss and clarify the topics that stood out as things people didn’t agree on or were confused about, and we ended up with the following, not counting the duplicates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep: 26 topics&lt;/li&gt;
&lt;li&gt;Add: 12 topics&lt;/li&gt;
&lt;li&gt;Less: 5 topics&lt;/li&gt;
&lt;li&gt;More: 11 topics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that there were so many responsibilities to be kept and almost the same number to be added and done more justified the need of the role definition. It felt really good to being able to do this important work together and have the assurance that the next Tech Lead would be set up for a good start.&lt;/p&gt;
&lt;p&gt;In addition to using this information to define the role of the new Tech Lead, the results were given to the former Tech Lead as feedback on the work they did in that role. It was also a chance to realize what the team was happy with, what was not necessary, what they were missing, and what they wished was done more often.&lt;/p&gt;
&lt;p&gt;Using the output of this session, together with the hiring manager and the former Tech Lead, we defined the responsibilities of what is now called the iOS Platform Lead.&lt;/p&gt;
&lt;p&gt;Involving the team in the process resulted in a renewed sense of trust, which reinforced morale. It also gave everyone confidence that the new iOS Platform Lead can work in a way that meets the needs of everyone. This is in addition to having well-defined expectations for their role, which is a necessity for performing well and growing.&lt;/p&gt;
&lt;h4&gt;Roles of the iOS Platform Lead&lt;/h4&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/c3ac2/ios-platform-lead-role.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 121.07396149949341%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAYABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAH2rKbBzBsH/8QAGBAAAgMAAAAAAAAAAAAAAAAAEEEBITH/2gAIAQEAAQUCYnQ6H//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABgQAAIDAAAAAAAAAAAAAAAAAAAxARAg/9oACAEBAAY/AhzlCr//xAAaEAEBAQADAQAAAAAAAAAAAAABABEhQVGB/9oACAEBAAE/IVBcp9uI09h2c+QD2xOa3VsAeX//2gAMAwEAAgADAAAAEEDHPP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EABwQAQACAgMBAAAAAAAAAAAAAAEAETGRIUGBYf/aAAgBAQABPxAQsX0Ag8Gz0hRl7DsqlurMQznwYQM7j2pAuCkYXTc//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ios platform lead role&quot;
        title=&quot;ios platform lead role&quot;
        src=&quot;/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/a296c/ios-platform-lead-role.jpg&quot;
        srcset=&quot;/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/f544b/ios-platform-lead-role.jpg 200w,
/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/41689/ios-platform-lead-role.jpg 400w,
/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/a296c/ios-platform-lead-role.jpg 800w,
/blog/static/b2ffc13bcb87da9f48c6c4edc51cb26e/c3ac2/ios-platform-lead-role.jpg 987w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Project Necromancy]]></title><description><![CDATA[Or, how to raise a project from the dead with tools you probably have lying around at home.]]></description><link>https://developers.soundcloud.com/blog/project-necromancy</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/project-necromancy</guid><pubDate>Tue, 05 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Or, how to raise a project from the dead with tools you probably have lying around at home.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;In a perfect world, every project would flow smoothly — from requirement gathering, to implementation, to acceptance by stakeholders, to launch. Sadly, we don’t live in a perfect world. Sometimes projects hit bumps in the road due to changing business requirements, market conditions, or a host of other factors. This post describes our experiences managing a project that was deprioritized during implementation and lay dormant for an extended period of time before resuming in a different form.&lt;/p&gt;
&lt;h2&gt;The Life and Death of Project Mark I&lt;/h2&gt;
&lt;p&gt;The original incarnation of the project in question would have added a feature to an internal tool with significant use across different areas of SoundCloud.&lt;/p&gt;
&lt;p&gt;For this project, we added a new microservice to our backend architecture. We deployed and integrated this service without any functionality to begin with, and then we set about building (though not yet deploying) the service’s feature set.&lt;/p&gt;
&lt;p&gt;We suffered a minor setback in feature development when a crucial, overlooked edge case came to light and forced a partial redesign of one service component. This resulted in our having to throw away the code we’d written for the component and start again. It was not a significant development in and of itself, but it later informed our thinking about this component. We felt we’d learned an important lesson from the initial mistake and any further changes to the component would risk reintroducing the design flaw.&lt;/p&gt;
&lt;p&gt;Otherwise, the project was going fine… Until the day it was suddenly canceled. Our product roadmap had shifted. The functionality we were building was no longer important. The team’s energy needed to focus elsewhere.&lt;/p&gt;
&lt;p&gt;We discussed removing our new, currently non-functional service altogether, but we decided to leave it in place. After all, it wasn’t doing any harm, and perhaps the project would resume one day. We also preserved our partially finished feature code in a git branch, just in case.&lt;/p&gt;
&lt;p&gt;Then we moved on and largely forgot about our seemingly doomed project.&lt;/p&gt;
&lt;h2&gt;Interlude&lt;/h2&gt;
&lt;p&gt;The next several months saw our team merge with another team and several individual team members move to unrelated areas within SoundCloud. Meanwhile, our product roadmap continued to evolve, and the functionality we’d started to deliver with Project Mark I, previously unimportant, eventually became important again. Or rather, certain aspects of the functionality became important again.&lt;/p&gt;
&lt;p&gt;Four months after the original project’s cancellation, we were ready to resume developing a subset of its features with a team that contained almost none of the original engineers.&lt;/p&gt;
&lt;h2&gt;The Project Reanimated&lt;/h2&gt;
&lt;p&gt;We had to make a decision. Should Project Mark II build on the work we’d done for Project Mark I or not? Project Mark II contained only some of the planned Mark I features. Was there enough commonality to resume the old project, or would it be easier to start afresh?&lt;/p&gt;
&lt;p&gt;We decided to reuse as much of the Mark I code and documentation as possible. And after an abbreviated requirements phase, we set to work building Mark II on top of the existing Mark I codebase and data model.&lt;/p&gt;
&lt;p&gt;Picking up unfinished code written by engineers who were no longer on the team presented challenges, but not the typical challenges of working with legacy code. We had no problems with documentation, technical debt, or understanding the existing code. Instead, we developed a cognitive anchor effect around choices the previous engineers had made. We were reluctant to question design decisions because: “They must have done that for good reason.” This was especially the case given that we knew an earlier design mistake had necessitated a partial code rework. We thought we were incorporating earlier learnings by leaving things as they were and that we risked repeating the earlier mistake if we changed things.&lt;/p&gt;
&lt;p&gt;The engineers who wrote the initial code were all still at SoundCloud and would have been happy to answer questions, but they were also busy with their new teams, and we didn’t want to bother them. As it turned out, we should have bothered them more.&lt;/p&gt;
&lt;p&gt;Another group we should have bothered more was our set of stakeholders in SoundCloud’s product organization. This became apparent when we demonstrated Mark II’s functionality to them only near the end of the project, and at that demo, learned a crucial fact: a major feature we’d built for them was not needed. Unfortunately, the concept behind this feature was “baked into” the data model we’d reused, unchanged from the original Mark I project where it had been a hard requirement. As we’d failed to question the original data model, we’d also failed to question the need for this complicated feature that we had spent a considerable amount of time building.&lt;/p&gt;
&lt;p&gt;Oops.&lt;/p&gt;
&lt;p&gt;It didn’t make sense to support this feature if it wasn’t needed, especially as it complicated the user interface. We therefore made the somewhat painful decision to push back our launch while we removed the feature and simplified the data model. While the delay was frustrating, we felt it was important to deliver the right project, even if that meant delivering later. Today, Project Mark II has many happy users, so we believe we made the right decision.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;Difficult or frustrating situations nevertheless yield valuable knowledge for the future. That said, here are some of the things we discovered over the lifetime of these projects.&lt;/p&gt;
&lt;h3&gt;Reanimated Projects Are Changed Projects&lt;/h3&gt;
&lt;p&gt;We thought of Project Mark II as a direct continuation of Project Mark I. This was only partially accurate. The decision to reuse the Mark I code and data model introduced an implicit assumption into Mark II: “When in doubt, old decisions are right.” While reusing the existing work certainly saved us substantial time and effort, the cognitive bias against questioning the existing work also cost us substantial time and effort.&lt;/p&gt;
&lt;h3&gt;Requirements Are Even More Important Than Usual&lt;/h3&gt;
&lt;p&gt;Getting requirements right is important in any project. It’s also hard, and it’s even harder when the requirements have a high chance of becoming outdated or being misunderstood.&lt;/p&gt;
&lt;p&gt;When we started Project Mark II, we revalidated some of the Mark I requirements, but we should have rerun the project’s entire requirements phase afresh. This would have forced us to examine requirements that had already become code. It would have also forced us to engage with our stakeholders, who were scattered across multiple teams and multiple geographic locations, and to have conversations we didn’t know we needed to have.&lt;/p&gt;
&lt;h3&gt;Show Your Work&lt;/h3&gt;
&lt;p&gt;Finally, given the project’s unusual lifecycle, we should have demoed our progress much sooner than we did. We don’t generally do sprint demos in our team, but in this case, it would have saved us a lot of effort.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;When resurrecting an unfinished, dormant project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Revisit your requirements. All of them.&lt;/li&gt;
&lt;li&gt;Revisit your existing code. All of it.&lt;/li&gt;
&lt;li&gt;Be conscious of cognitive biases that say, “But someone decided that earlier.”&lt;/li&gt;
&lt;li&gt;Adopt a flexible mindset. Be prepared to change course.&lt;/li&gt;
&lt;li&gt;Communicate with stakeholders even more than usual.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Engineering Levels at SoundCloud]]></title><description><![CDATA[An absolutely crucial part of the experience of being an engineer at SoundCloud is learning and growing as a person. Pretty much everyone we hire mentions this aspect as one of their main motivations for joining the company. And while retaining highly talented and motivated people and helping them develop is naturally valuable for SoundCloud as a company, it’s also profoundly beneficial for the employees themselves.]]></description><link>https://developers.soundcloud.com/blog/engineering-levels</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/engineering-levels</guid><pubDate>Thu, 24 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An absolutely crucial part of the experience of being an engineer at SoundCloud is learning and growing as a person. Pretty much everyone we hire mentions this aspect as one of their main motivations for joining the company. And while retaining highly talented and motivated people and helping them develop is naturally valuable for SoundCloud as a company, it’s also profoundly beneficial for the employees themselves.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Internally, we have programs that exist to help our engineers grow. These include providing a budget for conferences and courses, organizing tech talks and meetups, and coordinating with external experts to host special workshops. We put a lot of effort into creating an environment where people are encouraged to improve. It’s a mindset — part of our culture. We keep leveling up everything: ourselves, our teams, our systems, our processes. Every day is an opportunity to make something work a bit better.&lt;/p&gt;
&lt;p&gt;The other side of this story is that SoundCloud also has to recognize this growth. We want to embrace people who are making great progress, and we also want to make sure we keep paying them fairly as their market value grows alongside their skills and expertise.&lt;/p&gt;
&lt;h2&gt;Why Do We Have Levels?&lt;/h2&gt;
&lt;p&gt;To provide clarity on what our understanding of growth is, we have a level system (as many other companies do). This system defines our engineering culture by giving guidance to people on how to increase their impact within SoundCloud, facilitating career discussions between engineers and their managers, providing a framework for setting salaries, and last but not least, encouraging and rewarding certain behaviors.&lt;/p&gt;
&lt;p&gt;Transparency around everyone’s level at a company helps people identify their role models. Additionally, an explicit level structure is beneficial for people who are underrepresented, as non-explicit hierarchies tend to favor the people who form the majority (which, most of the time, is white men).&lt;/p&gt;
&lt;h2&gt;How Do We Set Levels and How Do We Use Them?&lt;/h2&gt;
&lt;p&gt;Our leveling process begins as early as a prospective new employee’s interviews. There are three key factors that help us assess the proper level someone enters the company at. We start with their past experience — how long they have been in the industry and what kind of companies they have worked for. Second, we review the interview panel’s feedback, which helps us understand how the candidate could contribute to the team. Finally, we take into consideration a candidate’s personal assessment of which level they belong at.&lt;/p&gt;
&lt;p&gt;With every candidate, we actively work against any unconscious biases to ensure that we make a fair and accurate decision. For example, &lt;a href=&quot;http://www.pnas.org/content/111/12/4403&quot;&gt;research shows&lt;/a&gt; that employers in science tend to underestimate women’s seniority and market value. This is the kind of bias that we are committed to acting against.&lt;/p&gt;
&lt;p&gt;Once someone has joined the company, we set expectations to match their level. It’s important to emphasize that even though being on a higher level comes with a higher salary, it also sets higher expectations.&lt;/p&gt;
&lt;p&gt;When someone has clearly grown, picked up new responsibilities, and through this, increased their impact, they should be promoted to the next level. Currently, we have a process where someone who wants to be promoted puts together a promotion case accompanied by feedback from peers with whom they work closely. People giving feedback are asked to use the document that defines each level to reason about skills and performance. Then a promotion panel, made up of a diverse group of SoundClouders, convenes twice a year to make decisions about each individual case.&lt;/p&gt;
&lt;p&gt;Regardless of an employee’s level, managers regularly meet with engineers in private one-to-one meetings to discuss how they are doing. The content of these discussions is based on the engineering level definitions, and the meetings themselves provide guidance about which areas an engineer is doing well in and where they need to improve. Conversations like this help them to be successful at the company and reach the next level.&lt;/p&gt;
&lt;h2&gt;What Is Expected at Each Level?&lt;/h2&gt;
&lt;p&gt;We have five levels for engineers at SoundCloud. Here is a short summary of them and what they entail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Level 1&lt;/strong&gt; — The individual scale&lt;br /&gt;
Focusing on themselves and improving their own skills. Successfully delivering as part of a team with the help of other team members.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level 2&lt;/strong&gt; — The team scale&lt;br /&gt;
Focusing on their team and improving the team’s efficiency and productivity. Successfully driving a team project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level 3&lt;/strong&gt; — The area scale&lt;br /&gt;
Focusing on their own team and some neighboring teams and improving collaboration between these teams, e.g. successfully driving a cross-team project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level 4&lt;/strong&gt; — The organization scale&lt;br /&gt;
Having an impact on the entire engineering organization. Improving efficiency and collaboration across the organization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level 5&lt;/strong&gt; — The company scale&lt;br /&gt;
Having an impact on the entire company. Driving cross-organization initiatives, and leveling up SoundCloud as a business.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To create the official descriptions of expectation for an engineer at each level, we collected all the behaviors we desire in an engineer and assigned them to five dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Results/Delivery&lt;/li&gt;
&lt;li&gt;Behavior/Mindset&lt;/li&gt;
&lt;li&gt;Tech Skills/Mastery&lt;/li&gt;
&lt;li&gt;Influence/Visibility&lt;/li&gt;
&lt;li&gt;Communication/Collaboration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s important that this list of behaviors is not used as a checklist; one does not need to tick all the boxes to be eligible for a promotion. Additionally, many of them are not explicit expectations, but rather indicators, and we are often convinced that someone has reached a particular level if we see most of these indicators in practice.&lt;/p&gt;
&lt;p&gt;Here is the detailed view of these levels:&lt;/p&gt;
&lt;iframe style=&quot;width: 100%; height: 400px;&quot; src=&quot;https://docs.google.com/spreadsheets/d/e/2PACX-1vRM9ubTFMAG8wktk25l6thTwSHi4b-bc7JD3mPtQ6z0CLF8QwgOtjJcOghds0Bn8bs4EdALVDVgzekg/pubhtml?widget=true&amp;amp;headers=false&quot;&gt;&lt;/iframe&gt;
&lt;hr /&gt;
&lt;p&gt;We are proud of our engineering culture here at SoundCloud and the many steps we have taken to grow it, and we’ve found that the definitions of our engineering levels are the best way to capture and document it. As our culture evolves, we will continue to update the level definitions in order to reflect these changes. This ensures that we can keep providing our engineers with clear definitions of how to be successful at their jobs.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Keeping Counts In Sync]]></title><description><![CDATA[Track play counts are essential for providing a good creator experience on the SoundCloud platform. They not only help creators keep track of their most popular songs, but they also give creators a better understanding of their fanbase and global impact. This post is a continuation of an earlier post that discussed what we do at SoundCloud to ensure creators get their play stats (along with their other stats), both reliably and in real time.]]></description><link>https://developers.soundcloud.com/blog/keeping-counts-in-sync</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/keeping-counts-in-sync</guid><pubDate>Fri, 11 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Track play counts are essential for providing a good creator experience on the SoundCloud platform. They not only help creators keep track of their most popular songs, but they also give creators a better understanding of their fanbase and global impact. This post is a continuation of an earlier post that discussed what we do at SoundCloud to ensure creators get their play stats (along with their other stats), both reliably and in real time.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;A long time has passed since we shared about the challenges of and our approach to &lt;a href=&quot;https://developers.soundcloud.com/blog/real-time-counts-with-stitch&quot;&gt;real-time stats&lt;/a&gt;. Since then, we’ve had to expand our infrastructure to cope with the continuously growing stream of events representing plays, likes, downloads, and other important social signals that we’d like to count and present to creators. As a result, what might seem like a simple counting system is actually a cornucopia of services orchestrated to power features like creator statistics, reporting, search, and recommendations. The list goes on and on.&lt;/p&gt;
&lt;h1&gt;&lt;img src=&quot;/blog/bb110ebe55e84aad2c7f8a59340e75ee/bubble.svg&quot; alt=&quot;problem&quot;&gt; What We Talk about When We Talk about Counts and Statistics&lt;/h1&gt;
&lt;p&gt;The main challenge for a statistics powering system is that we not only need to present summed up totals for arbitrary time ranges, but we also need to allow creators to dig into these summed time ranges with dynamic granularity. For instance, when someone is interested in a week of play counts, aside from displaying the totals for that week, they might like to see the daily performance as well. Ideally, the total of those days should add up to the total shown for the week, and when zooming in to a specific day, all the hourly counts would be shown as well.&lt;/p&gt;
&lt;p&gt;Another challenge is keeping the total counts of plays, likes, reposts, and other signals we would like to display for a certain track updated. These counters offer an aggregated view of the entire time a track is present on our platform, and they are ubiquitous in that they are visible everywhere a track is displayed. Because, most of the time, these counters are public, they are requested from the service several orders of magnitude more times than the actual stats that are only visible to the individual creators.&lt;/p&gt;
&lt;h1&gt;&lt;img src=&quot;/blog/f2f6d71e2b425162e229c3ffeb0c72a3/gears.svg&quot; alt=&quot;gears&quot;&gt; Current Solution: Stitching Counts&lt;/h1&gt;
&lt;p&gt;As highlighted in the aforementioned post, our system &lt;em&gt;Stitch&lt;/em&gt; is at the heart of the stats and counters. Stitch pre-aggregates events over several dimensions to answer queries efficiently. The main dimension is time, and the buckets consist of hourly, daily, and monthly partitions. The second dimension is the different type of events, of which the play events are the most abundant, making them the most challenging to keep updated. On top of all this, we are doing this pre-aggregation for every track, along with a summation of every creator’s overall stats and counters.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 776px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d55f08cc89ab80f46068e977d4213585/0901c/counts_stitch.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 78.35051546391753%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAADUklEQVQ4y6VTa0iTURg+c9907pI5sywTWohaSdKVLCrsRxhBfyolyloXmgrVjyAMM03KXIqF2izrh9oNLVIM0lLTec3mbc62uavTbU2dljbQrcvbOR9GYSJUD7yc873nfZ7znee8B8HEW3A7msbco7VrEYZ7pM4D/QkGDl8Wi7UIj36xMbF+BQUF4ReTk5ZoO8y3NHLjZ43cZFA09Fcig6ICJqz1TnC2bibMLw4ZLagYmEJ17wwIANCatRuYXC5PQMRwCCIiIvxTUlICIiMj+bouc01noxLkDYqvfU16J2qpLdnX1/44mIg8eliCHtxJRfVyHZoL166mr76RmbHy99x4/7RPbaUstrVGvtPw1s5GCWeShAjtZc4mV7+zUG2aSU55vYrztPa994PyRt71Gze3FD0sCyHr4vh4xpXUdPo0CWfFQvG5kwtpog/fa8GyAD/exg3rKXw8xoTTRRe9t7juqa3uCd0wDBocYNGNgFVtc+P4qlWYXaGkhtQTf+OOxAWJjh4ngl4kT3ZcKhQK+ZkSyTqJRBIZHb1H0DvgbLRPATR2DUAV9qiuXQsaqwsMowCV9b3R58/Gh1y6nBqEuUSI+OuLYxfaGbVDEHcsbjH+8MiX5gvy8nMDyC59Q1OysW8AGpsbVJZpwH8H5k8AxjEAWffQ9sxraetz824TQbZAIOAxmUwunq9CB3bHcsqflofM9lBp/r5JOTh9Wv3hm0jvAFGH9qO4x+Q8pRxyHVYNA+dnnU436wJLqu+veFRVFGYDU6DRrV4utzYvb+h/HVityfNB86B7GOZeGHCaQDOqgrb+VmhRN0PfByWQnHHSAF2Wjm0z5lP7D53wlhZXsPDco+xVJzq41Q89efaC1vD09Pwl2Gvryewe6rzepJJlNSjeSNt1bblKuyKD5O4/v0t6jhEcFkwYLA8MiqJoHp6if0JYeCjpUe7MKyE2sOYlmMbNlHZYT6msGsrmtjPtX0aY+lEjpXcYSF8iHuKjGREug8Egl0HNK5heKUapL0VIUnYB5RZJ0K3iDJTSFIOSSkQoITGBrklOTubh1uAkJib6l5aW0oI5OTl/f9ysrCx6zM7O5rPZbK+0tLRALEjfQGFhIfpfeEdFRfGIDQRSqXTOoh8lTJIf2+TVNQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Stitch architecture&quot;
        title=&quot;Stitch architecture&quot;
        src=&quot;/blog/static/d55f08cc89ab80f46068e977d4213585/0901c/counts_stitch.png&quot;
        srcset=&quot;/blog/static/d55f08cc89ab80f46068e977d4213585/9ec3c/counts_stitch.png 200w,
/blog/static/d55f08cc89ab80f46068e977d4213585/c7805/counts_stitch.png 400w,
/blog/static/d55f08cc89ab80f46068e977d4213585/0901c/counts_stitch.png 776w&quot;
        sizes=&quot;(max-width: 776px) 100vw, 776px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Stitch architecture is an incremental &lt;a href=&quot;http://lambda-architecture.net/&quot;&gt;Lambda Architecture&lt;/a&gt;. Instead of always regenerating the serving layer from the beginning of time, we only generate the changed buckets of hours, days, and months. Batch counts for the current day — including hourly batches — are handled with a daily job, and batch counts for the current month are handled with a monthly scheduled job.&lt;/p&gt;
&lt;p&gt;We’ve chosen this way of regenerating the serving layer in batches from the end of the last batch calculation performed because play counts do not generally come from the past (with the exception of things like spam and backfills from bugs in the event pipeline). Creators also tend to expect monotonically increasing play counts over time, although this property is not valid for counts of “likes” or “reposts” that could actually go down as well.&lt;/p&gt;
&lt;p&gt;With this setup (shown and described above), we have the possibility of swapping or recalculating certain days or months of data, but usually we don’t need to, so long as everything goes fine (as in no bugs and no network failures or other outages). The &lt;a href=&quot;https://developers.soundcloud.com/blog/data-pipelines-apache-crunch-java-8&quot;&gt;Crunch&lt;/a&gt; batch jobs run in our &lt;a href=&quot;https://www.cloudera.com/&quot;&gt;Cloudera&lt;/a&gt;-flavored &lt;a href=&quot;http://hadoop.apache.org/&quot;&gt;Hadoop&lt;/a&gt; cluster over data ingested and archived by our event pipeline based on &lt;a href=&quot;http://kafka.apache.org/&quot;&gt;Kafka&lt;/a&gt;. Both the batch and the real-time layer convert the &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;Protobuf&lt;/a&gt; event messages into increments and sum them up in their specific buckets.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 401px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/582e02631c980b94411d5cf92fe39825/302a3/counts_cassandra.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 89.52618453865337%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAACwElEQVQ4y6WU30uTURjH34lvbktzacPR0K3JjIGYOCyDflCX3XfllddeCdkfEHYzGgq1sYQ1icibYVGZoJKuKbsQTdPNjNnc+4PlNnU/IC2T0/e8O4LSD0c+8OF53/Oe832f55znORz3u1nBdXATXAY3gJ07humY6HlgAhZQB1TcMa0MaP9rpc/nO/Su1WotPM9fBVc0Gg2NkLPZbMq3rq6u4oU7Ozv3H/UsZRtLX7GGhobihAghnNfrVZ7T6XSlx+Mxtbe32zs6Opp7e3vPbWxsVP1l6RmgYZwAZ5VRl8tVOF6rtTyVSkVzuRxJJpM/wd7W1tYeBPP9/f2tdI7dbi87sLgWGNmhnQb1itDAwEAhT72+ChFOZ7PZ9UwmswaxOMRFjH1xu93NdI7BYKiEq2aLa5kQ3aIKVhGH7JQkSY/j8fiYKIpvwLAgCCPwQ36/n0ZSWlNTo2WC/AFKGWoqUmIymegLZ7FY9Igus7m5SSBEEokEwQ/Izs4OwbbQQucwl0Z48l9nUq5SqS7SbGjFxGKxR7Isj0DoBSJ7CV5DfDAUCtG0eJ1OV3FkkTc1NZVQbzQaDfl8XqIRrq6uEqSuRIq9JE6nk7Yg19LSoi66DlHEOpzyU0QZDIfDY9FodBzCExgbDgQC9AB4WmJHmiSJiler1ZVI14dyCaTSqdH15Poo/LggCm+773Y3spo9uqcTckLxZrNZn8vmklhEfnzfJbuA2va3bTL4fPCS0qZPfCVFdQqzisXY/J1lYenhihR5sCJHnMtiuG9pbeE+5vzpZPfrT8suk0KnDC0ULgf7LWv1vDz3dTY+SwKLk2Ti4zsSjLwnC/IH4vD2XFP6udG6v7icCdJeb2WFblaE3K8cir99r42fE2b8oc/TM8Hw5FQwEpia/hQMYWyy75mDXhYqU32dmrUejZjeFhdAG6B73PwLUMJTO+kcmzgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Stitch Cassandra structure&quot;
        title=&quot;Stitch Cassandra structure&quot;
        src=&quot;/blog/static/582e02631c980b94411d5cf92fe39825/302a3/counts_cassandra.png&quot;
        srcset=&quot;/blog/static/582e02631c980b94411d5cf92fe39825/9ec3c/counts_cassandra.png 200w,
/blog/static/582e02631c980b94411d5cf92fe39825/c7805/counts_cassandra.png 400w,
/blog/static/582e02631c980b94411d5cf92fe39825/302a3/counts_cassandra.png 401w&quot;
        sizes=&quot;(max-width: 401px) 100vw, 401px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The batch-generated “serving” layer and the real-time or “speed” layer, as coined in the Lambda Architecture, both use &lt;a href=&quot;https://cassandra.apache.org/&quot;&gt;Cassandra&lt;/a&gt; as their primary data store. In the batch layer, we use the Sorted String Table (SSTable) loading feature of Cassandra to effectively ingest even several months of data. For the speed layer, we took a different approach of leveraging the distributed counters feature, issuing every increment (or decrement for comment, repost, or like counts) as a database update.&lt;/p&gt;
&lt;p&gt;The Stitch API constitutes the query layer that consolidates (or should we say &lt;em&gt;stitches&lt;/em&gt;) the real-time view and the batch-aggregated numbers seamlessly as a unified response to the clients of the service.&lt;/p&gt;
&lt;h3&gt;Total Counts&lt;/h3&gt;
&lt;p&gt;Given that the system aggregates counts by the creator, track, and time, answering total counts on a given track for all time requires fetching by all track and time buckets. Given this constraint, we added a caching layer to store total counts for a given track. To lower the pressure on our Cassandra cluster, we added a &lt;a href=&quot;https://memcached.org/&quot;&gt;Memcached&lt;/a&gt; cluster to serve approximately the top 90 percent of the most requested total counters.&lt;/p&gt;
&lt;p&gt;We increment both the source-of-truth counters in Cassandra and the cache counters in real time, but in order to limit the divergence of the two views, we &lt;a href=&quot;https://martinfowler.com/bliki/TwoHardThings.html&quot;&gt;invalidate&lt;/a&gt; the entire cache on a daily basis with a randomized time to live (TTL) parameter. The randomization is needed to lower the probability of highly accessed track counters all &lt;a href=&quot;https://en.wikipedia.org/wiki/Thundering_herd_problem&quot;&gt;invalidating at the same time&lt;/a&gt;, which would cause periodic spikes in our cluster usage. The invalidation is needed because our event pipeline only supports at-least-once delivery and the updates are not transactional.&lt;/p&gt;
&lt;h3&gt;Top Lists&lt;/h3&gt;
&lt;p&gt;As part of our stats product for creators, we offer various top lists, thereby allowing them to gain more insight into their audiences and track performances. This system is responsible for generating and serving these lists in a way similar to how Stitch provides counts. By connecting to the same event pipeline, the system aggregates the events and creates partitioned top lists for every dimension we’re interested in, such as the most played tracks for a creator for a day.&lt;/p&gt;
&lt;p&gt;One of the challenges we’ve faced in splitting these two facets of the same product into two different systems is keeping the two views in sync by scheduling them at the same cadence and always triggering recalculation on all the systems affected by a problem. Additionally, it’s important to run spam filtering and throttling as part of a common processing unit upstream in the event pipeline.&lt;/p&gt;
&lt;p&gt;There is another system that provides global top lists. It relies on the same event stream and is a good example of the versatility of having a consistent pipeline that can be tapped into by several decoupled systems. That said, it faces challenges similar to those of the aforementioned stat product for creators in terms of consistency of the numbers and ordering.&lt;/p&gt;
&lt;h1&gt;&lt;img src=&quot;/blog/895d086e871a111fa120219d5d80655f/tools.svg&quot; alt=&quot;tools&quot;&gt; Alternatives&lt;/h1&gt;
&lt;p&gt;Our internal engineering culture is driven by &lt;a href=&quot;https://en.wikipedia.org/wiki/Request_for_Comments&quot;&gt;RFC&lt;/a&gt;-based decision making, and one of the key points in these collaborative documents is finding alternatives to the presented solution.&lt;/p&gt;
&lt;p&gt;Additionally, while growing and optimizing a system, it’s important to properly assess whether it’s actually the right thing to do or else consider that maybe it’s time for a radical change. Every time we have to grow our Cassandra cluster or fine-tune the batch jobs to cope with more and more data, we get a tingling feeling that maybe this is not the way forward. The inherent tradeoffs we’ve made around using more storage for query efficiency, or dealing with caching and invalidation, or having to reconcile different views of the same dataset, were all predicated on older knowledge of the problem and solution space.&lt;/p&gt;
&lt;p&gt;By exploring the alternatives (sometimes over and over again) we are able to substantiate our choices. In the case of Stitch, we tried out most of the following approaches, either during SAT (self-allocated time), or as scheduled spikes to improve our systems, but none resulted in a breakthrough that would justify fundamentally changing our current setup (yet).&lt;/p&gt;
&lt;h3&gt;Always Count&lt;/h3&gt;
&lt;p&gt;Historically, we started with a huge database of all events. Every time a statistic or a total count was needed, we issued a query to this database to actually count all the events in real time. This quickly became infeasible and we started extracting this functionality into dedicated databases, and finally into our current Stitch system.&lt;/p&gt;
&lt;p&gt;With “Fast Analytics“ databases “buzzwording” around, it might be time to revisit this approach. By storing these increments in columnar stores that provide efficient analytical queries, we could use less storage than the pre-aggregation approach does. Analytical databases like &lt;a href=&quot;https://aws.amazon.com/redshift/&quot;&gt;Amazon Redshift&lt;/a&gt;, &lt;a href=&quot;https://cloud.google.com/bigquery&quot;&gt;Google BigQuery&lt;/a&gt;, &lt;a href=&quot;https://kudu.apache.org/&quot;&gt;Apache Kudu&lt;/a&gt;, or even &lt;a href=&quot;https://www.vertica.com/&quot;&gt;Vertica&lt;/a&gt; provide results in seconds, but not milliseconds (as our current approach does). Maybe a hybrid approach like &lt;a href=&quot;https://github.com/filodb/FiloDB&quot;&gt;FiloDB&lt;/a&gt;, implementing columnar storage on top of the Cassandra store, could be a valid alternative.&lt;/p&gt;
&lt;h3&gt;Pre-Aggregate Differently&lt;/h3&gt;
&lt;p&gt;Our current solution relies heavily on pre-aggregation, so another approach would be to use a different pre-aggregation scheme by leveraging some funky data structures like &lt;a href=&quot;https://en.wikipedia.org/wiki/Fenwick_tree&quot;&gt;Fenwick trees&lt;/a&gt;. The main challenges would be in creating efficient distributed versions of these &lt;a href=&quot;https://en.wikipedia.org/wiki/Prefix_sum&quot;&gt;prefix sum&lt;/a&gt; data structures.&lt;/p&gt;
&lt;p&gt;One viable way could be by leveraging the partitions already present in our Kafka-based event pipeline and building stateful services by continuously consuming events, similar to Kafka Streams, and providing a query API on top of these mini stores.&lt;/p&gt;
&lt;p&gt;Another challenge is augmenting algorithms to not only result in total counts for a range, but to efficiently calculate all the individual data points for a specific range and granularity. When we tried out this alternative, we represented the trees as sparse arrays, utilizing fast integer-packing algorithms implemented in &lt;a href=&quot;https://github.com/lemire/JavaFastPFOR&quot;&gt;JavaFastPFOR&lt;/a&gt; to lower the serialized footprint of the data. Nonetheless, the overall latencies were not as performant as the Cassandra store approach, and the complexity of the code was significantly higher. Essentially, we were reimplementing a database.&lt;/p&gt;
&lt;p&gt;Another alternative would be to only aggregate data that will be queried. This would trade some initial latency for a queried period for a substantial amount of storage and potentially unused computation time. We could take user requests and pipe this into an adaptive caching system.&lt;/p&gt;
&lt;h1&gt;&lt;img src=&quot;/blog/2b48b1043f93447ab678bfb17fcda9ab/sound.svg&quot; alt=&quot;tools&quot;&gt; Back to the Drawing Board&lt;/h1&gt;
&lt;p&gt;As already stated above, none of these approaches has resulted in us changing our setup, because of extra operational/engineering overhead and the unacceptable increase in latency they would require. However, it has taught us valuable lessons about engineering tradeoffs and the challenges of keeping things synchronized and consistent, and we remain open to exploring new ways of keeping track of counts.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Special thanks to &lt;a href=&quot;https://github.com/anastasiiabasha&quot;&gt;Anastasiia Basha&lt;/a&gt;, &lt;a href=&quot;https://github.com/jkao&quot;&gt;Jeff Kao&lt;/a&gt;, and &lt;a href=&quot;http://www.natalye.com/&quot;&gt;Natalye Childress&lt;/a&gt; for all the help on this post, and of course to other past and present SoundClouders who have contributed to continuously evolving these systems. If you are interested in solving these kinds of challenges, please check out our &lt;a href=&quot;https://soundcloud.com/jobs&quot;&gt;jobs page&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Running Android UI Test Suites on Firebase Test Lab]]></title><description><![CDATA[Testing mobile applications is not always an easy feat. In addition to defining what to test and determining how to write those tests, actually running tests can also be problematic — in particular, UI test suites running on real mobile devices or emulators sometimes run for an extensive amount of time.]]></description><link>https://developers.soundcloud.com/blog/running-android-ui-test-suites-on-firebase-test-lab</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/running-android-ui-test-suites-on-firebase-test-lab</guid><pubDate>Fri, 04 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Testing mobile applications is not always an easy feat. In addition to defining what to test and determining how to write those tests, actually running tests can also be problematic — in particular, UI test suites running on real mobile devices or emulators sometimes run for an extensive amount of time.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;This post talks about both how we at SoundCloud initially handled our Android UI tests with internal tooling and our migration to Firebase Test Lab for running all of our UI tests.&lt;/p&gt;
&lt;p&gt;In the CI builds running on our &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.soundcloud.android&quot;&gt;Android listener application&lt;/a&gt;, the time it took to run the UI tests was always a bottleneck for the overall runtime of a build. So in order to not just rely on one single device or emulator, we developed tooling and services to allow us to run our UI tests on an internal device farm using 30 Nexus 5 devices in parallel. This made it so we could run our tests in about 15 minutes, as opposed to the three hours it would take if we ran them all on a single device.&lt;/p&gt;
&lt;p&gt;Unfortunately, after years of working successfully, this system started showing signs of its age, causing multiple problems in our day-to-day development. Initially, it had a great feature set with rich UI test results and reports, video recordings of failed tests, reports of the most frequently failing tests over time, and more. But eventually, it started to slow us down. We frequently saw long build queues and failures due to infrastructure issues, and soon it became clear that we had to either fully commit to maintaining and updating the system, or else start thinking about alternatives.&lt;/p&gt;
&lt;p&gt;We began investigating different platforms and cloud providers offering real devices and emulators in different configurations, and in the process, we realized two problems we would face.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our test suite was tailored to running on physical Nexus 5 devices, and due to shared state, many tests had to be run in isolation to work correctly. Additionally, our custom test services made sure every test ran in isolation without sharing any state with other tests. However, cloud providers for running Android tests offer a variety of devices in different sizes and usually run all tests one after another, often with shared state between tests. This difference, along with the age of our test suite, resulted in multiple issues for us.&lt;/li&gt;
&lt;li&gt;The size and runtime of our test suite meant that running our UI tests using a cloud provider to spin up a single device or emulator would degrade our test feedback times to multiple hours.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Making Our Test Suite More Robust&lt;/h2&gt;
&lt;p&gt;It was clear that in order to run our tests on other device/emulator farm setups, we would have to make our testing more robust. For example, some tests expected to run on the exact screen dimensions of a Nexus 5 and started failing if the screen size differed slightly. Others couldn’t handle the state other tests left behind.&lt;/p&gt;
&lt;p&gt;Around the same time that we were looking at different solutions and experimenting with ways to improve the stability of our test suite, Google released the first versions of the &lt;a href=&quot;https://developer.android.com/training/testing/junit-runner.html#using-android-test-orchestrator&quot;&gt;Android Test Orchestrator&lt;/a&gt;, which wraps the test instrumentation to allow each single test to run in its own Instrumentation. The result is that tests do not share a lot of state, and each one runs more in their own sandbox. Additionally, if one test happens to crash, the other tests can still run and report their correct states; the test suite will not be interrupted immediately due to a crash.&lt;/p&gt;
&lt;p&gt;Using the Android Test Orchestrator is as easy as adding a few lines to your project’s &lt;code class=&quot;language-text&quot;&gt;build.gradle&lt;/code&gt; file, and doing so can help stabilize a test suite quite a bit. This, of course, depends greatly on the test suite itself:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;android &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  defaultConfig &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
   testInstrumentationRunner &lt;span class=&quot;token string gstring&quot;&gt;&quot;android.support.test.runner.AndroidJUnitRunner&quot;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  testOptions &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    execution &lt;span class=&quot;token string&quot;&gt;&apos;ANDROID_TEST_ORCHESTRATOR&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  androidTestImplementation &lt;span class=&quot;token string gstring&quot;&gt;&quot;com.android.support.test:runner:&amp;lt;latest_version&gt;&quot;&lt;/span&gt;
  androidTestUtil &lt;span class=&quot;token string gstring&quot;&gt;&quot;com.android.support.test:orchestrator:&amp;lt;latest_version&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After adding this piece of setup every time tests from the &lt;code class=&quot;language-text&quot;&gt;androidTest&lt;/code&gt; configuration are run they will run with the Android Test Orchestrator.&lt;/p&gt;
&lt;h2&gt;Firebase Test Lab&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://firebase.google.com/&quot;&gt;Firebase&lt;/a&gt; is Google’s suite of many services for building great mobile applications. One part of it is the &lt;a href=&quot;https://firebase.google.com/docs/test-lab/&quot;&gt;Firebase Test Lab&lt;/a&gt; — this enables developers to easily create and launch a test run on a real device or Android emulator hosted in a Google data center and run a set suite of tests on it. After the tests have finished running, the Firebase web UI will then display the results of each test — in addition to information such as a video recording of the test run, the full Logcat, and screenshots taken with the help of the &lt;a href=&quot;https://firebase.google.com/docs/test-lab/test-screenshots&quot;&gt;Firebase Test Lab Screenshotter Library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tests can also be run on Firebase Test Lab through Android Studio. Simply sign into Android Studio with the Google account linked to your application’s Firebase project. When you run a test, select the “Cloud Testing” tab, choose the Firebase project, and configure the devices/emulators you want to run your tests on.&lt;/p&gt;
&lt;p&gt;For our use case, which was wanting to test our Android application, the choice to go with Firebase — which will always allow us to test on the latest Android versions and use the newest test tooling such as the Android Test Orchestrator — was an easy one.&lt;/p&gt;
&lt;p&gt;However, the main use case of Firebase Test Lab is one that allows users to upload the application APK and a test APK. Firebase Test Lab then runs all the tests contained in the test APK — or a subset of them depending on the configuration — on one device/emulator or a matrix of different ones. However, every device and/or emulator will run the same set of tests. For a test suite of our size, this would result in huge test durations.&lt;/p&gt;
&lt;h2&gt;Massively Sharding Tests&lt;/h2&gt;
&lt;p&gt;Firebase Test Lab currently does not offer splitting a test suite into multiple parts and executing smaller parts of the suite on multiple emulators in parallel to bring down testing times. However, the open source community (namely Walmart Labs) has come up with a solution for this.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TestArmada/flank&quot;&gt;Flank&lt;/a&gt; is a tool that allows for massively sharding an Android UI Test suite across many devices/emulators on Firebase Test Lab, at the same time, in parallel. In other words, instead of running all tests on a single emulator, it allows for scheduling a smaller amount of tests on multiple devices/emulators. Each of these shards will then execute in parallel, massively reducing the time it takes to run all tests in the process.&lt;/p&gt;
&lt;p&gt;Flank is configured with a small file defining the device or emulator that should be used, the Android OS version, and how long each shard should run for. Flank keeps a record of the duration of each test so that it can group them together to ensure a fairly constant runtime. Additionally, as Firebase bills Test Lab usage by the minute, Flank tries to intelligently group tests together so that they use the minimal overall billable time on the devices, thereby saving costs.&lt;/p&gt;
&lt;p&gt;By using Flank, we were able to reduce our overall test suite runtimes when compared to our previous internal systems. And in case our test suite grows larger, Flank will help us schedule our tests on more emulators, enabling us to continue to have our current test durations.&lt;/p&gt;
&lt;p&gt;Our previous in-house testing solution gave us many features we got used to and loved, which we lost when we started using Flank. For example, after a test run, we would receive a test report in rich HTML with links to Logcat and video recordings of the test run for failing tests. Additionally, filtering tests by annotations was a way for us to easily configure different suites of tests to run, depending on the current task. However, as part of our work to migrate our UI test suite to Firebase Test Lab, we built these features into Flank and contributed them back into the main repository so that other users of Flank can enjoy them as well.&lt;/p&gt;
&lt;p&gt;Overall, running our tests in Firebase Test Lab with Flank has helped us modernize our UI testing stack, stabilize our test suite, and even speed up test runtimes in the process, which leads to happier and more productive developers.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Managing Unplanned and Support Tasks]]></title><description><![CDATA[One challenge engineering teams often face is dealing with work that doesn’t revolve around developing new features but that still requires the team’s attention and time. The Content Engineering Team here at SoundCloud is no exception, so we iterated on a process to deal with unplanned and support tasks to end up with fewer interruptions and more time to spend on implementing planned features.]]></description><link>https://developers.soundcloud.com/blog/managing-unplanned-and-support-tasks</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/managing-unplanned-and-support-tasks</guid><pubDate>Mon, 26 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One challenge engineering teams often face is dealing with work that doesn’t revolve around developing new features but that still requires the team’s attention and time. The Content Engineering Team here at SoundCloud is no exception, so we iterated on a process to deal with unplanned and support tasks to end up with fewer interruptions and more time to spend on implementing planned features.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;We define unplanned and support tasks in the following way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unplanned tasks — work that unexpectedly appears and needs immediate action. An example of this is a severe bug that impacts our users.&lt;/li&gt;
&lt;li&gt;Support tasks — work that doesn’t need immediate action and can be planned but still needs engineering support. Reasons for these tasks can be lack of automation, lack of visibility into the systems we build, work caused by infrastructure changes, or non-critical bugs we introduced ourselves.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are several reasons why it is important that we come up with an approach to dealing with unplanned and support tasks. For one, these tasks take up time we would normally spend on delivering features, so naturally we want to minimize the number of them. Secondly, we want to make this kind of work visible; it is often invisible outside of the team, which can give the impression the team is slow and dysfunctional. Finally, these unexpected and often urgent tasks cause extra stress and context switching for the team — in addition to the fact that support tasks are often not very interesting, due to their manual and repetitive nature.&lt;/p&gt;
&lt;h2&gt;The Request Process&lt;/h2&gt;
&lt;p&gt;Initially, my team had a very open way of dealing with requests from stakeholders that resulted in unplanned and support tasks: People who had requests could leave them in our Slack channel, and a team member in a single point of contact (SPOC) role was supposed to address them.&lt;/p&gt;
&lt;p&gt;Some team members liked taking on this role because they liked helping out our stakeholders. Others didn’t like it as much because they didn’t want to be interrupted, and as a result, were slower in responding to requests.&lt;/p&gt;
&lt;p&gt;And even though this idea resulted in a designated person responsible for addressing incoming requests at any given time, all the other team members still saw them. As a result, they were often distracted, and some people started discussing the requests even when they were not urgent. It got to the point where the distractions and unclear priority of the requests began popping up as a recurring topic in our team retrospectives.&lt;/p&gt;
&lt;p&gt;This led us to try another approach. We got rid of the SPOC role and instead tried asking stakeholders to reach out using our team Slack channel or via direct messages, but only for urgent requests. We defined an urgent request as something that impacts our users, disrupts a key feature, or blocks employees from doing their work. For all other requests, we specified an email address that reached the team Engineering Manager (EM) and Product Manager (PM).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 544px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/43f261f9f2ab08ca0da36ec14bd626a4/6c314/request_process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.698529411764707%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABHElEQVQY042QS0rEUBBFszEX4yLcgkvQNTjpkaMoOgo4aDEJodGOSSedT8f8Xn4v6ZDP7bwSQRDFCw+Keodbt0ra7/cwDAOWZWGz2VDNOYeQFrW4fT/i8p5B9XtULIGm6dB1nTjbtrHdbpFlGfHzPEMyTROyLENRFIJc10WafgKPToWr5wbnNwx3ry0wddg5LjGe59Hgvu8xDAOZkWHTNItBSo8xhi/N+K7hR+c3SUmSoCgKxHFMpkEQQJyh61qwboJfjXjacST1iKYq4PsBoigiRrCiFmnDMITwksSHqqpYr9d0E9E8HKIlVI/VW4mLhwpn1zlWGsfAGV5UnXjHceh2Yqs8z1GWJeq6hvRX/Gkcl01HfLAW/Dj8a+UTsffCREnPo3YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Request Process&quot;
        title=&quot;The Request Process&quot;
        src=&quot;/blog/static/43f261f9f2ab08ca0da36ec14bd626a4/6c314/request_process.png&quot;
        srcset=&quot;/blog/static/43f261f9f2ab08ca0da36ec14bd626a4/9ec3c/request_process.png 200w,
/blog/static/43f261f9f2ab08ca0da36ec14bd626a4/c7805/request_process.png 400w,
/blog/static/43f261f9f2ab08ca0da36ec14bd626a4/6c314/request_process.png 544w&quot;
        sizes=&quot;(max-width: 544px) 100vw, 544px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We had no difficulties convincing stakeholders to try this approach, and as a result, the number of requests in Slack dropped almost immediately.&lt;/p&gt;
&lt;p&gt;This is the support request process we still use today.&lt;/p&gt;
&lt;h2&gt;Planning the Work&lt;/h2&gt;
&lt;p&gt;We have two-week iterations in our team, and if a non-urgent support request comes up, we add it to the next iteration. The next iteration rule is important because we want to avoid building up a backlog of issues that never get worked on.&lt;/p&gt;
&lt;p&gt;Initially, we went over the support task tickets during our planning session so that everyone was aware of them. Then we let the team self-organize with the goal of finishing the tasks by the end of the iteration. This resulted in a situation where it was often the same team members tackling the support tickets while other team members focused on feature work the entire length of the iteration. However, this wasn’t necessarily fair, so after the issue came up in one-on-one meetings and retrospectives, we addressed it and found a new solution.&lt;/p&gt;
&lt;p&gt;Our new way of handling these tasks was to assign support tickets to team members in a round-robin fashion, thereby ensuring everyone got a fair share of support tickets. This not only improved team morale, but it made it so that the work was divided more equally and fairly, which also resulted in increased knowledge sharing.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 752px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/379b53659c14f82127c31d4fc648625c/f6afc/planning_simpler_version.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.409574468085104%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABtUlEQVQoz3WSz07bQBDG/XhtUSUkHiFwqNQn6L3qFYkH6KEcUA9cUDlRxAGpFUVUKSlJiON17CV/sFPh1HZs726+zo6TCCox0mg860+/2ZlZB2SLxYJdKYUoiiClxGg04jgej1HM56h1tVZrzflsNoMQgrXGGD5zrIDF5EVRIM9zpGlaxyyD0gbK2GIaK+3KrCYIAvi+z3AGMswoTMQ3qGJek5fW+/0LonuD1tkxyiLls4yKfD74xN1YflmWDLP58oYGZTXH7ekRrv0+vrdOUOZT/im65xDuD5xeXcC7DhHJBOksxeHHL3hSmWzdcpJkGAzu4Tab6Ml73HhtPPwZIwyH8EQbk2iAXhzDv5Xod3x4noDfGSFJkrW7rkv6kKEE9NDvH8IPPmAa7UOVba40mYTk7voGCxhkecbzLaqcxlQv0kKqquJF2dyRchfd7gb5KxruJobDt7iTJ4jjA9rwO4pf8TcVsB1prXhmWhs8Z45SJW03IPdouD8Juo3LyxdotV6j09mi+BLT6Sa1drx8Ohqrp/b4ya1y5zHd3iCO92hOOwRv0EYbBHtDsPf03V4CzBPY//YPnLOkiVJTzPIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Planning Process&quot;
        title=&quot;The Planning Process&quot;
        src=&quot;/blog/static/379b53659c14f82127c31d4fc648625c/f6afc/planning_simpler_version.png&quot;
        srcset=&quot;/blog/static/379b53659c14f82127c31d4fc648625c/9ec3c/planning_simpler_version.png 200w,
/blog/static/379b53659c14f82127c31d4fc648625c/c7805/planning_simpler_version.png 400w,
/blog/static/379b53659c14f82127c31d4fc648625c/f6afc/planning_simpler_version.png 752w&quot;
        sizes=&quot;(max-width: 752px) 100vw, 752px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Another variant we started trying out recently is having one day in the iteration when the team works together to finish the support tickets. This has the additional advantage that more pairing takes place, which is better for knowledge sharing. It is also nice that everyone shares the work together at the same time as a team effort.&lt;/p&gt;
&lt;h2&gt;Tracking the Work&lt;/h2&gt;
&lt;p&gt;As part of the overall process, we also think it’s important to track the work done on these tasks. We do this by creating a JIRA issue for every request that comes in, even when the request can be answered by the Engineering Manager or Product Manager. Once an issue is created, we also share it with the stakeholder who initiated the work so they can follow up on the progress.&lt;/p&gt;
&lt;p&gt;Every request JIRA issue is labeled. Urgent requests get the “unplanned” label, and non-urgent requests get the “support” label. Then, once we are done working on an issue, we log the estimated amount of time we spent working on the issue using JIRA’s Log Work functionality.&lt;/p&gt;
&lt;h2&gt;Measure, Learn, and Act&lt;/h2&gt;
&lt;p&gt;Keeping track of the work in this way allows us to measure it, learn from it, and act on it.&lt;/p&gt;
&lt;p&gt;More specifically, about once every two months, we query all the finished unplanned and support issues of the past iterations in JIRA. We then group them in categories and add up the time spent on them. An example of this from earlier this year is shown below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 600px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/864b39d8ea729317de2b128c1f9d0ba3/34e8a/managing_unplanned_support_tasks_hours_spent_chart.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.83333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsSAAALEgHS3X78AAAByklEQVQoz6VSzUsbQRTfP8GDh156EXuq9F6oZy8eSqGHJhdTRKySowja3gr9sOhNLYFeYi96K2pIi0SUxNUQbUlod6Goh3xsPnb2e7OzuzPjy64iJrn5mIHfe29+b37z5nEYY4SQCqbpCmxdB9eyLE3TkGo0ZQ0pqm3bkICIYRjVhoqx5/s+pZSDTQhh/WwhcT42Vzz4owImhIbBluYZpm2ZJvCB3Ald1Nrff0l7BQSeT2hYbeLDv4cvc6ljmQXB3uqc6/qM0Z0j+cGLbPT9X8A3KTq5JAxH+fRJh+wBO7A75LDkzzyCc9PLotX2NzP1rf0GgKkv4lCEhxQcwB7pvZuDkiF56BX/ZlmsIfzkdR4WAHAhmA7I/WUHZAraHkX52RVRQs5o/PRZ/FSSnZkVcTjCZ87Q/4qV2K4URK3zGNoje4dHg89zEx+FuuKOxPKPY3kA4A6MZ3Mlbe1HlXuaefftsksCB31g1DsTGm+/FpO757Wm+ikpfN4Q6y19I3WxuF4SLtHhb2l+tZjKlYF75+ZACXXapm00XcdQVQU1y7oimYbOqMuYQwiMBIaWEYK7msZdf0ugh3aGgchIackonJzbf+s3RRy7h10BrhuCLbDl0f4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Visualizing the time spent&quot;
        title=&quot;Visualizing the time spent&quot;
        src=&quot;/blog/static/864b39d8ea729317de2b128c1f9d0ba3/34e8a/managing_unplanned_support_tasks_hours_spent_chart.png&quot;
        srcset=&quot;/blog/static/864b39d8ea729317de2b128c1f9d0ba3/9ec3c/managing_unplanned_support_tasks_hours_spent_chart.png 200w,
/blog/static/864b39d8ea729317de2b128c1f9d0ba3/c7805/managing_unplanned_support_tasks_hours_spent_chart.png 400w,
/blog/static/864b39d8ea729317de2b128c1f9d0ba3/34e8a/managing_unplanned_support_tasks_hours_spent_chart.png 600w&quot;
        sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Based on the categories and the time spent, we come up with actions. Some examples of actions we’ve come up with so far include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We found out that we spent quite a lot of time supporting our operations team because we were missing some automation to perform a specific task. This led us to prioritize the work required to automate the missing step, which not only got rid of the repetitive and boring tasks for our engineers, but also removed the dependency and waiting time for our operations team.&lt;/li&gt;
&lt;li&gt;We spent a considerable amount of time working on fixing our deployment pipelines because of infrastructural changes that were outside of our control (e.g. updated base Docker images). This led to the decision to inform the infrastructure team and ask them to consider if the benefits of their changes outweighed the additional workload for the engineering teams. We also improved our deployment pipelines so that they get triggered automatically when a new dependent Docker image has been updated. By doing this, we get notified of breaking changes immediately instead of being caught by surprise when we make a code change.&lt;/li&gt;
&lt;li&gt;When we introduced a bug in one of our systems, it required a lot of time to recover from the bug. We found out it took so much time because the system generated derived data / intermediate state. And because we couldn’t reprocess all the input data, it meant that all the derived data needed to get fixed/patched manually. This motivated us to come up with a plan to improve the system design.&lt;/li&gt;
&lt;li&gt;We found out that some of the bugs we introduced were caused by not having good enough validation criteria for our stories/epics. This resulted in us becoming better at defining validation criteria together with our PM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Measuring, learning, and coming up with action points is important, because having tangible numbers about the time spent on unplanned and support work makes it easier to prioritize work for fixing structural issues. This is good for both the team and for SoundCloud, as engineers are interrupted less and can spend more time on impactful work.&lt;/p&gt;
&lt;p&gt;If you are interested in learning more about our processes, you should also read the blog post &lt;a href=&quot;https://developers.soundcloud.com/blog/deliver-software-faster-by-managing-work-in-progress-not-by-adding-overtime&quot;&gt;Deliver Software Faster by Managing Work in Progress, Not by Adding Overtime&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Automatic Stubbing of Network Requests to Deflakify Automation Testing]]></title><description><![CDATA[Apple introduced automated UI testing in Xcode 7. This was a great addition for developers because this native support promised, among other things, an improvement in the flakiness notoriously associated with automation tests. As many of us developers have experienced, tests can sometimes fail even when there has been no modification to the test or underlying feature code.]]></description><link>https://developers.soundcloud.com/blog/automatic-stubbing-of-network-requests-to-de-flakify-automation-testing</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/automatic-stubbing-of-network-requests-to-de-flakify-automation-testing</guid><pubDate>Fri, 26 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple introduced &lt;a href=&quot;https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html&quot;&gt;automated UI testing&lt;/a&gt; in Xcode 7. This was a great addition for developers because this native support promised, among other things, an improvement in the flakiness notoriously associated with automation tests. As many of us developers have experienced, tests can sometimes fail even when there has been no modification to the test or underlying feature code.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Flakiness can often come from external factors. One such source of flakiness is &lt;em&gt;live data&lt;/em&gt;, e.g. data loaded from a web service API. Data from the web service often drives the state of the UI. A test might assume the UI is in a certain state, e.g. the like button shows as “liked” or that a playlist has 10 songs. But when the data from the web service changes, the test fails.&lt;/p&gt;
&lt;p&gt;Such occurrences are relatively common, and there are many other scenarios, such as network connectivity issues, in which these false negatives can occur. In these situations, the functionality of the app is not broken; it’s just that our assumptions about the data are no longer valid. What’s more, debugging the cause of these failures can be a time-consuming affair.&lt;/p&gt;
&lt;p&gt;In this blog post, I’ll discuss how we at SoundCloud implemented a simple system for automatically recording and replaying the network responses that occur during the course of an Xcode UI automation test in order to remove any flakiness that may be introduced from the existence of live data coming from a web service.&lt;/p&gt;
&lt;p&gt;It should be noted that third-party options such as &lt;a href=&quot;https://github.com/venmo/DVR&quot;&gt;DVR&lt;/a&gt; provide similar functionality. However, given that automatic stubbing of network requests can be achieved with a handful of classes and extensions, this post aims to showcase a lightweight approach that doesn’t require adding yet another dependency to your project.&lt;/p&gt;
&lt;p&gt;An example project showcasing the approach taken can be found &lt;a href=&quot;https://github.com/remover/NetworkStubbingBlog/tree/master&quot;&gt;here&lt;/a&gt;. Open &lt;code class=&quot;language-text&quot;&gt;ArtistsFeature.swift&lt;/code&gt; for instructions.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The first step is to create an &lt;code class=&quot;language-text&quot;&gt;AutomationTestURLSession&lt;/code&gt; object that is capable of stubbing the app’s network responses with the contents of a JSON file, since this is the data format we expect from our web service.&lt;/p&gt;
&lt;p&gt;The next step is to inject this session whenever tests are running.&lt;/p&gt;
&lt;p&gt;Finally, we will implement a system for automatically recording and replaying network responses in order to create an easy-to-use API.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/eeafeb4fdbe62b6686721e1ebf068086/f570d/dob-network-stubbing-start.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAB2klEQVQ4y4VT226bQBDl//+jT+1bpVbqQ5S+JL0krtxWqp02GAMxBmNjLmuWZeF0Z/ESTFA70jILczg7c2bWatsWZFJKlGWJLMu055zDxIwf7oUQyPNcr6Zp+phlNowxHQzDUBOOgd3mktC2bY03GE04zLBSIAKuXRe+718ADVm3OjxV47oeomj3MkNjTS3QyvqZrNtMlo+2wYkVEBV/JjSAohTwdjmcbQIvShEc2EXJ7YA0YxxulMEJDlhvjwiPpx7bE24ThuufAd5//o0P9yvcLCKIWmJoBuvHBd7eLvHq3Q1eX33D7XILOSY8cYFInbSJVXb7HHFaojnHKiHhqIy46KRghFVxP0yw2aXYZwrbdNiXGir9Kl4qeaRqUIW8KHC33ODN9XfcPQRIVSNkXet4reLkzQhcaGg0Ii2o2+SpkzROwT7Dl8UGQZypJjD1vTkvRUrkg3GyxvqQJ+DYOK90V8eaGuw/CbtTKVuJx+CI+fqI2Z9Q+1WYqljTV0PY/xLSqV05Agv/gK92go9zR/uHp0RpKPpyx7fJmhqLodEPvBIo2KnXdmzD/yYzNNeLHqsww2yV4NOvJ8ycRL/jHG8nbpCFCesJ1Sh4cY4fXoK5HWlP792ITFf0F9yIkWM4PmEgAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Network stubbing overview&quot;
        title=&quot;Network stubbing overview&quot;
        src=&quot;/blog/static/eeafeb4fdbe62b6686721e1ebf068086/8ff1e/dob-network-stubbing-start.png&quot;
        srcset=&quot;/blog/static/eeafeb4fdbe62b6686721e1ebf068086/9ec3c/dob-network-stubbing-start.png 200w,
/blog/static/eeafeb4fdbe62b6686721e1ebf068086/c7805/dob-network-stubbing-start.png 400w,
/blog/static/eeafeb4fdbe62b6686721e1ebf068086/8ff1e/dob-network-stubbing-start.png 800w,
/blog/static/eeafeb4fdbe62b6686721e1ebf068086/f570d/dob-network-stubbing-start.png 960w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The figure above shows how an &lt;code class=&quot;language-text&quot;&gt;AutomationTestURLSession&lt;/code&gt; is injected into the app in order to stub network responses using JSON files.&lt;/p&gt;
&lt;h2&gt;Session Stubbing&lt;/h2&gt;
&lt;p&gt;Let’s say we have a &lt;code class=&quot;language-text&quot;&gt;Client&lt;/code&gt; object our app uses to make all network requests. A simplified version might look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSession&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTask&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; task &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; task
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order to replace/stub network responses with the contents of JSON files on disk, what we do is replace the app’s &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt; with our own &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; when tests are running. The &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; will then be responsible for stubbing the responses. To facilitate this, we start with a couple of protocols that our &lt;code class=&quot;language-text&quot;&gt;Client&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt; will conform to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataTaskFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;suspend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With these protocols in place, we can easily make &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt; conform to it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataTaskFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt; conforming to our protocols, we can depend on the protocols from the &lt;code class=&quot;language-text&quot;&gt;Client&lt;/code&gt; instead of the concrete object types:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; task &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTaskFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; task
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can create our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; to stub network responses with the contents of JSON files by making it conform to the same &lt;code class=&quot;language-text&quot;&gt;URLSessionManaging&lt;/code&gt; protocol:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AutomationTestsURLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataTaskFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AutomationTestsDataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the code above, &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; returns an instance of &lt;code class=&quot;language-text&quot;&gt;AutomationTestsDataTask&lt;/code&gt;, which conforms to our &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTasking&lt;/code&gt; protocol instead of the standard &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;DataCompletion&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AutomationTestsDataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;DataCompletion&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token builtin&quot;&gt;DataCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;completion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; completion
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProcessInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;absoluteString&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HTTPURLResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; statusCode&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; httpVersion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headerFields&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;using&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utf8&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;suspend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;resume&lt;/code&gt; method of our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsDataTask&lt;/code&gt; is where the stubbing actually occurs.&lt;/p&gt;
&lt;p&gt;In order to explain the first line of the &lt;code class=&quot;language-text&quot;&gt;resume&lt;/code&gt; method, it’s useful to know that Xcode automation tests run to the app in a separate process, which limits the ways in which the app and the tests can share data. One common way to pass data between the tests and the app is by using the &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuiapplication/1500427-launchenvironment&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;launchEnvironment&lt;/code&gt;&lt;/a&gt; property on &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt;. This is basically a dictionary of type &lt;code class=&quot;language-text&quot;&gt;[String: String]&lt;/code&gt;. Keys and values you set in tests will be available to the app at runtime via the &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; property of &lt;code class=&quot;language-text&quot;&gt;ProcessInfo.processInfo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here we check to see if there is some JSON in &lt;code class=&quot;language-text&quot;&gt;ProcessInfo&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; dictionary for the data task’s request, as identified by its &lt;code class=&quot;language-text&quot;&gt;URL&lt;/code&gt;. If we have some JSON for a given request, then we convert it to data and call completion immediately. This results in the stubbed data being returned to our application instead of the live data from the API that would usually be returned!&lt;/p&gt;
&lt;p&gt;Now all we need is a way of detecting that automation tests are running so that we can swap out the session object the &lt;code class=&quot;language-text&quot;&gt;Client&lt;/code&gt; uses.&lt;/p&gt;
&lt;h3&gt;Session Injection during Automation Tests&lt;/h3&gt;
&lt;p&gt;At SoundCloud, we create a separate app delegate object to use when running automation tests, e.g. &lt;code class=&quot;language-text&quot;&gt;AutomationTestsApplicationDelegate&lt;/code&gt;. This can be a good place to set up application state that your tests rely on and/or clean up that state between test runs.&lt;/p&gt;
&lt;p&gt;We can hook into this system to detect whether or not tests are running. First we need to set an environment variable whenever tests are running. We can use &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;launchEnvironment&lt;/code&gt; property to set a flag. Say we’re writing a test for a sign-in flow in our app. We could alter the implementation of &lt;code class=&quot;language-text&quot;&gt;setUp&lt;/code&gt;, like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SignInFeature&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;launchEnvironment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;IsAutomationTestsRunning&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;YES&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_signIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// ... test code&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can dynamically set an app delegate class by deleting the standard &lt;code class=&quot;language-text&quot;&gt;@UIApplicationDelegate&lt;/code&gt; annotation that is added to the default app delegate class that Xcode creates and adding a &lt;code class=&quot;language-text&quot;&gt;main.swift&lt;/code&gt; file instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIKit&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; isAcceptanceTestsRunning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProcessInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;IsAutomationTestsRunning&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; appDelegateClass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AnyClass&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isAcceptanceTestsRunning &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AutomationTestsApplicationDelegate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AppDelegate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;UnsafeMutableRawPointer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;CommandLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unsafeArgv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindMemory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UnsafeMutablePointer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Int8&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; capacity&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;CommandLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;UIApplicationMain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;CommandLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSStringFromClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;appDelegateClass&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The main thing we do here is use the &lt;code class=&quot;language-text&quot;&gt;isAutomationTestsRunning&lt;/code&gt; flag in order to decide which app delegate object to use.&lt;/p&gt;
&lt;p&gt;We can use this to create a simple helper method on &lt;code class=&quot;language-text&quot;&gt;UIApplication&lt;/code&gt; to tell if tests are running:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; isAutomationTest&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; delegate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delegate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delegate &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;AutomationTestsApplicationDelegate&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then all we need to do is create the correct session depending upon whether or not tests are running and use it wherever we usually create the client:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isAutomationTest &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AutomationTestsURLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; client
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this, we already have the core of what we need to stub requests in our tests. However, using it as is would place a certain burden on developers; in order to stub requests, they first need to know what requests are made during a test, something which could potentially be a lot. Then they would need to manually make the requests, e.g. in Postman; save the responses to a file; and manually specify which stub should replace which request. The implementation currently lacks the API that would enable this final requirement, but since I’ve promised you automatic recording/replay functionality, I’ll skip straight to the good stuff!&lt;/p&gt;
&lt;h2&gt;Recording Stubs&lt;/h2&gt;
&lt;p&gt;What we’re aiming for is a simple API that takes the burden off developers and enables them to do network stubbing quickly. Our goal is to be able to call one API for recording, run our test, and if it succeeds, then switch to another API for replaying the recorded stubs — something like the record/replay functionality that is built into Xcode automation tests themselves.&lt;/p&gt;
&lt;p&gt;To achieve this, we rely on an important mapping throughout: &lt;code class=&quot;language-text&quot;&gt;URLRequest -&amp;gt; Stubbed Response&lt;/code&gt;. We will basically use the &lt;code class=&quot;language-text&quot;&gt;URLRequest&lt;/code&gt; to construct a path on disk to the stubbed response. We can then use this path to save responses to disk as JSON files when we’re in “record mode” and load them back when we’re in “replay mode.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/da59fc91a0d3e044d127ee78658f0c16/dob-sign-in-test.gif&quot; alt=&quot;Recording of responses during a test&quot;&gt;&lt;/p&gt;
&lt;p&gt;The gif above shows stubs being recorded for the &lt;code class=&quot;language-text&quot;&gt;test_singIn&lt;/code&gt; test.&lt;/p&gt;
&lt;h3&gt;Environment Setup&lt;/h3&gt;
&lt;p&gt;Before we get started with the automation and application side setup, it’s worth spending a little time on the part that binds the two together. We will need to pass data between the two processes, and because the &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;launchEnvironment&lt;/code&gt; dictionaries of &lt;code class=&quot;language-text&quot;&gt;ProcessInfo&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt;, respectively, are just “stringly typed,” we can add some syntactic sugar on top to make the whole thing a bit more type-safe:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;EnvironmentKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; stubbedTestName
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; networkStubsDir
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; recordMode
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;RecordMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; recording
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; replaying
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; processInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;ProcessInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processInfo

    &lt;span class=&quot;token keyword&quot;&gt;subscript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;EnvironmentKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; processInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rawValue&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCUIApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setEnvironmentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; environmentValue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forKey key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;EnvironmentKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; launchEnv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; launchEnvironment
        launchEnv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rawValue&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; environmentValue
        launchEnvironment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; launchEnv
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we basically define an &lt;code class=&quot;language-text&quot;&gt;EnvironmentKey&lt;/code&gt; enum that allows us to wrap the &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;launchEnvironment&lt;/code&gt; dictionaries of &lt;code class=&quot;language-text&quot;&gt;ProcessInfo&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt; in order to be able to pass data between the app and tests in a type-safe way. Along the way, we’ll discover what the various environment keys are used for, but most should be fairly self-explanatory.&lt;/p&gt;
&lt;h3&gt;Automation Tests Setup&lt;/h3&gt;
&lt;p&gt;We need to pass the name of the running test through to the app so that our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; can construct the directory in which to record/replay stubs. We can benefit from the fact that each test in a given test case is essentially a new instance of the test case. This is how &lt;code class=&quot;language-text&quot;&gt;XCTest&lt;/code&gt; helps ensure that individual tests don’t interfere with each other. In fact, for an example test case called, say, &lt;code class=&quot;language-text&quot;&gt;SignInFeature&lt;/code&gt;, if you print &lt;code class=&quot;language-text&quot;&gt;self.description&lt;/code&gt; during a test, you will see something similar to the following:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-[SignInFeature test_signIn]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The output above reflects the test case name (&lt;code class=&quot;language-text&quot;&gt;SignInFeature&lt;/code&gt;) and the actual running test (&lt;code class=&quot;language-text&quot;&gt;test_signIn&lt;/code&gt;). This is perfect for our needs because it provides us with an easy way to associate a test name with the requests that are made during the test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SignInFeature&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;launchEnvironment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;IsAutomationTestsRunning&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;YES&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startRecordingStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tearDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tearDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_signIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We implement &lt;code class=&quot;language-text&quot;&gt;startRecordingStubs&lt;/code&gt; as an extension on &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt;, passing &lt;code class=&quot;language-text&quot;&gt;self&lt;/code&gt; so that we can use &lt;code class=&quot;language-text&quot;&gt;self.description&lt;/code&gt; to get a name for the test. Although relying on the output from &lt;code class=&quot;language-text&quot;&gt;self.description&lt;/code&gt; may seem brittle, its convenience was too good for this developer to ignore:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCUIApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startRecordingStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;StaticString&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UInt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setupFileStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordingMode&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recording&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setupFileStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordingMode&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;RecordMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;StaticString&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UInt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; testName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;describing&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setEnvironmentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordingMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rawValue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forKey&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recordMode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setEnvironmentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forKey&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stubbedTestName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; networkStubsDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;networkStubsDirURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;setEnvironmentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;networkStubsDir&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forKey&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkStubsDir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code above basically sets &lt;code class=&quot;language-text&quot;&gt;test_signIn&lt;/code&gt; into “record mode” so that the app knows to start recording network requests during its execution. We’ll later use &lt;code class=&quot;language-text&quot;&gt;stubbedTestName&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;networkStubsDir&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;EnvironmentKey&lt;/code&gt; cases from the app to construct the path to the stubs on disk. The &lt;code class=&quot;language-text&quot;&gt;.networkStubsDir&lt;/code&gt; key will point to the root directory where all stubs are stored. The &lt;code class=&quot;language-text&quot;&gt;.stubbedTestName&lt;/code&gt; key will point to the name of the currently running test, used to create the path to the directory for all stubbed requests for this test.&lt;/p&gt;
&lt;p&gt;For the implementation of &lt;code class=&quot;language-text&quot;&gt;networkStubsDirURL&lt;/code&gt;, we take advantage of the default &lt;code class=&quot;language-text&quot;&gt;#file&lt;/code&gt; argument passed into every method, which gives the absolute path to the current file on disk. Using this, we can construct a new path for where we will record our network stubs, relative to some directory that we know will always be there, like &lt;code class=&quot;language-text&quot;&gt;AutomationTests/&lt;/code&gt; in the example below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;networkStubsDirURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;StaticString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UInt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; filePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description
    &lt;span class=&quot;token keyword&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; range &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filePath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;of&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AutomationTests/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;literal&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;XCTFail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;AutomationTests directory does not exist!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; testsDirIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filePath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;before&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; range&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;upperBound&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; automationTestsPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filePath&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;testsDirIndex&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileURLWithPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;automationTestsPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendingPathComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;NetworkStubs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isDirectory&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;App-Side Setup&lt;/h3&gt;
&lt;p&gt;In order to record the network responses in the app when the tests are running, we need to add a bit of functionality to our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AutomationTestsURLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionManaging&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; _session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;configuration&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; testStubsRecorder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TestStubsRecorder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataTaskFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token builtin&quot;&gt;CompletionBlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLSessionDataTasking&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isRecordingStubs &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; completion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testStubsRecorder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;recordResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;of&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; with&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; _session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldStub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AutomationTestsDataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; _session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; completionHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldStub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;_&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NetworkResponseStubStorage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stubExists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The logic of &lt;code class=&quot;language-text&quot;&gt;dataTaskFromRequest(_:completionHandler:)&lt;/code&gt; should be easy enough to read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we’re recording, record the stub and return the standard &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt; to the application.&lt;/li&gt;
&lt;li&gt;Otherwise, if we should stub the response, return an instance of our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsDataTask&lt;/code&gt;, which will do the stubbing.&lt;/li&gt;
&lt;li&gt;Otherwise, bypass the &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; by immediately returning a standard &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt; to the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve added three new private properties. The first is an instance of our &lt;code class=&quot;language-text&quot;&gt;Environment&lt;/code&gt; class. It has a new property, &lt;code class=&quot;language-text&quot;&gt;isRecordingStubs&lt;/code&gt;, which informs the &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; of whether or not it should record the responses from the network. This is based on the call we made back in &lt;code class=&quot;language-text&quot;&gt;setUp&lt;/code&gt; of our test case, i.e. &lt;code class=&quot;language-text&quot;&gt;app.startRecordingStubs(for: self)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; isRecordingStubs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; recordModelValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recordMode&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; recordModelValue &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;RecordMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recording&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rawValue
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The second property, &lt;code class=&quot;language-text&quot;&gt;_session&lt;/code&gt;, is an internal &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt; property. We use this when we want the network response to complete as normal, i.e. in points 1 and 3 above.&lt;/p&gt;
&lt;p&gt;The final property, &lt;code class=&quot;language-text&quot;&gt;testStubsRecorder&lt;/code&gt;, is an instance of a new class, &lt;code class=&quot;language-text&quot;&gt;TestStubsRecorder&lt;/code&gt;, which actually saves the responses to disk:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NetworkResponse&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;CompletionBlock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestStubsRecorder&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fileManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;FileManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;of request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; with completionHandler&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; @escaping &lt;span class=&quot;token builtin&quot;&gt;CompletionBlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;CompletionBlock&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;completionWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;networkResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NetworkResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;networkResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networkResponse&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;completionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;networkResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; networkResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; networkResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; completionWrapper
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;networkResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NetworkResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// handle errors&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; testName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stubbedTestName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; networkResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// create dir for test case if it doesn&apos;t already exist&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;createTestCaseDirIfRequired&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;forTestName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; testName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// create and return path where stub JSON file will be saved (inside the test case dir)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; stubPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeTestStubPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;forTestName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; testName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        fileManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;atPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; stubPath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contents&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The main method we’re interested in here is &lt;code class=&quot;language-text&quot;&gt;recordResult(of:with:)&lt;/code&gt;, which our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; calls whenever “record mode” is enabled. It uses a local function which wraps the passed completion block. This is the completion block that will return network responses to the application. We actually return this wrapped function and pass it as the completion parameter of the internal &lt;code class=&quot;language-text&quot;&gt;URLSession&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt; that our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt; holds so that when the data task’s completion block gets called, our &lt;code class=&quot;language-text&quot;&gt;recordResult(of:with:)&lt;/code&gt; method runs first, thereby writing the results to disk. This method then internally calls the &lt;code class=&quot;language-text&quot;&gt;URLSessionDataTask&lt;/code&gt;’s completion block so that the data can also be returned to the app as normal. The implementations of &lt;code class=&quot;language-text&quot;&gt;createTestCaseDirIfRequired&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;makeTestStubPath&lt;/code&gt; are omitted for brevity.&lt;/p&gt;
&lt;p&gt;One thing not explained so far is the &lt;code class=&quot;language-text&quot;&gt;AutomationTestsURLSession&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;shouldStub&lt;/code&gt; method. This makes use of a &lt;code class=&quot;language-text&quot;&gt;NetworkResponseStubStorage&lt;/code&gt; object, which is a simple object that makes use of the &lt;code class=&quot;language-text&quot;&gt;Environment&lt;/code&gt; object and &lt;code class=&quot;language-text&quot;&gt;FileManager&lt;/code&gt; in order to construct the file paths to the stubbed responses on disk. If &lt;code class=&quot;language-text&quot;&gt;shouldStub&lt;/code&gt; returns true, we return an instance of our &lt;code class=&quot;language-text&quot;&gt;AutomationTestsDataTask&lt;/code&gt;, which actually does the stubbing:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NetworkResponseStubStorage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fileManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;FileManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// return stub&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stubExists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// check if stub exists&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can take advantage of the &lt;code class=&quot;language-text&quot;&gt;NetworkResponseStubStorage&lt;/code&gt; object just introduced to update the &lt;code class=&quot;language-text&quot;&gt;resume&lt;/code&gt; method of &lt;code class=&quot;language-text&quot;&gt;AutomationTestsDataTask&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; jsonData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NetworkResponseStubStorage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HTTPURLResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; statusCode&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; httpVersion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headerFields&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jsonData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You might notice that, for the sake of brevity, we are hardcoding the &lt;code class=&quot;language-text&quot;&gt;statusCode&lt;/code&gt; parameter of the &lt;code class=&quot;language-text&quot;&gt;response&lt;/code&gt; object. If your app explicitly handles these status codes, you may need to extend the solution to also record/replay the status code for a given request.&lt;/p&gt;
&lt;p&gt;The only remaining thing to do is actually return the stubbed response during test runs. In order to do this, we switch the callback in the &lt;code class=&quot;language-text&quot;&gt;setUp&lt;/code&gt; method of &lt;code class=&quot;language-text&quot;&gt;SignInFeature&lt;/code&gt; from &lt;code class=&quot;language-text&quot;&gt;app.startRecordingStubs(for: self)&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;app.replayStubs(for: self)&lt;/code&gt;. The implementation is a simple one-liner added to the extension on &lt;code class=&quot;language-text&quot;&gt;XCUIApplication&lt;/code&gt; where we implemented &lt;code class=&quot;language-text&quot;&gt;startRecordingStubs(for:)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replayStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;XCTestCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;StaticString&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UInt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; #line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setupFileStubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; testCase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordingMode&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replaying&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So there we have it. With a relatively small amount of code, we’ve been able to design a powerful feature that should go some way to alleviating flakiness in automated tests in a way that is easy to use and saves development time.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we first demonstrated how it’s possible to stub the responses to network requests made during an automation test in order to improve the stability of our tests with just a small amount of code. We then showed how the recording and replaying of these stubs can be automated using a simple API in order to remove the burden from the developer and enable this stubbing to be done quickly.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qualitycoding.org/app-delegate-for-tests/&quot;&gt;How to Switch Your App Delegate for Improved Testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[PageRank in Spark]]></title><description><![CDATA[SoundCloud consists of hundreds of millions of tracks, people, albums, and playlists, and navigating this vast collection of music and personalities poses a large challenge, particularly with so many covers, remixes, and original works all in one place.]]></description><link>https://developers.soundcloud.com/blog/pagerank-in-spark</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/pagerank-in-spark</guid><pubDate>Wed, 24 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud consists of hundreds of millions of tracks, people, albums, and playlists, and navigating this vast collection of music and personalities poses a large challenge, particularly with so many covers, remixes, and original works all in one place.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;For &lt;a href=&quot;https://soundcloud.com/search&quot;&gt;search on SoundCloud&lt;/a&gt;, one of the ways we approach this problem is by using our own version of the &lt;a href=&quot;https://en.wikipedia.org/wiki/PageRank&quot;&gt;PageRank algorithm&lt;/a&gt;, which we affectionately refer to as &lt;a href=&quot;https://help.soundcloud.com/hc/en-us/articles/115003568168-Search-on-SoundCloud&quot;&gt;DiscoRank&lt;/a&gt; (Get it? Disco as in &lt;em&gt;disco&lt;/em&gt;very &lt;strong&gt;and&lt;/strong&gt; &lt;em&gt;Saturday Night Fever&lt;/em&gt;?!).&lt;/p&gt;
&lt;p&gt;The job of PageRank is to help rank search results from a query like finding all Go+ tracks called “&lt;a href=&quot;https://soundcloud.com/search/go?q=royals&quot;&gt;royals&lt;/a&gt;.” At first glance, this task might seem trivial. The first result is, and should indeed be, &lt;a href=&quot;https://soundcloud.com/lordemusic&quot;&gt;Lorde’s&lt;/a&gt; original song, “&lt;a href=&quot;https://soundcloud.com/lordemusic/royals-1&quot;&gt;Royals&lt;/a&gt;.” However, there are plenty of covers and remixes of this track, which leaves us with questions like: Which ones should we show at the top and in which order? What about other tracks in our catalog that have the word “royals” in them? Where should they be in our search results list?&lt;/p&gt;
&lt;p&gt;Our use of PageRank started in the summer of 2012 when we &lt;a href=&quot;https://blog.soundcloud.com/2012/05/09/next/&quot;&gt;introduced SoundCloud Next&lt;/a&gt; (now our current website). In the years since, we have grown exponentially, and our implementation of PageRank has had to scale with that enormous growth in several ways — from an in-memory Java implementation, to being distributed in Hadoop using the vanilla MapReduce API, and more recently, by using &lt;a href=&quot;https://twitter.github.io/scalding/powered_by.html&quot;&gt;Scalding&lt;/a&gt;. In the summer of 2017, we made the switch to our latest version of PageRank, which runs in &lt;a href=&quot;http://spark.apache.org&quot;&gt;Spark&lt;/a&gt;, and now we’re open sourcing our implementation as a reusable &lt;a href=&quot;https://soundcloud.github.io/spark-pagerank&quot;&gt;library&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;But before implementing yet another version ourselves, we first tried out the &lt;a href=&quot;https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.lib.PageRank$&quot;&gt;built-in PageRank from Spark&lt;/a&gt;. Unfortunately, we quickly realized it didn’t support the graph we were using, as we have nodes with no out-edges, some nodes with no in-edges, and edge weights that are not uniform.&lt;/p&gt;
&lt;p&gt;After this first try, we decided to implement PageRank ourselves and tried a variety of the Spark APIs, including &lt;a href=&quot;https://spark.apache.org/graphx&quot;&gt;GraphX&lt;/a&gt;. Although our GraphX implementation was working in small-scale test cases, when we ran at full-scale, we encountered several &lt;a href=&quot;https://issues.apache.org/jira/browse/SPARK-5480&quot;&gt;bugs&lt;/a&gt; that are currently unresolved. Based on this experience, we decided that a simple approach using the &lt;a href=&quot;https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD&quot;&gt;RDD API&lt;/a&gt; would suit us best. We spent about an equal amount of time on implementation and on performance evaluation and tuning, but in the end, Spark was a perfect fit for this job, and we were able to utilize many of the features Spark is known for, such as in-memory data storage, easy parallel computation, and the implementation of iterative learning algorithms.&lt;/p&gt;
&lt;p&gt;As a result of this work, we have made improvements to the performance, scalability, and general flexibility of our PageRank implementation. And because it’s able to perform one iteration of PageRank in approximately three to five minutes on a graph of more than 700 million nodes and 15 billion edges, this new implementation has enabled us to update ranks in search more frequently throughout the day, in addition to preparing us for continued growth over the coming years.&lt;/p&gt;
&lt;p&gt;With open sourcing our implementation of PageRank, we hope you can benefit from the work we’ve put into this for your own use cases, from search to recommendations. Go check out the &lt;a href=&quot;https://soundcloud.github.io/spark-pagerank&quot;&gt;project&lt;/a&gt;, try it out, open issues, and get involved!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Insights from SoundCloud’s DeveloperBridge Trainee Program]]></title><description><![CDATA[Back in 2016, the SoundCloud People Team collaborated with engineering management in an effort to bring more junior engineers into the…]]></description><link>https://developers.soundcloud.com/blog/insights-from-the-developerbridge-programme</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/insights-from-the-developerbridge-programme</guid><pubDate>Thu, 18 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Back in 2016, the SoundCloud People Team collaborated with engineering management in an effort to bring more junior engineers into the company. The result was DeveloperBridge, a paid junior engineering program that ran for 12 months and was based out of our Berlin headquarters.&lt;/p&gt;
&lt;p&gt;Trainees gained hands-on experience under the guidance of our engineers, and by working on a variety of assignments in selected teams, they became familiar with different types of engineering tasks and technologies. This, in turn, helped them develop a feel for the areas they enjoyed working in the most. The overall aim of the program was to help the participants develop and then hire them afterward into full-time roles at SoundCloud, while improving diversity in Engineering at the same time.&lt;/p&gt;
&lt;p&gt;Now with their traineeship year coming to a close, we’ve asked the participants to reflect on the experience, what they learned, how they feel about their accomplishments, and where they see themselves headed next in their careers as software engineers.&lt;/p&gt;
&lt;h2&gt;Suzanne Wood&lt;/h2&gt;
&lt;p&gt;The DeveloperBridge program exposed me to different teams so I could both improve my programming skills and learn more about how SoundCloud enables users to share and discover music. I learned new programming languages and developed confidence in my ability to pick up more.&lt;/p&gt;
&lt;p&gt;Over the course of the year, I worked on web development and data processing projects. I created features for and improved the UX of an internal web app using React, Typescript, and JavaScript, and I made a web service using Ruby, JavaScript, Mustache, and regular expressions. I also created ETL (Extract, Transform, Load) processes using different methods to determine which was the most efficient, and I used Spark and SQL together with the Tableau analytics platform to enable the flexible display of data.&lt;/p&gt;
&lt;p&gt;During the program, I also learned new approaches to tackling problems. For example, it is good to make small iterative changes to code, because if you make too many changes at once, errors can become blockers. Through collaboration, we shared ideas and approaches, leading to a more maintainable and consistent codebase. I also really like the hardworking yet relaxed office atmosphere — especially the office dogs!&lt;/p&gt;
&lt;p&gt;I now have a feel for what programming is really about: It is abstract and creative, and it requires a lot of thought to design a system well. This appeals to me, so I will continue to pursue it as a career. In particular, I’d like to continue with frontend or full-stack web development roles — I like delivering something tangible that people can use and appreciate, with clear and consistent designs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Suzie has accepted a full-time position on the SoundCloud Payments Team.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find Suzie on the web &lt;a href=&quot;https://www.linkedin.com/in/suzanne-wood-03&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Mike Smith&lt;/h2&gt;
&lt;p&gt;Some things about joining SoundCloud for the DeveloperBridge program met my expectations (ping pong tables! bean bags!), but many didn’t. I didn’t anticipate how much work goes into doing seemingly simple things. I had expected something more structured, but the refreshing reality was that I had the opportunity to pursue what I was interested in and motivated by, and I was allowed to develop at my own pace, which suits my working style completely.&lt;/p&gt;
&lt;p&gt;At first, Scala was a challenge. But the support I got and the freedom I was afforded led me to be surprised on a daily basis about how much I can understand.&lt;/p&gt;
&lt;p&gt;The people I worked with had years of experience, and they were well-versed in being productive in technology. Observing their behaviors and attitudes gave me insight into what it takes to work at a high level — something that is difficult to teach. I also found I learned most from those with more diverse backgrounds — who could sympathize with being the “odd one out” on the team — as opposed to the people who were the best technical coders. If the company is to continue developing junior talent, fostering diversity must be a key aspect.&lt;/p&gt;
&lt;p&gt;Extensive experience with the React/Redux/Typescript/Webpack stack has put me in the position where I’m able to advise other people on these technologies, allowing me to, in some way, pay forward all the help I’ve had from other people along the way. I now consider myself a professional software engineer, which is something I would not have believed a year ago. I’d love to continue developing my skills and eventually use my experience to teach others who want to take the same path I did.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mike has accepted a full-time position on the SoundCloud Core Clients Team.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find Mike on the web &lt;a href=&quot;https://www.linkedin.com/in/entering-mike-smith-zone&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Lucy Clarke&lt;/h2&gt;
&lt;p&gt;A few people I knew who worked at SoundCloud told me I would be working with some of the kindest and smartest people they knew — and they were right. I was super excited about the position, having come from a music industry background and having used SoundCloud as a music discovery tool for years.&lt;/p&gt;
&lt;p&gt;For most of the year, I have been working on an RSS service for podcasters. This service is built in Scala and replaces an older service that was written in Clojure. I had no previous experience with Scala and had zero exposure to lisp languages. But along the way, I became competent in Scala and was able to work my way through Clojure code. Podcasting is a segment I care a lot about, and I am proud of the work I did on a useful service for podcasters that serves a lot of traffic.&lt;/p&gt;
&lt;p&gt;As the RSS service for podcasters was essentially a new project, I was able to be involved with all aspects of building the service — from coding, to integration with CI, Docker, and Kubernetes, to alerting monitoring and scaling traffic between the old and the new service for fast feedback. As the previous service had not been touched in some time, we were also able to reconsider previous decisions around logic. I know more about podcasting, XML, aggregators, and iTunes than I thought possible.&lt;/p&gt;
&lt;p&gt;Surprisingly, my pun game also really improved.&lt;/p&gt;
&lt;p&gt;I want to continue to develop my skills as a backend engineer. I would love to keep working on products that enable audio creators to do their best work.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Lucy has accepted a full-time position on the SoundCloud Royalties and Reporting Team.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find Lucy on the web &lt;a href=&quot;http://linkedin.com/in/llcclarke&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Thea Kupler&lt;/h2&gt;
&lt;p&gt;My “Rails Girls Summer of Code” placement was hosted by SoundCloud, and this was followed by an internship with SoundCloud, so I was already familiar with the company infrastructure and culture, and I was excited to have found an environment in which I felt comfortable and supported.&lt;/p&gt;
&lt;p&gt;I worked on several teams and on several projects during the program. I worked on building a web app to view and compare event data stored in Hadoop, rewrote Java components in Scala, added functionality to a React frontend, and contributed to a Ruby web app. For my final project, I am contributing to a web app written in Clojure. My work includes generating folders and files, zipping everything up, and pushing it to GitHub automatically. This also reminds me of another a thing I have learned and will always still be learning: git, git, and git.&lt;/p&gt;
&lt;p&gt;I am proud of everything I accomplished. I took the opportunity to give demos in front of the company. I got the support to share and celebrate my pride and work.&lt;/p&gt;
&lt;p&gt;I gained confidence in what I am capable of, which helps me focus on learning, improving, and tackling new challenges, rather than wasting my energy on feeling like an imposter. I still remember the moment when I suddenly started explaining and defending my code choices rather than just silently implementing what I was told to do differently.&lt;/p&gt;
&lt;p&gt;I’d love to shift my focus more toward backend web development — either building up the knowledge I already have, or learning something completely new, especially working for a non-profit organization or one in the field of sustainability. Long term, I want to foster my passions in a more balanced way: less coding and hacking, more coaching and healing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;After 18 months of self-led training through internships and this program, Thea has chosen to leave SoundCloud to take some time to pursue other personal and professional options.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find Thea on the web &lt;a href=&quot;http://www.linkedin.com/in/thea-kupler&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/froilainhaeckse&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Marina Mircheska&lt;/h2&gt;
&lt;p&gt;I saw the program as a perfect start to my career in this industry. My interests as a Computer Science major were scattered across a few areas, and I was delighted to have the opportunity to find out what fit best. Moreover, this was about to happen in a very cool Berlin startup with an international team of specialists.&lt;/p&gt;
&lt;p&gt;For the better part of this past year, I worked on deep learning for content-based music recommendations. Deep learning is a hot topic, and I got an exciting chance to work with real data and have all the computational power I need.&lt;/p&gt;
&lt;p&gt;The DeveloperBridge program was my first opportunity to apply an abstract concept from textbooks and research papers to real data, and the challenges I faced — surprisingly — were not only on the theoretical side. It took some long thinking about design and optimization, experimenting and testing every step and decision while laying the infrastructure of the project. It took a while before training the neural network and getting initial results, and then a different kind of challenge arose: how to debug a machine learning algorithm.&lt;/p&gt;
&lt;p&gt;I also worked on a real-time recommendation service, thereby improving my Go skills; built Tableau dashboards for analytics purposes; wrote ETLs using Scala and Spark; and did some exploratory data analysis using Python Notebooks.&lt;/p&gt;
&lt;p&gt;Furthermore, I learned how to be part of team meetings and participate in discussions efficiently, while at the same time keeping communication healthy, fair, and respectful. My supervisors and my mentors set an example for patience, generous teaching, and efficient collaboration, and because of that, I will definitely be a better team player in the future.&lt;/p&gt;
&lt;p&gt;During my master’s degree, I focused on Data Science and Algorithmics, and my time at SoundCloud helped me gain the experience of a junior data scientist. Considering my interests, I will try to build myself as a Machine Learning Engineer in the coming years.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Marina has accepted a full-time position on the SoundCloud Discovery Team as a data scientist.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find Marina on the web &lt;a href=&quot;https://www.linkedin.com/in/marina-mircheska&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How SoundCloud Uses HAProxy with Kubernetes for User-Facing Traffic]]></title><description><![CDATA[A little less than two years ago, SoundCloud began the journey of replacing our homegrown deployment platform, Bazooka, with Kubernetes. Kubernetes automates deployment, scaling, and management of containerized applications.]]></description><link>https://developers.soundcloud.com/blog/how-soundcloud-uses-haproxy-with-kubernetes-for-user-facing-traffic</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-soundcloud-uses-haproxy-with-kubernetes-for-user-facing-traffic</guid><pubDate>Thu, 07 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little less than two years ago, SoundCloud began the journey of replacing our homegrown deployment platform, &lt;a href=&quot;http://gotocon.com/zurich-2013/presentation/Building%20an%20in-house%20Heroku&quot;&gt;Bazooka&lt;/a&gt;, with &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;. Kubernetes automates deployment, scaling, and management of containerized applications.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/570aa/cdn-haproxy-k8s.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACK0lEQVQ4y4VU247aMBDl//+o6sM+INGCllZUaC9cQggk5EbuxobgnM64GzZhkTqS5bEyczxz5jiDpmnAi413KSXCMMR6vcZ2u4Xv+7BtG8vlEoeDR/4Gq9Wa/APquoZS6pbLa9Ae2j3LMux2OziOA9uy4GwsWJsNFot3WNYWs9k7fbMNKF98Pp/RxRh0wa7Xq9m11tC0X5Ij9EfCP2sQBBqnE8ddTYVtXotxA+TKjscjiqJAWZYo8hySfJsq/TkeY/r8jDiOiBKFPD8RqIAQlYmPouhrhVx6VVUUSMFCQNJZunuUnoskjpGkKYGdaF3guQpRqIg/aTjkvBtgt/+eUdsoC2i6AJSIs0QtFFWpwR1WFVFyAbXdtnvXMlc2HA4xn8/N+dd0ismPEeI0M+f5nxmevn0nagpznkwmGI3GSKny3pRbQCaYORTULlsa+EgWb6ijEJAC4esCi/EbjYUrbLC1M3hewq30p/yo5Ya0SD0BoY8mS9H4LmovQOFLVFkNQSxUZSe+o5SebIwRmPb20Mwfy4aJKnOI4grh5whWMTiyLElaurm1+lDYOVXjzn7TAJRJAskCBArSnO2USBOB1csL4khxyEPrATJ/zn5/++jRcwt3jvEPnkf6y40fBCm9nLWREUuGB8M65sEO7nnoXUABrD90OTat8WBq86Ik6TBJEgPI4F/eMgc9uuDzJ9L0pHoSNb2W3AAqdf6s8D75v/4HolYs9tgsftN/ARuiiYVxa4vwAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HAProxy routing to Kubernetes pods&quot;
        title=&quot;HAProxy routing to Kubernetes pods&quot;
        src=&quot;/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/8ff1e/cdn-haproxy-k8s.png&quot;
        srcset=&quot;/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/9ec3c/cdn-haproxy-k8s.png 200w,
/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/c7805/cdn-haproxy-k8s.png 400w,
/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/8ff1e/cdn-haproxy-k8s.png 800w,
/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/6ff5e/cdn-haproxy-k8s.png 1200w,
/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/2f950/cdn-haproxy-k8s.png 1600w,
/blog/static/6cb36141e8e4bfe91edaa69597db5b6a/570aa/cdn-haproxy-k8s.png 2224w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;An ongoing challenge with dynamic platforms like Kubernetes is routing user traffic — more specifically, routing API requests and website visits from our users to the individual pods running in Kubernetes.&lt;/p&gt;
&lt;p&gt;Most of SoundCloud runs in a physical environment, so we can’t leverage the built-in support for cloud load balancers in Kubernetes. At the edge of our infrastructure, a fleet of HAProxy servers terminates SSL connections and, based on simple rules, forwards traffic to various internal services. The configuration for these servers is generated and tested separately before it is deployed to these terminators. But because there are a lot of safeguards built in, this process takes a long time to complete, and it cannot keep up with the rate at which pods in Kubernetes are moving around. The fundamental challenge of getting user traffic to the pods that keep moving around is a mismatch between our static termination layer and the highly dynamic nature of Kubernetes.&lt;/p&gt;
&lt;h2&gt;The Process&lt;/h2&gt;
&lt;p&gt;To address this challenge, we initially configured the terminator layer to forward HTTP requests to a separate HAProxy-based &lt;a href=&quot;https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers&quot;&gt;Ingress controller&lt;/a&gt;, but this solution did not work well for us, as the Ingress controller was intended for low-volume internal traffic and is not very reliable.&lt;/p&gt;
&lt;p&gt;Our users generate a lot of traffic, and every problem in the traffic stack means that SoundCloud is not working for someone. Between the Kubernetes Ingress and the terminator configuration, we had two layers of &lt;a href=&quot;https://en.wikipedia.org/wiki/OSI_model#Layer_7:_Application_Layer&quot;&gt;Layer 7&lt;/a&gt; routing that needed to match up and often didn’t. This was frustrating for us as developers and resulted in additional work for us. We also knew that the Ingress controller would not be able to handle the long-lived connections used by some of our clients.&lt;/p&gt;
&lt;p&gt;When SoundCloud engineers build applications, we use a custom command-line interface that generates the Namespace, Service, and Deployment. Optionally, it generates the Ingress Kubernetes objects from command-line flags. We added a flag to the tool that generates these objects, which changes the Service to the &lt;a href=&quot;https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport&quot;&gt;NodePort&lt;/a&gt; type.&lt;/p&gt;
&lt;p&gt;Kubernetes allocates a port number that is not yet used in the cluster to this service, and it opens this port on every node in the cluster. Connections to this port on any of the nodes are forwarded to one of the instances for this service. (When we generate the Kubernetes objects, there is a one-to-one correspondence between Service and Deployment objects. For the sake of brevity, we’ll gloss over the details of ReplicaSet, Pod, and Endpoints objects in Kubernetes here.)&lt;/p&gt;
&lt;p&gt;Note that this is irreversible for a given Service — Kubernetes does not allow removing the node port from a service. We are still looking for a solution for this — thus far, this has only happened early enough in the service lifecycle that we could delete and recreate the service, but this could lead to service interruptions later on when the service is used in production.&lt;/p&gt;
&lt;p&gt;To configure their applications to receive traffic from the internet, application developers declare the public hostname and path, as well as the cluster, namespace, service, and port name for the applications. The systems routing public traffic to applications, such as SSL terminators, CDN distributions, and DNS entries, are configured based on this declaration.&lt;/p&gt;
&lt;h2&gt;The Implementation&lt;/h2&gt;
&lt;p&gt;When the terminator configuration is generated, the script queries the Kubernetes cluster to find the assigned node port for each service, along with the list of Kubernetes nodes. Originally, we added all nodes into the terminator configuration, but this turned out to be a problem.&lt;/p&gt;
&lt;p&gt;Each terminator independently checks the health of each port on each node. With several dozen terminators and hundreds of Kubernetes nodes, this amounted to tens of thousands of health checks being generated every second. Because of the indirection through the nodes, this was not affected by the size of the service itself, so even services that would normally get very little traffic needed a lot of resources just to be able to satisfy these health checks.&lt;/p&gt;
&lt;p&gt;We needed to reduce the number of nodes configured for each service, but we also did not want to send all the traffic through only a limited set of nodes to avoid them becoming a scaling bottleneck. One possible solution would be to simply pick a number of nodes out of the list at random, but this would mean that the configuration completely changes every time it is generated, obscuring the real differences.&lt;/p&gt;
&lt;p&gt;Instead, we decided to use rendezvous hashing between the service name and the node address to pick a fixed number of servers per backend. With this method, a different set of nodes are selected for each service, but the selection is always the same as long as the nodes do not change.&lt;/p&gt;
&lt;p&gt;We chose a high enough number of nodes so that we didn’t need to worry about one or two nodes going away or the nodes overlapping between different high-traffic services.&lt;/p&gt;
&lt;p&gt;To replace a node, we simply restart the pipeline that generates and deploys the terminator configuration. This takes several hours, but it is fully automated. Because each service is routed through a limited number of nodes, we have to take care not to take too many nodes out of service at once. This means we can only replace a limited number of Kubernetes nodes per day, but as of yet, it hasn’t been a problem.&lt;/p&gt;
&lt;h2&gt;The Node Agent&lt;/h2&gt;
&lt;p&gt;During short-term maintenance, such as when rebooting nodes for kernel upgrades, we needed the ability to gracefully drain a node. We wrote an agent for the HAProxy agent-check protocol that listens on a fixed port on every node. For the sake of simplicity, we decided to always remove traffic and pods at the same time. Now, when the node is cordoned in Kubernetes, preventing new pods from being scheduled onto it, the agent updates the HAProxy server state to shift traffic away from this node.&lt;/p&gt;
&lt;p&gt;From time to time, we also need to shift traffic between different deployments of a service on the same cluster or between clusters. To support this, we extended the agent. By adding some annotations on the Kubernetes Service object, we can instruct it to open another per-service agent-check port that includes not only the node maintenance status, but also the desired weight of this service.&lt;/p&gt;
&lt;p&gt;The terminator configuration generation picks this up and configures the agent check accordingly. Application developers can now add multiple Kubernetes backends to any public host and path. For each backend, we select a number of nodes using rendezvous hashing as before and combine the results. By changing the Service annotations, the weight for each backend can then be changed within seconds.&lt;/p&gt;
&lt;h2&gt;The Future&lt;/h2&gt;
&lt;p&gt;We are quite happy with the NodePort-based routing, but there are some caveats.&lt;/p&gt;
&lt;p&gt;For one, the load distribution between the instances of an application is not very even, but we compensate by slightly over-provisioning the corresponding apps. At this point, the cost of the increased resource usage is minor compared to the cost of engineering a better load balancing mechanism.&lt;/p&gt;
&lt;p&gt;Additionally, operating Kubernetes in physical data centers is a challenge. There are standardized solutions for most use cases on cloud providers, but each data center is slightly different — especially if you are not starting from scratch, but rather integrating Kubernetes into an existing infrastructure.&lt;/p&gt;
&lt;p&gt;In this blog post, we described how we solved one particular problem for our particular situation. What’s next? The questions we are thinking about currently revolve around having multiple Kubernetes clusters in different locations, and determining how to route traffic to them in a way that results in each user having the best experience possible.&lt;/p&gt;
&lt;p&gt;How have you solved this problem? We would love to hear how you run Kubernetes. Leave a comment below!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Leveraging frameworks to speed up our development on iOS - Part 1]]></title><description><![CDATA[Growth in code bases come with exciting scalability challenges. As the size of our iOS codebase and team at SoundCloud grew, we faced challenges: long compile times and conflicts. Our productivity started to suffer as a result. We took inspiration from the work done in the backend (Building Products at SoundCloud) and applied it to mobile development. The main goal was to get back to a state where development is fun, fast, and would scale as the number of contributors grew. We modularized our iOS project by splitting it up into modules with well-defined responsibilities and public interfaces that interconnect them.]]></description><link>https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1</guid><pubDate>Mon, 16 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Growth in code bases come with exciting scalability challenges. As the size of our iOS codebase and team at SoundCloud grew, we faced challenges: long compile times and conflicts. Our productivity started to suffer as a result. We took inspiration from the work done in the backend (&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;Building Products at SoundCloud&lt;/a&gt;) and applied it to mobile development. The main goal was to get back to a state where development is fun, fast, and would scale as the number of contributors grew. We modularized our iOS project by splitting it up into modules with well-defined responsibilities and public interfaces that interconnect them.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;It’s been a little over a year since we started our journey towards a modularized iOS app and we’re very happy with the results that we’ve achieved so far. In this series of posts we’ll talk about the modularization journey, the motivation behind deciding for modularizing the app, a technical overview of the approach, and the impact it has on developers when they work on new features.&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;Originally, the iOS app was organized in two targets: one target that compiled the main app, and another target that compiled and ran the unit tests. The source code was mainly Objective-C, although some developers had already started adding Swift to the codebase. Moreover, the project used &lt;a href=&quot;https://cocoapods.org&quot;&gt;CocoaPods&lt;/a&gt; to fetch and statically link around 20 dependencies.
The product was growing at a good pace and so did the size of the two main targets. The build time became very high, and the lack of some control over how things were getting imported triggered clean builds when a single file was modified. Waiting for the build gave us the perfect amount of time to grab a coffee, but we’d have a serious problem with our caffeine intake if we didn’t do anything about it. In a team of 10 people, doing 20 clean builds per day, each taking 7 minutes, the cost was 1400 minutes, 23.3 hours, or 3 days of work. These build were a waste of time and money. Also, delivering features took too long compared with our other platforms.&lt;/p&gt;
&lt;p&gt;On top of all that, CocoaPods and our git branch-based workflow made the problem worse  as we had to run &lt;code class=&quot;language-text&quot;&gt;pod install&lt;/code&gt; on most branch switches &lt;em&gt;(It’s worth noting that CocoaPods has improved a lot since. The latest version prevents Xcode from invalidating its build cache after running pod install.)&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Why modularization&lt;/h2&gt;
&lt;p&gt;Most of our development time went into the compilation of the huge app and tests target. We undertook research by looking at peer companies and conducting interviews. After careful research we put all the options on the table and decided towards splitting the iOS app into modules. With smaller compilation targets, modules would take much less time to compute and run, and developers could quickly iterate over better solutions. Moreover, smaller targets would encourage good API design, and allow us to leverage tools like &lt;a href=&quot;https://www.apple.com/swift/playgrounds/&quot;&gt;Playgrounds&lt;/a&gt; and example apps to iterate on features without compiling the main app.&lt;/p&gt;
&lt;p&gt;Our research showed that most companies going through this transition had bigger teams than us, including teams dedicated to building and maintaining tools to facilitate their transition. We had to come up with a plan to gradually transition to building modular features, iterating over the new architecture while still allowing the team to build features.&lt;/p&gt;
&lt;p&gt;Some companies are very successful using React Native, including SoundCloud with &lt;a href=&quot;https://itunes.apple.com/us/app/soundcloud-pulse/id1074278256&quot;&gt;Pulse&lt;/a&gt;. We ruled out that option because the framework comes with integration costs that we could not assume in the listener application: tooling maintenance and training for instance. However, React Native makes a lot of sense for some features and we may reconsider it in the future.&lt;/p&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;h3&gt;Clean interfaces (APIs)&lt;/h3&gt;
&lt;p&gt;When all the code is in the same target, having access to any other class is a matter of importing a header &lt;em&gt;(Objective-C)&lt;/em&gt;, or having an internal access level &lt;em&gt;(the default one in Swift)&lt;/em&gt;. It’s flexible, but at the same time dangerous since it allows everyone to create dependencies with almost no control. Having no control over how dependencies get imported leads to a very messy dependency graph, coupled code, and eventually to unpredictable states propagating through that graph. In a small app, with a small team, you can avoid these issues with code review. However, with our application and our team this was too hard to do.&lt;/p&gt;
&lt;p&gt;Modules help you prevent these issues by exposing an API that you as a developer need to define. As part of your workflow, you need to define how the API of your module will look like and how consumers of your API will interact with it. In Objective-C that means specifying which headers will be public, and in Swift, which components will have the public access level. An API defines a contract in the same way REST APIs do, and developers who consume your API will have to comply with it.&lt;/p&gt;
&lt;h3&gt;Example apps&lt;/h3&gt;
&lt;p&gt;Since modules include standalone features they can include example app for testing the features without the main iOS application. We recently built the new home screen in a separate app using test data, which allowed us to test different scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Having tracks with long titles that exceed the limit of the label.&lt;/li&gt;
&lt;li&gt;Having tracks with no artwork.&lt;/li&gt;
&lt;li&gt;Having no data because of a connection error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With example apps, you can iterate on your feature with fast build and test times, designing the public API that the main application will eventually use. Following with the example above, the home feature would expose a view controller that the main application would push on the view hierarchy.&lt;/p&gt;
&lt;p&gt;The image below shows the structure of one of our modules that include an example app that you can experiment with.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8036afa4de908d212e78fd25f6bbbadf/9bac8/ios-example-app.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.67805953693495%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACRklEQVQoz42Ty08TURTGJ/4BgkYwshEoFK0IKzHRaHyhppgYjVt3mqAkmhjQYGx1gXvcYFr8N3AjC/fGsR1a2jK282jnaae0dDrT1Laf945prUYTT3JyH7nzm++e812mUilDFAVYlgXHcWDbNuyaA7O0563bbUBTZOT4FFRFg6HpyGVz2E5ueylLMqyiBaWggP3MgqmYCYhf49B00wPUajUvJVmCYRgolUoQBAGGaUKWZaiq6mUmk0E2m4Wu696anksmk2B2VRYCz2Kv6vQAHXzTREhEeT5f8D6iUa1WUSgUuhBV+wmne51g7IqO/M5HKKrWBbr1OprfG3AdF41GAzzPI/QihGdLT/F8eRmvV1bwfmMD79bXsbS4iJfhMB7OP8Dmh00wjluHIX0idTDhkjkFUnAnWq0WWJbF2KgP/fv7vDx39gxehUO4eSOIg339ODwwiH0Mgzerq0ThrgBZSKNYLBGg2wW2STdoNptNxGMxnD41A9/IKCbG/bhz+xYeLdxH8NoVTE2exDH/BAYPDSAaiYAR8iIpOo9yudy98m/AVhOxLzEEjgcwdGQII0eHcX12FvP37uLyxQvkJz5P/QGi9O3aGhirbJOG2ETdrw53gDQ8hfE4pqemMUxgft845oJzePJ4AVcvnSewMUwGTnjXjkaiYCioV9mfQFoGRVGQSqXAxTlscRzS6TQkScROJg2O20IikUCMlEUnHmV6Vf0NSOcm8eD/BtMB/QtIFVLfaZrmjQrxnKEb5HWJ3X1qePoIqCN+ANyE4Bt0L3SEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ios example app&quot;
        title=&quot;ios example app&quot;
        src=&quot;/blog/static/8036afa4de908d212e78fd25f6bbbadf/8ff1e/ios-example-app.png&quot;
        srcset=&quot;/blog/static/8036afa4de908d212e78fd25f6bbbadf/9ec3c/ios-example-app.png 200w,
/blog/static/8036afa4de908d212e78fd25f6bbbadf/c7805/ios-example-app.png 400w,
/blog/static/8036afa4de908d212e78fd25f6bbbadf/8ff1e/ios-example-app.png 800w,
/blog/static/8036afa4de908d212e78fd25f6bbbadf/9bac8/ios-example-app.png 907w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Playgrounds&lt;/h3&gt;
&lt;p&gt;Similarly to an example app, Playgrounds allow you to import your module and use its APIs. Changes run in real time and you can even see UI components there. We found Playgrounds very useful for documenting and onboarding.&lt;/p&gt;
&lt;p&gt;You can check out &lt;a href=&quot;https://talk.objc.io/episodes/S01E51-playground-driven-development-at-kickstarter&quot;&gt;this episode&lt;/a&gt; that shows how Kickstarter leverages Playgrounds to develop their features and try them with different scenarios.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/602eee3352663d0b66444926a0aca01a/64b01/ios-playground.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.377756471716204%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABE0lEQVQoz5WQy26DMBBFeRmblzEOwYYmqtIsumIBiP//tFvPRFSRqr4WR4MH+c74RF3Xoa5raK3Rti2apoG1lntUq6pCFEV/xzmHbduYdV2x7zvXeZ6xLAumaUKSJJBSQgjB5Hn+5TvLskfgsQFdIuI4ZtI05TNdKIoCZVlyPVBKMUefBnIgPavvew7419O+wxgDQpO/4JEJHgnySJX86qf+85n9Nw//tG1kOsPy/TBgcgNc2Hb0Hn4cQX69HzGESlD/HP4foRRwDP0MdHZA31koY5H1LxDhLJREmUvInKQLqCBcBfGJyBElv6h5dVe8X+6Y7jdU1zdU4wXanXCxJ5zbMNnWcLrBGDbKGoNY1kh+CPwAgoW0J8UMFp8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ios playground&quot;
        title=&quot;ios playground&quot;
        src=&quot;/blog/static/602eee3352663d0b66444926a0aca01a/8ff1e/ios-playground.png&quot;
        srcset=&quot;/blog/static/602eee3352663d0b66444926a0aca01a/9ec3c/ios-playground.png 200w,
/blog/static/602eee3352663d0b66444926a0aca01a/c7805/ios-playground.png 400w,
/blog/static/602eee3352663d0b66444926a0aca01a/8ff1e/ios-playground.png 800w,
/blog/static/602eee3352663d0b66444926a0aca01a/64b01/ios-playground.png 1043w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Fewer merge conflicts&lt;/h3&gt;
&lt;p&gt;Having a single project that indexes all the files and configurations in your projects leads to conflicts quite often when more than one branch is modifying the project file. By having different projects that contain the modules we still encounter conflicts but they are less probable. As a result, we spend less time solving conflicts on git when someone merges first and also modifies the project file.&lt;/p&gt;
&lt;h3&gt;Exploring new patterns&lt;/h3&gt;
&lt;p&gt;Working on modules allows developers to decide about the code architecture and the patterns that they want to use internally. They can even choose the language, for instance, using Objective-C instead of Swift, or maybe even React Native in the future. However, the public APIs of these modules need to comply with a contract to ensure there’s consistency across all our public APIs. We’ll talk in more detail in the upcoming articles about what the contract looks like.&lt;/p&gt;
&lt;p&gt;Moreover, we’ve tried to commit to Cocoa patterns for the interfaces of core components that modules have access to. Since developers are familiar with those patterns &lt;em&gt;(delegate, completion block…)&lt;/em&gt; they don’t need to spend time figuring out how to hook those components into their features.&lt;/p&gt;
&lt;h2&gt;Pitfalls&lt;/h2&gt;
&lt;h3&gt;Maintenance&lt;/h3&gt;
&lt;p&gt;Maintaining a modular setup becomes a cumbersome process when you have a lot of modules. &lt;code class=&quot;language-text&quot;&gt;.xcconfig&lt;/code&gt; files are a good tool to ensure that all targets share the same configuration, yet it’s not enough. When it comes to specifying the targets that you want for each project, the schemes that are available, and the configuration of those schemes, there’s no tool or API that you can leverage to automate the process of creation and maintenance. We could use a tool like CocoaPods but they would decide the structure of our modules, which is  too restrictive for our requirements. Buck is another option that we considered, but it didn’t fully support Swift back then and would have required a lot of investment that we couldn’t afford.&lt;/p&gt;
&lt;p&gt;We are internally exploring some tools to automate the process of creating and maintaining new modules.&lt;/p&gt;
&lt;h3&gt;External dependencies&lt;/h3&gt;
&lt;p&gt;In our experience, having a large amount of external dependencies brought unnecessary complexity and work, so we tried to reduce them. However, there are some dependencies that are necessary, which we need to fetch and link from the app. Some examples of these dependencies are &lt;a href=&quot;https://fabric.io/kits/ios/crashlytics/install&quot;&gt;Crashlytics&lt;/a&gt;, &lt;a href=&quot;https://firebase.google.com/docs/ios/setup&quot;&gt;Firebase&lt;/a&gt; or &lt;a href=&quot;https://developers.facebook.com/docs/ios/&quot;&gt;Facebook&lt;/a&gt;. Each dependency is distributed in a different way. Some provide a .podspec that you can use from CocoaPods. Some others publish the compiled framework/library and headers that we can fetch and link from the app. If they support &lt;a href=&quot;https://github.com/carthage/Carthage&quot;&gt;Carthage&lt;/a&gt;, they include a project that gets compiled by Carthage and whose output &lt;code class=&quot;language-text&quot;&gt;.frameworks&lt;/code&gt; gets copied to the Carthage build folder. There’s no standardized way to distribute dependencies and that makes things really hard, especially with no dependency management tool.&lt;/p&gt;
&lt;p&gt;There’s some manual work that we had to do in this area to generate a compiled version of the dependency that we can use, and that includes information about the version. Unfortunately, there’s no automation that we could do since as I mentioned, there’s no homogeneity in the setup of the dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In the next post of this series, we’ll dive into technical details. We’ll present the current project architecture, guiding you through the principles of it, as well as some iterations the architecture has gone through based on the feedback from the team.
Stay tuned!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud's Data Science Process]]></title><description><![CDATA[Here at SoundCloud, we’ve been working on helping our Data Scientists be more effective, happy, and productive. We revamped our organizational structure, clearly defined the role of a Data Scientist and a Data Engineer, introduced working groups to solve common problems (like this), and positioned ourselves to do incredible work! Most recently, we started thinking about the work that a Data Scientist does, and how best to describe and share the process that we use to work on a business problem. Based on the experiences of our Data Scientists, we distilled a set of steps, tips and general guidance representing the best practices that we collectively know of and agree to as a community of practitioners.]]></description><link>https://developers.soundcloud.com/blog/soundclouds-data-science-process</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundclouds-data-science-process</guid><pubDate>Wed, 04 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here at SoundCloud, we’ve been working on helping our Data Scientists be more effective, happy, and productive. We &lt;a href=&quot;https://medium.com/@_eleftherios/https-medium-com-eleftherios-above-the-clouds-5-years-of-data-at-soundcloud-part-1-8803e2059fa&quot;&gt;revamped&lt;/a&gt; our organizational structure, clearly defined the role of a Data Scientist and a Data Engineer, introduced working groups to solve common problems (like this), and positioned ourselves to do incredible work! Most recently, we started thinking about the work that a Data Scientist does, and how best to describe and share the process that we use to work on a business problem. Based on the experiences of our Data Scientists, we distilled a set of steps, tips and general guidance representing the best practices that we collectively know of and agree to as a community of practitioners.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The process represents an informal agreement amongst the team on how we accomplish data science objectives, and it serves as a basis with which to reflect on how to improve ourselves and our work. When we work on tasks, we use the process to bring higher levels of quality to our work and to improve our ability to communicate with our peers on progress, successes, and failures. The process itself is iterated on and adapted as we see systematic failures appearing in our work, or ways in which we can improve on our work.&lt;/p&gt;
&lt;p&gt;One thing worth highlighting is the iterative nature of our work here at SoundCloud. We work daily with Product Managers, Designers, and Engineers and we do so in a highly collaborative and iterative way. As such, we have designed our process to not only allow for iterative work, but to embrace it as a fundamental principle. We aim to deliver high-value work on a regular basis to our stakeholders and to adapt to a rapidly changing environment and available information. Think of it as a Bayesian process — we’re constantly updating our prior!&lt;/p&gt;
&lt;p&gt;When you see the steps below, you should keep in mind that these are logical steps of the work process and that the actual workflow contains loops and iterations — an outcome of one step might require revisiting previous steps. In most cases, we expect first iterations to be as small in scope as possible and that completing steps is more important than adding complexity to steps so that we are able to deliver incremental value earlier. As such, following the process in practice should end up looking like the following diagram, as opposed to a single pass through each step once and only once.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/bd6dc01f8d572a606882bc87996bfe14/d19c0/ds_process_example.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAB8klEQVQ4y4VSS2saURidZKzRGNPoTNUxTh1LY0x22fTxA4r7bFIodBNCSKBQaPoD+ge6LdKlm/Y3dDaS2PhCEUURfIGCD1y7teeGM2WQmFw43Psdz5zzffcqzWaz761W62+pVPrT6/VuptNpejKZ/Gw0Grf5fN7sdDq34H6A+wXuGjqz3+9fo06Nx+Pf1Wr1plwum91ut4j6m1QsFt/ncrmvpml+rlQqV8ApPjhpt9uXOJ9BeDkajY6hO89ms1dCB4MvhULhI/gPzWbzolarnQ0Gg08wfCfZVkR6fDmA3QcV0WhUNQxD8/v9CV3XI/F4XEkmk+7FYrGeyWTk+Xwup1IpRywWC0SwFEVJQB9GrabTaQeuTK7X6/JwOJTR8brwfAps2/YtwLmUuwbsENs2rN3XpBvwAiHAx7HuM3QBfupE+MYqwwCTfUwNst5gmJNdazTycQ+sMnRRfASEgU0aW1yIGsHpNs656l2eUficLx2iscXp1ER4tniVQS7bNE8k25hxjiHGUWi8zw+95HbJ+Qgvw1/ymsR0kofCN+xii6bi/Jaje8jFgFcM8xJ7wGtybutRooDBccKEnQsxwFjSaTbOuoa7NkVxSJGHyUJ0QLNNdvgCSJDzcJo9ckHe5f//lsZuVe4WF1zSWJzKx9L4m8DOPx8xpkdWOnIzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Iterative example&quot;
        title=&quot;Iterative example&quot;
        src=&quot;/blog/static/bd6dc01f8d572a606882bc87996bfe14/d19c0/ds_process_example.png&quot;
        srcset=&quot;/blog/static/bd6dc01f8d572a606882bc87996bfe14/9ec3c/ds_process_example.png 200w,
/blog/static/bd6dc01f8d572a606882bc87996bfe14/c7805/ds_process_example.png 400w,
/blog/static/bd6dc01f8d572a606882bc87996bfe14/d19c0/ds_process_example.png 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Speaking of iterative work, this is our first iteration on capturing our data science process. We’re happy with and proud of what we have collected here, and we’re looking forward to figuring out the next steps. We want to see where the process does not fit and what changes we should make to it. There might be practices that we should add or some that we should remove. We’d also like to introduce a set of values and principles that we can use to guide our decision-making as Data Scientists.&lt;/p&gt;
&lt;p&gt;We look forward to learning, adapting and iterating as we gain more experience as a group. Below you will find a condensed version of our Data Science process and we’d love to know what you think about it. How do you work on a Data Science task or problem? Have we forgotten any stages or important attributes of each stage? Is this something you’d like to adopt for your work? Let us know what you think, and join us in the conversation!&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Data Science Process&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Authors&lt;/strong&gt;: &lt;a href=&quot;mailto:demir@soundcloud.com&quot;&gt;Özgür Demir&lt;/a&gt;, &lt;a href=&quot;mailto:josh@soundcloud.com&quot;&gt;Josh Devins&lt;/a&gt;, &lt;a href=&quot;mailto:max.jakob@soundcloud.com&quot;&gt;Max Jakob&lt;/a&gt;, &lt;a href=&quot;mailto:janette.lehmann@soundcloud.com&quot;&gt;Janette Lehmann&lt;/a&gt;, &lt;a href=&quot;mailto:christoph@soundcloud.com&quot;&gt;Christoph Sawade&lt;/a&gt;, &lt;a href=&quot;mailto:warren.winter@soundcloud.com&quot;&gt;Warren Winter&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Problem Definition&lt;/h2&gt;
&lt;p&gt;At the outset, we collaboratively make a &lt;strong&gt;definition of the objective&lt;/strong&gt;, ensuring that all stakeholders are on the same page about what problem we’re trying to solve. To accompany this we define the &lt;strong&gt;metrics to measure success&lt;/strong&gt;, to make sure that we agree on when we are successful. This is similar to the definition of done in agile engineering practices.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the business need (in collaboration with stakeholders)&lt;/li&gt;
&lt;li&gt;Challenge and validate assumptions about the business needs&lt;/li&gt;
&lt;li&gt;Identify and prioritize subproblems, including metrics&lt;/li&gt;
&lt;li&gt;Narrow the scope to minimal size, in order to achieve the business goal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;Before diving into a potential solution, we take some time to first prepare ourselves and the data. We build some &lt;strong&gt;intuition and knowledge of the problem domain&lt;/strong&gt;, with the support of domain experts and research. We spend some time exploring our data and finding the &lt;strong&gt;relevant variables or attributes of the datasets&lt;/strong&gt; available to us. We finally &lt;strong&gt;define our solution space&lt;/strong&gt; by narrowing down from all possible solutions into the general area. An example would be understanding if a supervised, binary classification is a suitable approach, or if we need a multi-label or multi-class classifier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Collect all available data, understand if it is sufficient and representative&lt;/li&gt;
&lt;li&gt;Exploratory data analysis, including any manual labeling&lt;/li&gt;
&lt;li&gt;Get peer feedback on the solution space and any conclusions drawn&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Solution Development&lt;/h2&gt;
&lt;p&gt;Solution development is where we really dig into solving the problem at hand. We create a &lt;strong&gt;ranked list of candidate approaches&lt;/strong&gt; for solving the problem, and after obtaining a simple baseline, we implement one or more &lt;strong&gt;prototype(s)&lt;/strong&gt;. As we go, we make sure that we have &lt;strong&gt;documentation of the decision-making process&lt;/strong&gt; (e.g. in a journal of decisions), which serves as a kind of story-line supporting our chosen approaches and solutions, problems or edge-cases that we encountered, quirks with the data or a library, and so forth.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Review prior art, literature, existing implementations, etc.&lt;/li&gt;
&lt;li&gt;Establish a baseline, through simple means&lt;/li&gt;
&lt;li&gt;Consider trade-offs such as between the goodness of fit, ease of use, complexity, maintenance, community, etc.&lt;/li&gt;
&lt;li&gt;Prototype solution(s), evaluating the models (e.g. accuracy, precision/recall) and operational performance (e.g. scalability, runtime performance)&lt;/li&gt;
&lt;li&gt;Challenge and validate assumptions that the solution solves the business problem&lt;/li&gt;
&lt;li&gt;Get peer feedback on solutions, implementation ideas, and evaluation procedures&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Production-Ready Deployment&lt;/h2&gt;
&lt;p&gt;Once a solution has been validated through offline evaluation (where possible), we prepare and deploy a &lt;strong&gt;production-grade implementation&lt;/strong&gt;. Where possible, this will be deployed as an A/B test first, or launched in “dark mode” on a subset of traffic in order to observe the effects and provide further validation of the solution.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand how the solution fits into existing infrastructure&lt;/li&gt;
&lt;li&gt;Ensure quality standards are met for runtime performance (e.g. with SLAs/SLOs), automation, testing, documentation, code reviews, etc.&lt;/li&gt;
&lt;li&gt;Document how the approach has been validated in production&lt;/li&gt;
&lt;li&gt;Monitor quality (semantic and operational)&lt;/li&gt;
&lt;li&gt;Communicate results to stakeholders and broader audience (e.g. with a demo)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Validation and Maintenance&lt;/h2&gt;
&lt;p&gt;Once a solution is in production, we need to keep an eye on things and &lt;strong&gt;evaluate the metrics&lt;/strong&gt; that we started with. We &lt;strong&gt;analyze A/B test results&lt;/strong&gt; and regularly monitor the solution to ensure that over time, it keeps on meeting our expectations and solving our stated business problem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monitor performance&lt;/li&gt;
&lt;li&gt;Correlate A/B test results with offline evaluation&lt;/li&gt;
&lt;li&gt;Regular re-training of models (understand freshness requirements)&lt;/li&gt;
&lt;li&gt;Cycle back to the problem statement (e.g. the problem is solved or partially solved, the problem statement needs reformulation)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Deliver software faster by managing work in progress, not by adding overtime]]></title><description><![CDATA[Product development flow (flow) is the rate at which our products are developed, from idea to deployment. Good flow means that products should pass through the development cycle quickly and continuously.]]></description><link>https://developers.soundcloud.com/blog/deliver-software-faster-by-managing-work-in-progress-not-by-adding-overtime</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/deliver-software-faster-by-managing-work-in-progress-not-by-adding-overtime</guid><pubDate>Wed, 27 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Product development flow (flow) is the rate at which our products are developed, from idea to deployment. Good flow means that products should pass through the development cycle quickly and continuously.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Problem Statement&lt;/h2&gt;
&lt;p&gt;SoundCloud faces challenges with its product development flow. SoundCloud’s CTO, Artem Fishman, summed up these challenges succinctly during the May 2017 Engineering Town Hall.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c6f5381bbfbad6246acf19130fa69aec/67148/artem_engineering_town_hall.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.92948717948718%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACjklEQVQoz22SW0jTURzHt3lJ3fxv+/83r3PL66ZOnbdNzaksL7lMmwMNxQwlL5g9REKllNmNkMIXBQkqCKL3LpAvQS+hqD0UQT4kFSEWkuBr8ulMCrE88OXDeTgfvr9zjko2xqLIOuLNsSQl6ElJMmCzGEmM1+PISkYxarFnJBM46sZfV4ivwkGmVUKlUu1EE6YmMjKciAgNki4SlUEfg0kIE+IkLIlGrMmyiGBKPGMjHeQ7EjDFWXAVlZPtLMLp9pJXKpJXisvlxiaK/JVHRISFqGK/ROlkbl8bIDvVyOitGX4CK5u/WHwxx/v5ZT5twdLKJh7PITIddhSTWTTdR6jWqFELGs0WZqbO4bLHM353mm0h/Li+weKzOT4sLLEu9u++beD1NdF6opN8VxGSdOB/oUYIQ9QpSTycvUhZvpWJqVlCa217mx+CIVmo8dKXdc5fuEKw/TjNwS4URdq/od9lw1tYzOMHV6kqyaBrcJTltU1er3zmzeoa86vfeft1i5cLq+Q4i9HrJRISLcjiLfYI1X/Y583kcH4Bj+5fp//kEWKitZgUM1qdXhyWMRgUJL2CQTbvKWOUtfsLrSYJm9GMr6aSV08nGehtoa83SLC1gTs3B7k3dRavR0zhsYqkU1aSSpG4mhx73D8jh4ULhv5VNDFRMei0Wi6N9NLUVM+ZoVP09LQzPnqa508mCPrzCDTmijhp9Dmorcqi0m3bFaabNVTnaKl2aqnK1WGVw6nwFNDZ0UpLcwPDQlheXkJBnp3hvgBNtdlClEVHsHhHGJIVO827wjq7mu4SFV1lGhE1x3LDmJ4c4vLYAH6/T7RrIzXNSlV5LjfGunEXWvBVHiTgd1JfnUZ9TQ5D/W38BmTcemagRQWMAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;artem engineering town hall&quot;
        title=&quot;artem engineering town hall&quot;
        src=&quot;/blog/static/c6f5381bbfbad6246acf19130fa69aec/8ff1e/artem_engineering_town_hall.png&quot;
        srcset=&quot;/blog/static/c6f5381bbfbad6246acf19130fa69aec/9ec3c/artem_engineering_town_hall.png 200w,
/blog/static/c6f5381bbfbad6246acf19130fa69aec/c7805/artem_engineering_town_hall.png 400w,
/blog/static/c6f5381bbfbad6246acf19130fa69aec/8ff1e/artem_engineering_town_hall.png 800w,
/blog/static/c6f5381bbfbad6246acf19130fa69aec/6ff5e/artem_engineering_town_hall.png 1200w,
/blog/static/c6f5381bbfbad6246acf19130fa69aec/67148/artem_engineering_town_hall.png 1248w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We have a ton of [good] product pressure… We work really hard, but are fundamentally slow to deliver… This resonated everywhere. It takes a very long time to ship something here.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;His conclusion was reached by interviewing over a hundred employees from the Technology Organization. It is clear that employees understand these challenges on a qualitative and intuitive level.&lt;/p&gt;
&lt;p&gt;These are classic symptoms of an overloaded product development queue.&lt;/p&gt;
&lt;p&gt;The problem was then for project managers to develop quantitative metrics for understanding our product development flow and guidelines for improving the flow issues that the company already knew on qualitative and intuitive levels.&lt;/p&gt;
&lt;h2&gt;Post Structure&lt;/h2&gt;
&lt;p&gt;This post describes recent efforts to measure and improve software product development flow at SoundCloud. It demonstrates that we can deliver software faster by managing Work in Progress (WIP) and product quality rather than pushing employees to work overtime.&lt;/p&gt;
&lt;p&gt;The second section describes the discovery phase in which we define and measure some variables for improving flow. Project managers used tools from the &lt;a href=&quot;https://github.com/soundcloud/project-dev-kpis&quot;&gt;Project Development KPIs Project&lt;/a&gt; (PDKP) to understand the problems in our workflow. Understanding that data, they derived the strategy below for improving SoundCloud’s product development flow.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The company should commit to fewer products at a time&lt;/li&gt;
&lt;li&gt;Teams should work on fewer product features at a time&lt;/li&gt;
&lt;li&gt;Engineers should work on fewer tasks at a time&lt;/li&gt;
&lt;li&gt;Engineering should reduce its code inventory&lt;/li&gt;
&lt;li&gt;Engineering should monitor and reduce the number of bugs over time&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The third section describes our continuous learning process and our first set of guidelines for improving flow.&lt;/p&gt;
&lt;p&gt;The fourth and last section describes preliminary results of our efforts.&lt;/p&gt;
&lt;h2&gt;Defining success and variables&lt;/h2&gt;
&lt;p&gt;This section identifies lead and cycle time as metrics of success in product development and motivates their use. It identifies variables that have an impact on lead and cycle time.&lt;/p&gt;
&lt;h3&gt;Lead and cycle time&lt;/h3&gt;
&lt;p&gt;To improve business outcomes, businesses &lt;a href=&quot;https://en.wikipedia.org/wiki/SMART_criteria&quot;&gt;define the metrics for the desired outcome&lt;/a&gt; then &lt;a href=&quot;https://en.wikipedia.org/wiki/PDCA&quot;&gt;iterate towards achieving that outcome&lt;/a&gt; by observing if actions taken improve or worsen the metrics. Members of the Process Optimization Group at SoundCloud use lead and cycle time as two important measures of our success in product development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lead time&lt;/strong&gt; is the time between the initiation and completion of a product’s development. At SoundCloud, this is the time between when a product owner receives a work request to the time when the work is completed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cycle time&lt;/strong&gt; is the time between when the production of a product is started and when it is completed. At SoundCloud this is the time between when an engineering team receives a feature request to the time that the software is deployed to users.&lt;/p&gt;
&lt;p&gt;Keeping lead and cycle times low has many benefits, four of which are noted here. The first benefit is that we ship products to users faster, before our competitors. Being first increases the chance that we capture market share.&lt;/p&gt;
&lt;p&gt;The second benefit is the reduction of backlog size. Product managers are incentivized not to commit to projects too far in advance. Since requirements for new products change rapidly, a minimal backlog means more responsive plans and product ideas are used instead of outdated ones.&lt;/p&gt;
&lt;p&gt;The third benefit concerns the size of products shipped. Focusing on minimizing lead time incentivizes teams to deliver new products as MVPs and smaller changes to existing products. This more granular approach decreases the probability of large failures and over-investment in projects that will not succeed.&lt;/p&gt;
&lt;p&gt;The fourth and most important benefit is that SoundCloud can gain information on its product development faster. By shipping faster, we get market information before our competitors. We get feedback on a product earlier in its development cycle. We can make small adjustments in strategies towards our desired outcome rather than large corrections after over-investing in a product that had limited success.&lt;/p&gt;
&lt;p&gt;How can SoundCloud improve lead and cycle time? There are several important variables we can leverage.&lt;/p&gt;
&lt;h3&gt;Variables affecting lead and cycle time&lt;/h3&gt;
&lt;p&gt;This section presents the definitions of variables affecting lead and cycle time as well as an explanation for the effect of each.&lt;/p&gt;
&lt;h4&gt;The ratio of WIP to contributors&lt;/h4&gt;
&lt;p&gt;The ratio of Work in Progress (WIP) to contributors is a measure of how much contributors in a team are multitasking on average. PDKP estimates this by dividing the number of WIP issues in a &lt;a href=&quot;https://www.atlassian.com/software/jira&quot;&gt;Jira&lt;/a&gt; project by the number of engineers active on that project.&lt;/p&gt;
&lt;p&gt;Keeping the ratio of WIP to contributors slightly below 1.0 can decrease cycle time in two ways. First, if engineers develop one product at a time, products will be ready sooner than if they multitask. Say an engineer has two tasks, each of which will take two days. They can either work on both in parallel or one after the other. The schedules of these two options are shown below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/12305/one_engineer_two_tasks_schedule.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 16.237942122186492%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAA1UlEQVQI1xWLS0uEYAAA/f+39lGWleu6hXQr2Ah6E3UwK8hVET6waNcHFurXty6eJjvMaWa0qqrouo62bZFSEoQR5fcPat1SNw2h71PmORulkH3rLwLyokS1G6q6Ifj3RUHbe9X/WhRFxHFMnmXI3zXG/A3zRjB7yji+T3gebyP2DXJrQjK1MS8C7Icvjh5XOHeCWNdJzQNSyySdmGie5+G6LkIIaqnYuxTsXH+ye7tEv/pgevqCM3/HOfc5OfN4NWzCwxmLHt+wSAZbpOMhy9GA1WjIHwGAzGWTJKq1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;one engineer two tasks schedule&quot;
        title=&quot;one engineer two tasks schedule&quot;
        src=&quot;/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/8ff1e/one_engineer_two_tasks_schedule.png&quot;
        srcset=&quot;/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/9ec3c/one_engineer_two_tasks_schedule.png 200w,
/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/c7805/one_engineer_two_tasks_schedule.png 400w,
/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/8ff1e/one_engineer_two_tasks_schedule.png 800w,
/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/6ff5e/one_engineer_two_tasks_schedule.png 1200w,
/blog/static/e4dbf7cc6a9142f179b26e4f6f1a06ee/12305/one_engineer_two_tasks_schedule.png 1244w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Scheduling two tasks for one engineer&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;In Schedule 1, the engineer works on both in parallel, task 1 is complete at the end of day 3 and task 4 is complete at the end of day 4. The more the engineer interleaves the work, the longer it takes to complete both tasks. In the extreme case—multitasking within each day—both projects might not be completed until the end of day 4. In Schedule 2, the engineer works on the first and then the second, the first is completed at the end of day two and the second at the end of day four. By working on one thing at a time, the engineers have completed a task earlier without adding work!&lt;/p&gt;
&lt;p&gt;The second reason to keep the ratio of WIP to contributors ratio slightly below 1.0 concerns the ability of individual members to change development roles quickly. To understand why, see the idealized model of a team’s workflow below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/7588d21a9431e8685dab8c4030a439f2/c6f47/u_shaped_flow_cell.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.42105263157895%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAB+ElEQVQ4y22TW5OqQAyE+f//yicf3GN5Q0VLvIsKeAEVFTXr16fG3YedqoEwk3Q6neBlWWaLxcImk4l1Oh0bDoc2n89tMBhYGIa2XC5tu93aZrORzZt1v9/tcrmYW6/XS9vj4ng82vl8tjzPZa/Xa4GNRiOdYddqNatWqxYEgRK0221rtVq23+8NUg7Usz8WjH3fF0sWoM1mU2fc5fnJiqKw0+lkX1//BPoB5FGWpUq4Xq92u91sPB4r+3Q6FVve9Xrd0jRV4OPx+CSnIgCfz6e2lySJslIWDLrd7lurle12u7dGhe4oLwgGCibI6ccdUvT7fWnP8lwGdOn1etISljBnwZqSaRSVuAUg/pVKRdpyD5aakqY7i6JIoL+B/rPrWKPReDPvCTCOE5UOIL5IQuxsNlMT1RQcGBtGgiDAuKQMykEK2CADfiRBV7pLLJofDgdp67kuhuFYTmhKEAA4wXq1WoklLCgLAHQFDMbML1OAVCqZEYBNHMcaBWxAYIqmgFI+FQBG45hHABkt3+/KBywPLWgKOlASNoCcAwhrNkB8kxAC2PiQkA1z4j3XALIDRjCa8Z1luQBwRh+AOIeZm1vOGaWyfPz8KdQOOm8ckySVlth/Lfd74l8UVwEB+gHEIDMO7psyYIeuUbSWRtiwc38KjPEDzMV9AxnHIEnPMdJQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;u shaped flow cell&quot;
        title=&quot;u shaped flow cell&quot;
        src=&quot;/blog/static/7588d21a9431e8685dab8c4030a439f2/8ff1e/u_shaped_flow_cell.png&quot;
        srcset=&quot;/blog/static/7588d21a9431e8685dab8c4030a439f2/9ec3c/u_shaped_flow_cell.png 200w,
/blog/static/7588d21a9431e8685dab8c4030a439f2/c7805/u_shaped_flow_cell.png 400w,
/blog/static/7588d21a9431e8685dab8c4030a439f2/8ff1e/u_shaped_flow_cell.png 800w,
/blog/static/7588d21a9431e8685dab8c4030a439f2/6ff5e/u_shaped_flow_cell.png 1200w,
/blog/static/7588d21a9431e8685dab8c4030a439f2/c6f47/u_shaped_flow_cell.png 1273w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt; Software engineering is an invisible “U-shaped flow cell.”&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;Engineers need to quickly transition between planning, developing, reviewing, and fixing bugs. They fulfill multiple roles in the software development process. This collaboration pattern is similar to &lt;a href=&quot;http://www.tpslean.com/glossary/cellmfgdef.htm&quot;&gt;U-shaped flow cells&lt;/a&gt; in Lean manufacturing.&lt;/p&gt;
&lt;p&gt;Processing tasks in U-shaped cells can be very efficient, but not if the queue of tasks is overloaded. If more tasks are in progress than there are engineers to complete it, items will form queues waiting for attention. For example, say an engineer developing code is overloaded with new feature requests. A queue of feature requests will form. Further, it is likely that they will not be able to review code completed by their teammates, contributing to the formation of a second queue in the review stage. Either cycle time will rise or quality will suffer. Engineers need to switch roles to where queues are forming to push tasks through the queue.&lt;/p&gt;
&lt;p&gt;Analogously, overloaded teams with a high WIP to contributors ratio will be less capable of dealing with incoming bug fixes and ad-hoc requests.&lt;/p&gt;
&lt;h4&gt;The number of products in WIP&lt;/h4&gt;
&lt;p&gt;“Products in WIP” are products in progress. PDKP estimates the number of products in WIP by counting the number of issues in a Jira project that represent a product. For most teams, these are ‘Epic’ or ‘Story’ issues. Products can be either for internal or external users.&lt;/p&gt;
&lt;p&gt;Decreasing the number of products in WIP can decrease lead and cycle time. To illustrate, imagine we have two tasks each of which takes four days and can be shared between two engineers with no overhead. There are two schedules for this work shown below: either each engineer takes one task or they pair on the first, then the second.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b766f1ac96e814c9391fd28fa8872d63/1da0e/two_engineers_two_tasks.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.837060702875398%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABRUlEQVQY0z2Q7UvCcBRG9/cXllqpqZWKSYGU1MdK0TICCXVuWr6kc3NzWa5mycQPp18L+nDguTwX7uFKjuOwXq9ZLBbM53M8z/OxLIun3oAPd4Hz6dJS2lj2DPfrm15/wMtI43vpYUzE3nOPd+fT7yRFUWi326KYYAq63R7O/A11OGX/skHuTid7O+bgqkv2zvDz4U2fTEUjV51wVOyTKg05Fnu5qoHkui6r1cq31PUxcqOBIez0ZotyIEwzdogcO6ARSdJJpGlGk9RFVhMZWvEUj3sJlHhakPJnSVVUOp0OmjZGG41QhbFtT3nqjsnl7zm7qJE/f+A0X6Ug8kmhRl0cmQU3MXe2GQY2MENbWOE/pN8fLpdLPGFp6Dqy3GJqGsjDGeGiSbRis1OaELrWiFWmbJVtqqk8r7tB9GiEQSiIEdn95wdcZVOFZN2nXQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;two engineers two tasks&quot;
        title=&quot;two engineers two tasks&quot;
        src=&quot;/blog/static/b766f1ac96e814c9391fd28fa8872d63/8ff1e/two_engineers_two_tasks.png&quot;
        srcset=&quot;/blog/static/b766f1ac96e814c9391fd28fa8872d63/9ec3c/two_engineers_two_tasks.png 200w,
/blog/static/b766f1ac96e814c9391fd28fa8872d63/c7805/two_engineers_two_tasks.png 400w,
/blog/static/b766f1ac96e814c9391fd28fa8872d63/8ff1e/two_engineers_two_tasks.png 800w,
/blog/static/b766f1ac96e814c9391fd28fa8872d63/6ff5e/two_engineers_two_tasks.png 1200w,
/blog/static/b766f1ac96e814c9391fd28fa8872d63/1da0e/two_engineers_two_tasks.png 1252w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt; Scheduling two tasks for two engineers&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;In Schedule 1, each engineer takes a single task, both products ship in four days. In Schedule 2, the first task is ready in two days and the second at four days. We’ve shipped the first product two days earlier without adding any work. Pairing adds some overhead, but getting information from shipping a product sooner is often worth it. These same principles apply to the planning phases of product development.&lt;/p&gt;
&lt;h4&gt;Inventory&lt;/h4&gt;
&lt;p&gt;In business, inventory is the components of a product that have not been assembled or manifested into a product ready for sale. In engineering, our inventory is anything we work on that results in a feature that is yet undelivered including undeployed code, RFCs, and unfinished Jira issues. PDKP estimates inventory with the number of commits or number of additions and deletions in pull requests in repositories that have been updated in the last three months.&lt;/p&gt;
&lt;p&gt;Increasing amounts of code inventory is a symptom of bad flow and has a negative effect on cycle time. The more inventory there is, the more development time has accumulated in work not being seen by our users. Generally, inventory depreciates in value as it ages.&lt;/p&gt;
&lt;p&gt;Ideally, we want to achieve what is called a “one piece flow,” a process where all code inventory is actively being worked on. This means that there should be at most one product in progress per team and one task in progress per engineer.&lt;/p&gt;
&lt;p&gt;The challenge in accomplishing one piece flow in software development is that code inventory is invisible. In a factory that produces physical goods, inventory is obvious, it stacks up on the factory floor. In a software company, it’s not so obvious.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e73797046b8aa406329cb906069e466b/d9f6f/code_inventory_visualized.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.97326203208556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAEMElEQVQ4yx2TW0zTBxSH+2jM4jInmHgBlGtpgdLKTVplFoHBbKFQoNxrLVBKgd64WEpZsUIRB0xoYeqKgCyiQ2XMbDFsLht7cNniLtmyzZk4NSZ725Zsyfbw7Z89nNfvfL9zES2+0cf1t/wsj9qZ6qhiLWDjo9lBNsM+3p8d4s7lAFs3Z/jl/rv8/uQL/vnrKfz9hB+3Nhg9VY4mLxWzoYwpXxeTvh5ES+ed3I6MMTfUgUOjwlOtZqC6kMGGMjzNWjwnKxh1mIhMDLK+FGJ9cZY3PTbqi/KoKlIyPdLLavgskfEBJvosiK56NayFB7h+4TRecznzXhPXx9pYHrOwNtfH5++d44ev13n+/Dk/f/sVLqFJlToDZ3sNM2edjDhNOIxVQumJBN2IFlwFrIybWJ50M9JZzbirnq4aFZ2GQsJnu7m/uczjh1+yuhDCWq+hx2xg88YYy9M2/H2n6Okw0ufsYTzg49NlL6K5bhVLAvCdM20s2XTMmdQ0l2VhqX2FlbCHD9fCBH12DNqjtBt1uK1NglUjfkcdlRXlVOoqOdVqxWhx4O7tRTTTmc9lfz0fXBzmwZVx7trL2Ir0shHx8/HGRTzOFjw2PcOOJvrb9ATcZk5b9DjNFVyYmmRowElnu5nzoStcXr0jADuUhE/r2boV4o/H93l2wcJKsBWdEDs0asXdXsloIIDT4WDq3CiLkWvYjTV0txl49Ow3QqFZ+u02Prt5la3b1xCFLCpm+vV8cjXIvw9u8OdUMwNNxRyU7hGAvQx2NtLS0kx5qZrikmOYs3NwlSjRVRSTpsgjMUWKPCOdSbeJOwuziHwNeYSHGrgbNPO0KI7vahW8bnoVb+Nx4bacWOvKyc/LQJkjIzE+jsTYfejUhzDW60lOz2X/gSTkOQUUlWgp1egR2arymPPWsRG0cC9jH/eKJTgrsxk2HKHOVMvxMjVqZSYKWTKxsXuErVrwdRjQvlZCqiwfQ1EWCyM25vvqCPm7EHVW5jIvGC4OtfK2Oo5LWgk2YT5t9lZKGqtR1tZQUKFBmS0hVRKPy9RMl76Uo6rDiDMVGI4l0F+ejFefyry9FFGP7pBg2EDEb8X7qoyRxiKm54K4Js5QLAClxws51tnP0fxMDmdLkWUkkymTID0kxxqfwKWYnWiOxHAiZy91qhhEXVoF0+4abglf8es36zz76XtmQudo6rVScKKQlxMTkVefRKVUkJslJUchJVcuJS1HzsSuKFZf2Mb5uJ0EDr5EvZBQ1F6aznh3BQu+Fh5urvBoZgpblZa9gsWBNDFRSfHESNPISE8R7MQoMsXI0lIQpyZQHhNNdWwUi9u2M7F7B1UFsYhc2nQGjEUMt+sYaq3BbzFxJCuDF3dsZ1f0TqL3RP+/jDRpMnKZmCx5CumSg4gT9nMgaR9JyXsxxURRsD8KSeJu/gOR35qwQmhx1AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;code inventory visualized&quot;
        title=&quot;code inventory visualized&quot;
        src=&quot;/blog/static/e73797046b8aa406329cb906069e466b/8ff1e/code_inventory_visualized.png&quot;
        srcset=&quot;/blog/static/e73797046b8aa406329cb906069e466b/9ec3c/code_inventory_visualized.png 200w,
/blog/static/e73797046b8aa406329cb906069e466b/c7805/code_inventory_visualized.png 400w,
/blog/static/e73797046b8aa406329cb906069e466b/8ff1e/code_inventory_visualized.png 800w,
/blog/static/e73797046b8aa406329cb906069e466b/d9f6f/code_inventory_visualized.png 935w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt; Software inventory isn’t visible in the same way manufacturing inventory is.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;For this reason, we need to take measures to monitor code inventory and make sure it gets shipped in a timely manner.&lt;/p&gt;
&lt;h4&gt;The ratio of features to engineers&lt;/h4&gt;
&lt;p&gt;The ratio of features to engineers measures how many features each team member must maintain on average. We estimate it by dividing the number of configuration files indicating an individual feature in a team’s active repositories by the number of engineers active on a team’s Jira board.&lt;/p&gt;
&lt;p&gt;Higher ratios correlate with higher rates of bug reports and maintenance overhead. If a team has an abnormally high ratio, the company should consider providing them with more headcount or redistributing their responsibilities to get a more even distribution.&lt;/p&gt;
&lt;h4&gt;Bugs&lt;/h4&gt;
&lt;p&gt;Our exposure to bugs is estimated by the number of issues of issue type ‘Bug’ in a Jira project.&lt;/p&gt;
&lt;p&gt;Persistent bugs have a negative impact on lead and cycle time. Users and Community Support re-report the same bugs which then have to be reinvestigated before confirming they are duplicates. If engineers build on top of buggy code, the resultant implementation can be flawed, at worst resulting in wasted code that cannot stay in production. Most importantly, bugs make it harder for engineers to learn what is going wrong and to prevent future problems.&lt;/p&gt;
&lt;h4&gt;Lower bound on time to backlog completion&lt;/h4&gt;
&lt;p&gt;Multiplying a team’s cycle time by the number of products remaining in their backlog estimates a lower bound on how long it would take a team to complete their backlog.&lt;/p&gt;
&lt;p&gt;Having a large backlog has three negative effects. First, it increases our lead time. Planning too far in advance creates a queue of work that teams cannot work through quickly and takes development time away from line managers and engineers. Second, it decreases our flexibility in changing plans in the future. Once something is on the backlog, teams feel committed to deliver them. Third, it decreases motivation.&lt;/p&gt;
&lt;h2&gt;What did we do about it?&lt;/h2&gt;
&lt;p&gt;This section describes SoundCloud’s continuous learning and improvement cycle where its project managers collect flow metrics and apply analysis thereof in guidelines to improve product development flow.&lt;/p&gt;
&lt;p&gt;For the first set of guidelines, project managers used principles from manufacturing and queueing theory to encourage teams to work on fewer products at a time and engineers work on fewer tasks at a time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Importantly, the guidelines don’t suggest that teams work harder. They are intended to increase focus, not the total number of hours worked.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Metrics collection&lt;/h3&gt;
&lt;p&gt;Starting in March 2017, PDKP has collected lead time, cycle time, WIP, bugs, contributor, and code inventory metrics from SoundCloud’s Jira and GitHub organizations.&lt;/p&gt;
&lt;p&gt;To date, 18 engineering teams have configured the system to collect lead and cycle time metrics. Inventory metrics are collected for all development in SoundCloud’s GitHub organization.&lt;/p&gt;
&lt;p&gt;All metrics are aggregated and presented in the PDKP &lt;a href=&quot;posts/pdkp_overview_dashboard.png&quot;&gt;dashboard&lt;/a&gt; and &lt;a href=&quot;posts/pdkp_summary_dashboard.png&quot;&gt;summary dashboard&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Guidelines and motivating analysis&lt;/h3&gt;
&lt;h4&gt;1. The company should commit to fewer products at a time&lt;/h4&gt;
&lt;p&gt;SoundCloud can reduce cycle time and increase its flexibility in planning new products by reducing the number of products it commits to. Teams had high lower bounds on time to backlog completion, suggesting that overcommitment was a problem.&lt;/p&gt;
&lt;p&gt;Of the 18 teams in the PDKP metrics collection, the median lower bound on time to backlog completion is 766 days, or about 2.1 years. While this number could represent misuse of Jira, it suggested an opportunity to shift more time from planning future products to focusing on more immediate products.&lt;/p&gt;
&lt;h4&gt;2. Teams should work on fewer product features at a time&lt;/h4&gt;
&lt;p&gt;Provided the work can be parallelized, teams should work on one product at a time in order to maximize flow. The number of products teams had in progress was too high.&lt;/p&gt;
&lt;p&gt;Across the 18 teams sampled, PDKP reported the median number of products in progress at once is four. An in-person survey of teams in the Creators Organization reported a similar number.&lt;/p&gt;
&lt;p&gt;It is unlikely that there is enough downtime in the development of products that teams needed to have four in progress at any given time to be productive.&lt;/p&gt;
&lt;h4&gt;3. Engineers should work on fewer tasks at a time&lt;/h4&gt;
&lt;p&gt;In theory, engineers should work on one task at a time until it is completed to maximize flow. The ratio of tasks in WIP to engineers was too high.&lt;/p&gt;
&lt;p&gt;Across the 18 teams sampled, PDKP reported the average ratio of WIP to engineers was consistently over 2.0.&lt;/p&gt;
&lt;p&gt;Of course, it is not always practical to work on one thing at a time while developing software. For example, teams with long running batch jobs will have enough downtime while waiting for the job to finish to do meaningful work on other tasks. However, like teams with too many products, it is unlikely that the majority of teams with high ratios in the sample have this specific problem.&lt;/p&gt;
&lt;p&gt;Importantly, high engineer utilization is not a goal. One important result of queuing theory is that &lt;a href=&quot;https://hbr.org/2012/05/six-myths-of-product-development&quot;&gt;not all nodes in the queue have to be fully utilized&lt;/a&gt; to maximize the throughput of the queue overall. In fact, it’s good if our engineers have a little idle time.&lt;/p&gt;
&lt;h4&gt;4. Engineering should reduce its code inventory&lt;/h4&gt;
&lt;p&gt;SoundCloud has a high amount of code inventory. Reducing it is a big opportunity for us to improve these metrics.&lt;/p&gt;
&lt;p&gt;Over SoundCloud’s GitHub repositories updated in the last three months at the time of the first metrics collection, there have been between 1,200 to 1,500 commits in open pull requests representing 200,000 to 300,000 lines of code. The average age of this inventory was 65 days old. On average, teams have 22 commits in open pull requests. That’s a lot.&lt;/p&gt;
&lt;p&gt;Teams at SoundCloud have had success in increasing flow by decreasing inventory. The Content ID Team reduced its inventory over a period of two months, bringing the daily average number of commits in open PRs from 46 to less than 5 and the average age of those commits from 14 to less than 1 day. During this time, the team’s cycle time dropped from 12 days to 3 days.&lt;/p&gt;
&lt;p&gt;Given this &lt;a href=&quot;https://en.wikipedia.org/wiki/Longitudinal_study&quot;&gt;longitudinal experimental design&lt;/a&gt;, it’s only possible to show correlation—not causation—between reducing inventory and cycle time. However, the correlation is positive and suggests further effort on this front could help.&lt;/p&gt;
&lt;p&gt;Importantly, code review should not be rushed. Rather, it is better to carefully and thoroughly review code as it reaches the review stage promptly or use a continuous integration review process where merging code into the master branch is not blocked by code review.&lt;/p&gt;
&lt;h4&gt;5. Engineering should monitor and reduce the number of bugs over time&lt;/h4&gt;
&lt;p&gt;Monitoring and reducing the number of bugs in a team’s system has a positive impact on lead and cycle time. SoundCloud is currently implementing a standardized bug reporting process. This is another opportunity to speed up both development and the learning feedback cycle.&lt;/p&gt;
&lt;h3&gt;Guidelines for management&lt;/h3&gt;
&lt;p&gt;The number of products product management commits to at once caused teams to overload their product development queues. Engineering managers then overloaded engineers by accepting multiple products into WIP at once.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0ca86d617846e9b6982889164343f3b5/0b460/multitasking_propagation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.920502092050206%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAnUlEQVQY03VP2wrDIAz1/3+ya9FZi8No6VXqmQkE9rLAefDcEk1rDYx/8zwPiDKstYJpmrBtm2ia0w6G0aASx3EIUkq4rgv3faOUIp51XeGswxIWWfRbqmN4OwfY/IkRb+fgOobhhdnPUl5rlauISDS+MnYvZzifcxZ933eYEALGcRRCLyJK8r3UC87z7FyVsBbxAvayxpz3Xhbz+wuwazS+6vqPHgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;multitasking propagation&quot;
        title=&quot;multitasking propagation&quot;
        src=&quot;/blog/static/0ca86d617846e9b6982889164343f3b5/8ff1e/multitasking_propagation.png&quot;
        srcset=&quot;/blog/static/0ca86d617846e9b6982889164343f3b5/9ec3c/multitasking_propagation.png 200w,
/blog/static/0ca86d617846e9b6982889164343f3b5/c7805/multitasking_propagation.png 400w,
/blog/static/0ca86d617846e9b6982889164343f3b5/8ff1e/multitasking_propagation.png 800w,
/blog/static/0ca86d617846e9b6982889164343f3b5/6ff5e/multitasking_propagation.png 1200w,
/blog/static/0ca86d617846e9b6982889164343f3b5/0b460/multitasking_propagation.png 1434w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Overloaded product queues propagate from product managers to project managers to engineers.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;Note that the propagation of queue overload can cause both the single engineer and multiple engineers scheduling problems.&lt;/p&gt;
&lt;p&gt;Product and engineering management must lead and support changes to the way we work in order to improve flow, especially since some of these practices have become habitual in the company. Accordingly, the following guidelines were given to product managers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. The company should commit to fewer products at a time&lt;/strong&gt; - Ideally, teams and product leadership should agree on a limit for the number of products that can be in progress for each team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Teams should work on fewer product features at a time&lt;/strong&gt; - Product managers should prioritize one product at time, one feature at a time and encourage the teams to deliver likewise if possible.&lt;/p&gt;
&lt;p&gt;The following guidelines were given to engineering managers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Teams should work on fewer product features at a time&lt;/strong&gt; - Engineering managers should be conscious of pulling disparate product features onto the board at the same time. They should encourage the team to focus on a limited number of features at once, ideally one. They should encourage dividing work for a product feature into subtasks that can be shared in the team to enable that focus.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Engineers should work on fewer tasks at a time&lt;/strong&gt; - Engineering managers should educate their teams about flow management and encourage them to focus on a task until it’s done, if possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Engineering should reduce its code inventory&lt;/strong&gt; - Engineering managers should monitor their team’s code inventory and encourage them to keep it low.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Engineering should monitor and reduce the number of bugs over time&lt;/strong&gt; - Engineering managers should monitor their team’s backlog of bugs and schedule them to be fixed.&lt;/p&gt;
&lt;h3&gt;Guidelines for teams&lt;/h3&gt;
&lt;p&gt;Teams were given the following guidelines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Engineers should work on fewer tasks at a time&lt;/strong&gt; - Engineers should learn the basics of flow management and try to focus on one feature at a time if possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Engineering should reduce its code inventory&lt;/strong&gt; - Engineers should review code promptly once it hits the review stage and alert others when work is blocked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Engineering should monitor and reduce the number of bugs over time&lt;/strong&gt; - Engineers should fix bugs!&lt;/p&gt;
&lt;h3&gt;A tool for improving flow&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Visual_control&quot;&gt;Simple, visual controls&lt;/a&gt; are provided as part of PDKP to help project managers improve their product development flow. These are presented on the PDKP Summary Dashboard, shown below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8906688cbf168a58b495068d43e3a767/1c938/pdkp_summary_dashboard.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.47895229186156%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACtklEQVQozyWP60uTYRiH3z+go2fb5qbvDu+7Q3Mepk5ztrRlmy5HQUVB6czcrKzIPBWVlBadKAiCvvilVhDkXFpJZYcvJlTfglJsWWgE/QtXT/PDxf27Lx547p+kKSpDLW4gGNxLoz+Mz7sd3QaTwIhWa6XG48dXF8AX2ImjtpkMUwVZOjNGl5fiSh9e/w6q/TtRS6rRmJxIBp+KLqhQEFCwhEqwhd0Yg07kRuEbLeibrBga7DTWONhdp9LiMRL2yIRqrDR7LOz3u9jX4CRYbqCl2oRUPGqk4WkVdWNl1D4pwTfmpn68grpEGd4nLmqeOnHdNfAqtpafPWuYP7WOhVNr+X7OKJCZP7kqzfeeFS8Z7tiofFhPbTxAVXwrnrgfzwM/7vv1VN8P4o43Y7rm4FEkm5muHN7EcngZ1TDV6+H5wGaedcniM63weWmkoXYdt45lc70ri5uCG8d1Aj03Yv9dLre78xlq1XJvTx5THbkk27IZb8slEdGQaNeSaMtfIaJNO+nL0Qz+iFOXBvQsX3Tx+1IJyxdUlkfcLA0W8LdnNR+j65kR7371r2FhxEnqsovUsJ3UiIPUkEzqSimp/vWkekXlR5FCJqN6El0WEt3FjJ1wMy7mxDGVZMzIpKg0esBAfG8BU20yE+02Xh4y8qLTRjJaSjJi40WHlWetMpMHC5GSrZm87cxkujOH6Y51vI5pmD6iXcmHM3kfy+DxviwmLCY+KVZmVJVZk4kPLhczpaV8sCjMms3MKmo6S7Pdecz15fNtuIK5K5uYF7XnRir4dtXL1+FKFvpyeBfdwGeHwp/ycn6GQiw2NZFqCbO4axe/wmF+hHawWFnFksOO1L2tiN6gTG/AQF+gkP6AXkyD2Is4LehvkoluKeKEWeG8xc5Zi5VBxcGA4IzZJnZbOg8qGwV2/gEUKqUhO6otbgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;pdkp summary dashboard&quot;
        title=&quot;pdkp summary dashboard&quot;
        src=&quot;/blog/static/8906688cbf168a58b495068d43e3a767/8ff1e/pdkp_summary_dashboard.png&quot;
        srcset=&quot;/blog/static/8906688cbf168a58b495068d43e3a767/9ec3c/pdkp_summary_dashboard.png 200w,
/blog/static/8906688cbf168a58b495068d43e3a767/c7805/pdkp_summary_dashboard.png 400w,
/blog/static/8906688cbf168a58b495068d43e3a767/8ff1e/pdkp_summary_dashboard.png 800w,
/blog/static/8906688cbf168a58b495068d43e3a767/1c938/pdkp_summary_dashboard.png 1069w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;The PDKP Summary Dashboard.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;The metrics below are displayed on the board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lead time&lt;/li&gt;
&lt;li&gt;Cycle time&lt;/li&gt;
&lt;li&gt;Ratio of features to engineers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A second set of metrics, shown below, is paired with colors to suggest specific actions. Green means the team’s performance against the metric is good, yellow means performance against the metric is likely OK, red means the metric should be brought down to promote flow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The estimated lower bound of days until backlog completion&lt;/li&gt;
&lt;li&gt;The number of unresolved bugs in the team’s backlog&lt;/li&gt;
&lt;li&gt;The number of products in WIP&lt;/li&gt;
&lt;li&gt;The ratio of WIP to engineers&lt;/li&gt;
&lt;li&gt;The number of commits in open PRs&lt;/li&gt;
&lt;li&gt;The average age of code in open PRs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The motivations for optimizing these metrics was discussed in a previous section.&lt;/p&gt;
&lt;p&gt;The actions suggested by the action colors on the Summary Dashboard will not always be appropriate in your day-to-day work. Sometimes they will be wrong. However, simply asking questions about your workflow when a metric lights up red will likely improve your product development flow.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Importantly, actions never suggest that a team isn’t working hard enough. They are intended to increase focus, not the total number of hours worked.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Preliminary results&lt;/h2&gt;
&lt;p&gt;Since the addition of the last teams to the PDKP project on June 23rd, 2017, SoundCloud has seen a reported 39% decrease in the average lead time from a mean of 106.4 days to 64.7 days and a 52% decrease in average cycle time from a mean of 77.8 days to 37.2 days.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a043f1ce1cca7c3accfd1acce356c867/4b160/lead_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.46370023419204%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAAzklEQVQY02WQWW7DMAxEfY7GIrVbku3aiYs0QdL7H2tKeUlR5ONhpCExotg4Y1BhpaCJXrCi3VM4TxMu8/zW879fasxovHNIIYBaBSIGSVDFSdFrRtsSTh+nlaNWYXqnBjfjMGLJHtfEOHcaF2HpGPei8ejlHjW+0sYcGJMwOEbQhGhIHqX1XNXWCa18d+njGvhdzEY2uAq33uJeKZveyp/3GDaeu/6MDoPXEihjfpaMMSf0KaCkDiV3iN4hOAu/7zjKag6tHL639uUbrfELjRmOFfu2OPwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lead time&quot;
        title=&quot;lead time&quot;
        src=&quot;/blog/static/a043f1ce1cca7c3accfd1acce356c867/8ff1e/lead_time.png&quot;
        srcset=&quot;/blog/static/a043f1ce1cca7c3accfd1acce356c867/9ec3c/lead_time.png 200w,
/blog/static/a043f1ce1cca7c3accfd1acce356c867/c7805/lead_time.png 400w,
/blog/static/a043f1ce1cca7c3accfd1acce356c867/8ff1e/lead_time.png 800w,
/blog/static/a043f1ce1cca7c3accfd1acce356c867/4b160/lead_time.png 854w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Aggregate lead time metrics since June 23rd, 2017.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/12ad0764bf4bbe223d41b436ddfba418/4b160/cycle_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.229508196721312%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA10lEQVQY022Qa27DIBCEfZDAPsCYh9s4aZs4Se9/relCq6qV8uPTzgxol2UKIhAWuIMDOQ/vHJzhf/HYjtug+36H/TMcmAiTMuNUIqp6BCYkIZRAOC2MaxFktYwdgj+McyU/hjyDrPG01oa3EvDZGPcmuFcePMzvpTdl3KqYl6EvmXBOhJdI2H7qGr9rf8yktvK5pnHpPcs/Pooaf7TVS1XcmuJqdbdBe+1aRlYDW0Nb4VgLXmvGWhJazmhlwTJHpBAwqwyWGBFteIphEC3rflYdumf9+74Ah96PEofeUTAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;cycle time&quot;
        title=&quot;cycle time&quot;
        src=&quot;/blog/static/12ad0764bf4bbe223d41b436ddfba418/8ff1e/cycle_time.png&quot;
        srcset=&quot;/blog/static/12ad0764bf4bbe223d41b436ddfba418/9ec3c/cycle_time.png 200w,
/blog/static/12ad0764bf4bbe223d41b436ddfba418/c7805/cycle_time.png 400w,
/blog/static/12ad0764bf4bbe223d41b436ddfba418/8ff1e/cycle_time.png 800w,
/blog/static/12ad0764bf4bbe223d41b436ddfba418/4b160/cycle_time.png 854w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Aggregate cycle time metrics since June 23rd, 2017.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;This decrease is correlated with product and engineering leadership successfully decreasing the average number of products SoundCloud’s teams have in WIP from an average of 7.4 products per team to 3.1 products per team.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/35c55e88dd9e42b3a19ade9557142471/4b160/products_in_wip.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.156908665105384%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAAA90lEQVQY02WRWXKEMAxEOUfAWLJZzJCwmllSSVUq979TR7In5CMfr1pCcreBorEGDdVwNlObjDUGjhhHjLgK6zSD6vof9qwNmAjF1xvh85VxGwjXQEk7zosaVFclypdKKMEp0JyhasSCqXLPVgxjbxF7wiFmWt8vjO+J8RC9Xwjvo9wyMOIzTNn6rDpXdLZ0Vt5IDG/Bppsph9S7mH6MlNAAPfjQw4Pu2aS5fwaE3Ku2TgyXjjC3hLVjaL0PXpSxia49Cw5byM/2ofnrz5lLugiOLIqx9YjLhFlovIdjlm9h4Z0Diep38Zzrxrn0o353dJZq0h1OOz+mV6ybNZiJ2AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;products in wip&quot;
        title=&quot;products in wip&quot;
        src=&quot;/blog/static/35c55e88dd9e42b3a19ade9557142471/8ff1e/products_in_wip.png&quot;
        srcset=&quot;/blog/static/35c55e88dd9e42b3a19ade9557142471/9ec3c/products_in_wip.png 200w,
/blog/static/35c55e88dd9e42b3a19ade9557142471/c7805/products_in_wip.png 400w,
/blog/static/35c55e88dd9e42b3a19ade9557142471/8ff1e/products_in_wip.png 800w,
/blog/static/35c55e88dd9e42b3a19ade9557142471/4b160/products_in_wip.png 854w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;Aggregate metrics of the number of products in WIP decreasing since June 23rd, 2017.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;More recently, engineering managers and engineering teams successfully reduced our code inventory from a rolling average of about 1,200 commits to about 500 commits. It is likely that we have not yet seen the full benefits of this reduction reflected in our lead and cycle times.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c031a6e6e484c9c502afa42b5758b239/b5f89/code_inventory_total.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.260257913247358%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABQElEQVQY0yWQ2U7CYBSEeQ3pRiHG0IotVZBFwEKlZSmEvUAtoAY18cYbX//zBy4mc3EmX85M6jqXI6vryJKMlE6jyhIZVUFVFG5NA9u2sC0Lp2hz7zhYhQLpqyt0NYMs8prIZVT14ppGSpEkbMPEr9dplWuYN6Y4auQ0Hce0eC4/US0+UrHKdGou/ecuQzdgHoyolSvo2gV8gp6Uyufz+I0Wx3nEazhm2esSui7LYEA8HnGYheynEw7zrfAZH4spR6HdODx7z21SMAwUWRGPCGA2m2HYGfL7/kc8jdkK8G6+4m2x5RglHNcJn5uI99WYn33MdxJzWM34ijd8LhM+1juSKGI06J2nSUlij+pDlbDTo9/2eGl28Z48uq1TNZ++qDfwfKEXgrZPoxFQrQeUSh73pTbNmkcgZpj0Jzh3Rf4BKQCrZF22kQkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;code inventory total&quot;
        title=&quot;code inventory total&quot;
        src=&quot;/blog/static/c031a6e6e484c9c502afa42b5758b239/8ff1e/code_inventory_total.png&quot;
        srcset=&quot;/blog/static/c031a6e6e484c9c502afa42b5758b239/9ec3c/code_inventory_total.png 200w,
/blog/static/c031a6e6e484c9c502afa42b5758b239/c7805/code_inventory_total.png 400w,
/blog/static/c031a6e6e484c9c502afa42b5758b239/8ff1e/code_inventory_total.png 800w,
/blog/static/c031a6e6e484c9c502afa42b5758b239/b5f89/code_inventory_total.png 853w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;A recent decrease in code inventory.&lt;/div&gt;&lt;/br&gt;
&lt;p&gt;It is possible that much of the improvement in our measured lead and cycle times is due to cleanup and better use of our Jira. Tickets in Jira now better represent what is actually happening in reality. If this is true, at least we are getting a better understanding of what the true numbers are.&lt;/p&gt;
&lt;h3&gt;Future improvements&lt;/h3&gt;
&lt;p&gt;SoundCloud still has a great deal of room left to improve its product development flow. Specifically, the number of products teams work on at a time, the number of issues engineers work on at a time, and its amount of code inventory needs to be further reduced.&lt;/p&gt;
&lt;p&gt;The number of products teams are working on at a time has successfully been reduced, but should be reduced further. Teams still work on an average of 4.5 products at a time. While it does not always make sense for a whole team to work on one product simultaneously, having the average be just slightly below the average number of engineers per team (about five), suggests there is room for improvement.&lt;/p&gt;
&lt;p&gt;The number of tasks that engineers have in WIP has not improved and should be reduced. Since June 23rd, 2017, it has remained at an average of about 2.0 per engineer. Bringing this number closer to 1.0 would likely decrease our lead and cycle times.&lt;/p&gt;
&lt;p&gt;SoundCloud’s amount of code inventory also remains a problem. Further reducing this number represents another big opportunity to reduce our lead and cycle times.&lt;/p&gt;
&lt;p&gt;This said, we’ve improved our product development flow a great deal. We’re excited to learn more about our development cycle and to make future improvements.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Inside a SoundCloud Microservice]]></title><description><![CDATA[If you’re a regular visitor to this blog, you might be aware that we have been transitioning to a microservices based architecture over the past four to five years, as we have shared insights into the process and the related challenges on multiple occasions. To recap, adopting a microservices architecture has allowed us to regain team autonomy by breaking up our monolithic backend into dozens of decoupled services, each encapsulating a well defined portion of our product domain. Every service is built and deployed individually, communicating with other services over the network via light-weight data interchange formats such as JSON or Thrift.
What we haven’t touched on so far is how a microservice at SoundCloud looks backstage.]]></description><link>https://developers.soundcloud.com/blog/inside-a-soundcloud-microservice</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/inside-a-soundcloud-microservice</guid><pubDate>Fri, 28 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re a regular visitor to this blog, you might be aware that we have been transitioning to a microservices based architecture over the past four to five years, as we have shared insights into the process and the related challenges &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;on&lt;/a&gt; &lt;a href=&quot;https://developers.soundcloud.com/blog/synchronous-communication-for-microservices-current-status-and-learnings&quot;&gt;multiple&lt;/a&gt; &lt;a href=&quot;https://developers.soundcloud.com/blog/microservices-and-the-monolith&quot;&gt;occasions&lt;/a&gt;. To recap, adopting a microservices architecture has allowed us to regain team autonomy by breaking up our monolithic backend into dozens of decoupled services, each encapsulating a well defined portion of our product domain. Every service is built and deployed individually, communicating with other services over the network via light-weight data interchange formats such as JSON or Thrift.
What we haven’t touched on so far is how a microservice at SoundCloud looks backstage.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The answer is: it depends! A necessary property of a microservices driven architecture is that a microservice represents a unit of encapsulation. As long as there is some well-understood contract in the form of an API that other services can consume, anything else becomes an implementation detail. Consequently, moving between code bases may reveal different languages, stacks, and patterns, and whether this is a blessing or curse is a hotly debated topic for someone else to explore. &lt;/p&gt;
&lt;p&gt;What’s covered in this article is written from the perspective of a single team—the Creators Team—and as such highlights some of the programming patterns that we unilaterally settled on.&lt;/p&gt;
&lt;h2&gt;Your Server as a Function&lt;/h2&gt;
&lt;p&gt;With that out of the way, it might be interesting to know what kinds of services our team owns. Since we’re responsible for anything powering the user-facing products that serve our creators (i.e. musicians and podcasters rather than listeners) the services we build and operate are quite varied: track management, messaging, RSS feed syndication, statistics and APIs powering our mobile app (&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.soundcloud.creators&quot;&gt;SoundCloud Pulse&lt;/a&gt;) are just some examples. All our services are written in Scala and built on top of &lt;a href=&quot;https://twitter.github.io/finagle/&quot;&gt;Finagle&lt;/a&gt; with the help of a few light-weight, shared internal libraries adding support for cross-cutting concerns such as configuration injection, telemetry, and session termination.&lt;/p&gt;
&lt;p&gt;Microservices are often intermediate nodes in a graph of services, acting as façades where an incoming request translates to N outgoing requests upstream, the responses to which are then combined into a single response back downstream to the client. In the fictional scenario illustrated below, ServiceA needs to reach out to and collect responses from ServiceB and ServiceC, which in turn might have more dependencies such as other services, databases, and caches.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/6ce7cbc21f85a59ae3ab7712a969b0c3/microservice_graph.svg&quot;&gt;&lt;/p&gt;
&lt;p&gt;One solution to this “scatter-gather” problem based on functional programming was pioneered by Twitter and presented in the very approachable paper &lt;a href=&quot;https://monkey.org/~marius/funsrv.pdf&quot;&gt;Your Server as a Function&lt;/a&gt;, which outlines the ideas that eventually materialized in Finagle. Finagle models each node as a pure function from some request to some eventual response:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Service&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;Rep&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Req &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; Future&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Rep&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While Finagle does an excellent job at abstracting away the tedious details behind communication protocols, connection management, and concurrency, it does leave us with certain complexities when working with this abstraction. Specifically, we will explore solutions to the following problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to collect results from multiple service calls into a single data structure&lt;/li&gt;
&lt;li&gt;How to explicitly model failures in a service’s logic layer using Scala’s &lt;a href=&quot;http://www.scala-lang.org/api/2.11.0/index.html#scala.util.Either&quot;&gt;Either&lt;/a&gt; type&lt;/li&gt;
&lt;li&gt;How to bring the above together in a convenient way using monad transformers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Parallel Futures&lt;/h2&gt;
&lt;p&gt;The service signature above tells us that we’re dealing with effectful functions: they return &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt;s, not the things we’re actually after. This means we will have to lean on different combinators to extract the desired result. Imagine we want to fetch a track and all user comments it received into some hypothetical &lt;code class=&quot;language-text&quot;&gt;TrackWithComments&lt;/code&gt; object:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  track &lt;span class=&quot;token keyword&quot;&gt;&amp;lt;-&lt;/span&gt; fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  comments &lt;span class=&quot;token keyword&quot;&gt;&amp;lt;-&lt;/span&gt; fetchTrackComments&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; TrackWithComments&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Scala will desugar for-comprehensions into a combination of &lt;code class=&quot;language-text&quot;&gt;flatMap&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; calls, which means that comments will be fetched only after we have retrieved the track. However, these two calls do not depend on each other, so we could run them in parallel instead. There are &lt;a href=&quot;http://viktorklang.com/blog/Futures-in-Scala-protips-2.html&quot;&gt;documented solutions&lt;/a&gt; to the problem of executing Scala futures in parallel in for-comprehensions, but they are non-intuitive and do not clearly express our intent.&lt;/p&gt;
&lt;p&gt;Twitter’s &lt;a href=&quot;https://twitter.github.io/util/docs/com/twitter/util/Future.html&quot;&gt;Future&lt;/a&gt; implementation gives us a powerful combinator, &lt;code class=&quot;language-text&quot;&gt;join&lt;/code&gt;, to solve this problem both more elegantly and explicitly:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; res&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Future&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithComments&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;&amp;lt;-&lt;/span&gt; Future&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fetchTrackComments&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; TrackWithComments&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These instructions explicitly encode our intent to obtain a pair of independent values: given &lt;code class=&quot;language-text&quot;&gt;(Future[A], Future[B])&lt;/code&gt; we want &lt;code class=&quot;language-text&quot;&gt;Future[(A, B)]&lt;/code&gt;. Moreover, if either of the original futures fail, the result is a failed &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt;, which allows us to propagate errors as return values instead of throwing exceptions. Looking at this, you might be reminded of database joins: given two tables, the SQL join operator will take the &lt;a href=&quot;https://en.wikipedia.org/wiki/Cartesian_product&quot;&gt;cartesian product&lt;/a&gt; of their rows and return a new table where rows are tuples containing data from both sources.&lt;/p&gt;
&lt;p&gt;In distributed systems where data crosses network boundaries frequently, failure is expected and should be embraced. We will now explore how we represent failure as a first class concept in our systems, without error handling ever getting in the way of productivity. We accomplish this by using Scala’s &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; class, a return type that allows us to focus on the “happy path”.&lt;/p&gt;
&lt;h2&gt;Down The Future Stack&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt;s are internally represented by the &lt;a href=&quot;https://twitter.github.io/util/docs/com/twitter/util/Try$.html&quot;&gt;Try&lt;/a&gt; structure. &lt;code class=&quot;language-text&quot;&gt;Try[A]&lt;/code&gt; is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tagged_union&quot;&gt;union type&lt;/a&gt;, which can be either successful (carrying a value of &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;) or failed (carrying an exception.) Exceptions are well suited to dealing with operational issues such as I/O failures, runtime platform errors, and unmet system expectations, but when it comes to representing failures in our problem domain, we avoid using exceptions. This is because logical failures in an application are best represented by proper values, and their presence should be made explicit in the type system instead of disappearing from plain view in unchecked exceptions. The upshot is that any service call in the logic layer of our systems returns a type that can either be a success or a failure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Future&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Either&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ApplicationError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;where &lt;code class=&quot;language-text&quot;&gt;ApplicationError&lt;/code&gt; is a union type of possible things that can go wrong. For instance, we can define errors like the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; ApplicationError
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; NotFound &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; ApplicationError
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; AccessDenied &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; ApplicationError
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; as the return type instead of a &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; directly states that any call to &lt;code class=&quot;language-text&quot;&gt;fetchTrack&lt;/code&gt; will return either a valid track, or a “not found” error, or an “access denied” error, but not all, meaning all possible outcomes are explicit in the function type, and must be dealt with accordingly.
&lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt; is already a functor so it comes with the &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; combinator defined, which allows us to focus on the happy path and “dive into” a successful &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// only executes the lambda if the Future was successful&lt;/span&gt;
fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trackOrError &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, &lt;code class=&quot;language-text&quot;&gt;fetchTrack&lt;/code&gt; now returns an &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; value, not a track. We would ideally like to map &lt;code class=&quot;language-text&quot;&gt;trackOrError&lt;/code&gt; again to continue following the happy path, but indeed this only works as of Scala 2.12. In Scala 2.11 or older (which is what our systems are deployed with), &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; is not “biased” toward either of the outcomes it captures (in our case an &lt;code class=&quot;language-text&quot;&gt;ApplicationError&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt;), so no functor instance is defined that allows us to focus on the happy path. Fortunately, we found a solution in Typelevel’s &lt;a href=&quot;http://typelevel.org/cats/&quot;&gt;Cats&lt;/a&gt; library, which compresses an enormous amount of utility into a small library. Let’s check it out!&lt;/p&gt;
&lt;h2&gt;Catnip’ed&lt;/h2&gt;
&lt;p&gt;Cats is not a reference to the furry animal but is short for “categories”, a nod to the branch of mathematics that studies structure in mathematics itself. It turns out that category theory is concerned with abstractions that are extremely useful when working with strongly typed languages. Specifically, category theory provides us with “blueprints” or “recipes” for the traversal and transformation of types in ways that are provably correct. One such recipe is encoded in the &lt;a href=&quot;http://typelevel.org/cats/typeclasses/functor.html&quot;&gt;Functor&lt;/a&gt; type class, which allows us to turn any type constructor into a functor, given some rules are obeyed. Cats already provides a functor instance for Scala’s &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; type, so we can now &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; over &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt;s:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Future&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Either&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ApplicationError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trackOrError &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; trackOrError map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; track &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The drawback of nesting effects is that we now have two levels of machinery: &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt;, which have to be dealt with separately. This makes it cumbersome to get our hands on a track. Ideally we want a functor instance which folds both into one: if the &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt; succeeds, and the &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; contains a “happy outcome”, then map the result. It turns out that Cats provides precisely such an abstraction: it’s called a monad transformer, and for &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; it’s called &lt;code class=&quot;language-text&quot;&gt;EitherT&lt;/code&gt;. In our case specifically, since we’re always dealing with &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt;s and &lt;code class=&quot;language-text&quot;&gt;ApplicationError&lt;/code&gt;s, we decided to glue these together into a compact internal library written on top of &lt;code class=&quot;language-text&quot;&gt;EitherT&lt;/code&gt; to model results of any computation in our systems. We call it Outcome.&lt;/p&gt;
&lt;h2&gt;The Final Outcome&lt;/h2&gt;
&lt;p&gt;Outcome is a set of type aliases and type converters that bind together &lt;code class=&quot;language-text&quot;&gt;Future&lt;/code&gt; and a well thought out &lt;code class=&quot;language-text&quot;&gt;ApplicationError&lt;/code&gt; hierarchy (with broad applicability to most or all our services) using Cats’ &lt;code class=&quot;language-text&quot;&gt;EitherT&lt;/code&gt; type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; OutcomeF&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;A&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; EitherT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Future&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ApplicationError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; A&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This definition allows us  to specify return types in a compact fashion while maintaining our focus on the happy path:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trackId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OutcomeF&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; track &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// works!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can also recover from potential errors:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; track &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; recover &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; NotFound &lt;span class=&quot;token keyword&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;(unknown)&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In fact, we have recover clauses like these in all our request handlers, so we can easily map a &lt;code class=&quot;language-text&quot;&gt;NotFound&lt;/code&gt; error to an HTTP 404 response code. But that’s not all. You may recall from above how &lt;code class=&quot;language-text&quot;&gt;Future.join&lt;/code&gt; can flip futures inside out. Using the &lt;code class=&quot;language-text&quot;&gt;EitherT&lt;/code&gt; transformer, we can do the same for outcomes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scala&quot;&gt;&lt;pre class=&quot;language-scala&quot;&gt;&lt;code class=&quot;language-scala&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; trackWithComments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OutcomeF&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;TrackWithComments&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetchTrack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;@&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; fetchTrackComments&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TrackWithComments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class=&quot;language-text&quot;&gt;|@|&lt;/code&gt; is the product combinator of Cats’ &lt;a href=&quot;http://eed3si9n.com/herding-cats/Cartesian.html&quot;&gt;Cartesian&lt;/a&gt; type which joins outcomes into a tuple of values without stepping out of the “outcome context”. We can then conveniently map this tuple onto a properly named data structure.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We started out by exploring what makes writing microservices challenging. The frequent crossing of network boundaries requires us to deal with asynchronous, error prone operations.
We furthermore pointed out that we draw a sharp distinction between operational errors and those arising in our product domain. To deal with the latter, we used Scala’s &lt;code class=&quot;language-text&quot;&gt;Either&lt;/code&gt; type, leaving us with more explicit but verbose service function calls. Finally we revealed how Cats can restore compactness of service function calls via &lt;code class=&quot;language-text&quot;&gt;EitherT&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Cartesian&lt;/code&gt;, and how we capture their functionality in a convenient type alias: &lt;code class=&quot;language-text&quot;&gt;OutcomeF&lt;/code&gt;. We barely scratched the surface of Cats in this article, it provides a wealth of utility when opting for a more functional style of programming. However, we hope we could give you a glimpse at the patterns you would find in a typical Scala code base in SoundCloud’s Creators Team and hope they may turn out as useful for you as they did for us.&lt;/p&gt;
&lt;p&gt;Until next time!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Matthias and the Creators Team&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Remote device sign-in]]></title><description><![CDATA[A method for signing in to a device that doesn’t have a keyboard When we were developing our SoundCloud app for Xbox One, something became very obvious during usability testing: signing in with a game controller really sucks. Entering text requires navigating a virtual keyboard to individual letters, numbers, and characters one at a time – such a nightmare! Plus, letters, numbers, and special characters are spread across three screens. The more secure your password is, the worse the experience is. Entering a password with a game controller using an onscreen keyboard]]></description><link>https://developers.soundcloud.com/blog/remote-device-sign-in</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/remote-device-sign-in</guid><pubDate>Wed, 28 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;A method for signing in to a device that doesn’t have a keyboard&lt;/h2&gt;
&lt;p&gt;When we were developing our SoundCloud app for Xbox One, something became very obvious during usability testing: signing in with a game controller really sucks. Entering text requires navigating a virtual keyboard to individual letters, numbers, and characters one at a time – such a nightmare! Plus, letters, numbers, and special characters are spread across three screens. The more secure your password is, the worse the experience is.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/eb13cfd093ee1597a3acd0c7672fc8de/sc-xbox-password-entry.gif&quot; alt=&quot;Entering a password with a game controller using an onscreen keyboard&quot;&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;We needed a way for people to sign in to the app some other way. Something secure and simple and fast.&lt;/p&gt;
&lt;h2&gt;The solution, in brief&lt;/h2&gt;
&lt;p&gt;The person wants to sign in to SoundCloud on Xbox One. They visit soundcloud.com/activate on another device, such as their phone or laptop, and enter a short code. Boom, they are now signed in to SoundCloud on Xbox One. Magic!&lt;/p&gt;
&lt;p&gt;We needed to consider that the person might initiate this flow on a laptop, tablet or a mobile phone. We hoped to take advantage of the person already being signed in on their second device.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Consider a person who has two devices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Device A: a game console or a TV or really any device with no keyboard or otherwise awful usability for entering text.&lt;/li&gt;
&lt;li&gt;Device B: a device where it’s easy to enter text, like a phone or a laptop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/13f2004388594fcc16116f97e5302987/e11df/pairing-illustrations-0.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAAn0lEQVQY06WQwQqEIBRF/f/v6Qf8ADdDoVgbURDRxVgRIU3NzQZXM0LMXTxc3OM7So6beeVcZ7Lv+zMnhDBN0y8GNUznHGOs7/sPnFKKMYKc5xlXlN5XmHPedd0wDOu6nvC2bVprpRSm974OCyGMMdiMTScMk6ZpHjmUUohUtCHYtq2U8no2WZYFCzHHcbTWQqT+YSgUNXL8EXIpldyC37IM0hs2fek7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A game console with monitor on the left, labelled “A”, and a mobile phone on the right, labelled “B”.&quot;
        title=&quot;A game console with monitor on the left, labelled “A”, and a mobile phone on the right, labelled “B”.&quot;
        src=&quot;/blog/static/13f2004388594fcc16116f97e5302987/8ff1e/pairing-illustrations-0.png&quot;
        srcset=&quot;/blog/static/13f2004388594fcc16116f97e5302987/9ec3c/pairing-illustrations-0.png 200w,
/blog/static/13f2004388594fcc16116f97e5302987/c7805/pairing-illustrations-0.png 400w,
/blog/static/13f2004388594fcc16116f97e5302987/8ff1e/pairing-illustrations-0.png 800w,
/blog/static/13f2004388594fcc16116f97e5302987/e11df/pairing-illustrations-0.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The person wants to be signed in to your service on Device A. The person has Device B nearby.&lt;/p&gt;
&lt;p&gt;The app on Device A requests a short easy-to-read code from your service and displays it along with an easy-to-read URL.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/de58b17e8e6a61d895298deefbfbd4d6/e11df/pairing-illustrations-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAA7klEQVQY05VQuwqDQBC8//8PSzvFxsZWEDUoKkRRfD+Q+EDwnckZQlBSZIrlltuZ2VmyX9D3fdM0bdvmeb6u6/4bZNu2B0VVVaBhOgxDTdN0XZdleVmWEyFNU9u2oyjCJJmmCT5gdl2HB1rf903TBN913Y8zPFDLshQEgWVZhmHquibQhlUQBKhFUWAiSRJFUe4UwzCAj5l5nvGVZZkoihzH8TwPJ4JgaG4UkiRhCCbwBxOK2MLzPJ8C7TiOjuOoqoqAr8zQxjKouBDyHCEhYRiGZVkgQAVM1DiOzwf7dUn4LxTH2tfLvcnbF/Z/8AQ2zsKjewElYwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A game console labelled “A” on the left and a web server on the right are having a short dialog. The game console says, “Give me a code, please!” The web server says, “Okay! ABC123”&quot;
        title=&quot;A game console labelled “A” on the left and a web server on the right are having a short dialog. The game console says, “Give me a code, please!” The web server says, “Okay! ABC123”&quot;
        src=&quot;/blog/static/de58b17e8e6a61d895298deefbfbd4d6/8ff1e/pairing-illustrations-1.png&quot;
        srcset=&quot;/blog/static/de58b17e8e6a61d895298deefbfbd4d6/9ec3c/pairing-illustrations-1.png 200w,
/blog/static/de58b17e8e6a61d895298deefbfbd4d6/c7805/pairing-illustrations-1.png 400w,
/blog/static/de58b17e8e6a61d895298deefbfbd4d6/8ff1e/pairing-illustrations-1.png 800w,
/blog/static/de58b17e8e6a61d895298deefbfbd4d6/e11df/pairing-illustrations-1.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The app on Device A polls your service every few seconds to see if the code is associated with a user.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1af3dd922e401466c29156a301ae649f/e11df/pairing-illustrations-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAA/0lEQVQY042QOW6EQBBFuf89CIlYoknmBAgiECCGTQz7DPtm7GecWMiBf9Dq6qr6Swuf/8PHicujcBzH60RZlsMw9H1vWVZd12EYRlH0fD6zLHs8HpR0WUjTlIE4juES1nV9v99sdl0HBVyU+75P00Q7CALXdX3f9zyvqqqiKDRNkyRJFEUEBOZQYIiT3jzPcCdJAh2XPM+3bUNgWRZkMXK73WRZVlUVDQFXiqKYpmkYxv1+R/Bnej+BHUiZcRyHk5Zt27quk+478ziOCHK2bYsUwcIT6QnS4oKQWIPr+mGXmgn8NE2DJhFgZJPYsPAjfywfv3Bp84JDWLBGlkv3C7WCwW4IWTPAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A game console labelled “A” on the left and a web server on the right are having a dialog. The game console says, “What is the status of ABC123?” The web server says, “Not activated.” The game console says, “How about now?” The web server says, “Not activated.” The game console says, “How about now? Any update?” The web server says, “Not activated.”&quot;
        title=&quot;A game console labelled “A” on the left and a web server on the right are having a dialog. The game console says, “What is the status of ABC123?” The web server says, “Not activated.” The game console says, “How about now?” The web server says, “Not activated.” The game console says, “How about now? Any update?” The web server says, “Not activated.”&quot;
        src=&quot;/blog/static/1af3dd922e401466c29156a301ae649f/8ff1e/pairing-illustrations-2.png&quot;
        srcset=&quot;/blog/static/1af3dd922e401466c29156a301ae649f/9ec3c/pairing-illustrations-2.png 200w,
/blog/static/1af3dd922e401466c29156a301ae649f/c7805/pairing-illustrations-2.png 400w,
/blog/static/1af3dd922e401466c29156a301ae649f/8ff1e/pairing-illustrations-2.png 800w,
/blog/static/1af3dd922e401466c29156a301ae649f/e11df/pairing-illustrations-2.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The person pulls Device B from between the couch cushions, opens a browser, and goes to that easy-to-read URL. On Device B, the person might already be signed in to your service. If they aren’t, they either sign in or create an account.&lt;/p&gt;
&lt;p&gt;Once authenticated, the person is prompted to enter the short easy-to-read code.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/686af2fc0c46de5ce22a0bf509175526/e11df/pairing-illustrations-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAA1ElEQVQY06WQzQqDMBCE8/7vIt70KHjw4g8oRkVFREQUFAwoKtrawdhUSksPncOy+ZLZTEL2T7o/tSwLY2yaJrF1O8R7wtee57mu23XdOI4gaEAopbqum6aZZdm6ruBN09i2nSTJywykqqosy4qi5HkO0ve97/vwB0GAWlUVN4dhiIlpms7zfJrrutY0zTAMSZK4eRgGegixER7RUMGjKCrLEjfjwGnetq0oCsdxLMviIxEbyziOxfO4uW1bJBKcfPswTBQXXgUuILkOfvPvv0T2P/QACODHDhwpu80AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A web server on the left and a mobile phone labelled “B” on the right are having a dialog. The phone says, “Hey is ABC123 a valid code?” The web server says, “Yes!”&quot;
        title=&quot;A web server on the left and a mobile phone labelled “B” on the right are having a dialog. The phone says, “Hey is ABC123 a valid code?” The web server says, “Yes!”&quot;
        src=&quot;/blog/static/686af2fc0c46de5ce22a0bf509175526/8ff1e/pairing-illustrations-3.png&quot;
        srcset=&quot;/blog/static/686af2fc0c46de5ce22a0bf509175526/9ec3c/pairing-illustrations-3.png 200w,
/blog/static/686af2fc0c46de5ce22a0bf509175526/c7805/pairing-illustrations-3.png 400w,
/blog/static/686af2fc0c46de5ce22a0bf509175526/8ff1e/pairing-illustrations-3.png 800w,
/blog/static/686af2fc0c46de5ce22a0bf509175526/e11df/pairing-illustrations-3.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Device B asks your service if the code that was entered is valid. For security reasons (outlined in a later section), the person is asked to confirm that they want to allow Device A to be signed in as them.&lt;/p&gt;
&lt;p&gt;Upon confirmation by the person, Device B updates the code using the authenticated user’s access token, to associate the code with the target user.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/74397e83677fe39912ec67eaa63c47df/e11df/pairing-illustrations-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAABB0lEQVQY06VQyYqDQBT0/79FPOpRvbgjajSIimsMcUyCGjdwyRQxmGHmOHVout+rqq73iOcfLMuyrisu8zz3fT8MQ9M0XdftXWC7E9vbsizTNG+3G0iXy0VRFFmWNU0zDMNxHNTruq6qCi1d133f/4iLomAYhqIomqajKLrf74IgQM9xnKqqkiSB/fWC67rwCoJgHMe3GH4sy0JAkmSWZRAjSBzH7gv4/HA4QIMpPM8DAV5t277FqKZpijzIOU1TWZb4P8/zJEmOx2MYhlgBOGBer1fbtmGxjU3se9qWBICNFDzPi6KICKfTCY47DS47k/ip3M7H43E+nzELTqTAqnb2LxDPf+AbcJS8agrsyOsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A web server on the left and a mobile phone labelled “B” on the right are having a dialog. The phone says, “Someone with access token XYZ2753258 just confirmed ABC123”. The web server says, “I’ll take it from here, thanks!”&quot;
        title=&quot;A web server on the left and a mobile phone labelled “B” on the right are having a dialog. The phone says, “Someone with access token XYZ2753258 just confirmed ABC123”. The web server says, “I’ll take it from here, thanks!”&quot;
        src=&quot;/blog/static/74397e83677fe39912ec67eaa63c47df/8ff1e/pairing-illustrations-4.png&quot;
        srcset=&quot;/blog/static/74397e83677fe39912ec67eaa63c47df/9ec3c/pairing-illustrations-4.png 200w,
/blog/static/74397e83677fe39912ec67eaa63c47df/c7805/pairing-illustrations-4.png 400w,
/blog/static/74397e83677fe39912ec67eaa63c47df/8ff1e/pairing-illustrations-4.png 800w,
/blog/static/74397e83677fe39912ec67eaa63c47df/e11df/pairing-illustrations-4.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;At the moment of confirmation, the code is associated with the user. The next time Device A polls, it sees that it can use the code to request an access token for that user. Device A then makes a request for an access token using the code.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/38fbf30a70c481bb27226a36390f8ce5/e11df/pairing-illustrations-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAABG0lEQVQY02WQyY6CYBCEef/38OgJSLx4gZCJG6tE4TBGhYQ9A7gvzHwDiXGpQ6fTf3VV/S38fuB6vZ5Op8PhsN/vqcfjkcqE5o0pNE3z0yJNU0hVVX212G63tm17nrfZbKjz+dx1XXSjKFoul0EQ3O934XK5lGWZZVld10jcbrdOqKuj0cg0TVSm0ylNGIaDwaDf7/d6vaIoBNiYrNdrKmzCIEkcTHj6buH7/mKxwB/OcDgURVGWZSyFOI4lSULbsixVVUk1m810Xe88WaAyURTFcZzz+czEMIzdbvf/Z/75+C1CpF2tVviQExINQjTs0xPq5WCf1+5icxXWsB2Px5qmTSYTepzfl5snPB4IkiQJV+GWaYs8z58J4A+pFrOppadYPwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A game console labelled “A” on the left and a web server on the right are having a dialog. The game console says, “What is the status of ABC123?” The web server says, “It’s confirmed!” The game console says, “Great! Please accept ABC123 in exchange for an access token.” The web server says, “Here: 52WTQ710J6”.&quot;
        title=&quot;A game console labelled “A” on the left and a web server on the right are having a dialog. The game console says, “What is the status of ABC123?” The web server says, “It’s confirmed!” The game console says, “Great! Please accept ABC123 in exchange for an access token.” The web server says, “Here: 52WTQ710J6”.&quot;
        src=&quot;/blog/static/38fbf30a70c481bb27226a36390f8ce5/8ff1e/pairing-illustrations-5.png&quot;
        srcset=&quot;/blog/static/38fbf30a70c481bb27226a36390f8ce5/9ec3c/pairing-illustrations-5.png 200w,
/blog/static/38fbf30a70c481bb27226a36390f8ce5/c7805/pairing-illustrations-5.png 400w,
/blog/static/38fbf30a70c481bb27226a36390f8ce5/8ff1e/pairing-illustrations-5.png 800w,
/blog/static/38fbf30a70c481bb27226a36390f8ce5/e11df/pairing-illustrations-5.png 1000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Et voilà! Now Device A has an access token for the target user. In other words, the person is signed in to Device A as the desired user.&lt;/p&gt;
&lt;p&gt;This pattern is not something we invented. Our flow is influenced by a similar flow used by YouTube on TVs and &lt;a href=&quot;https://developers.google.com/identity/sign-in/devices&quot;&gt;Google Sign-In for TVs and Devices&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Using an authenticated session on Device B&lt;/h2&gt;
&lt;p&gt;This method of signing in means the person does not have to enter their password on Device A. If they are signed in to your service on Device B, it is possible for them to avoid needing to sign in again at all.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we have a web app at soundcloud.com, and we have native apps available for Android and iOS.&lt;/p&gt;
&lt;p&gt;If the person visits soundcloud.com/activate on a laptop, we use our standard means of requiring authentication before showing the prompt for the person to enter the activation code.&lt;/p&gt;
&lt;p&gt;On mobile, we chose to reuse the same “activate” web app opened in a web view in the native app. If the person visits soundcloud.com/activate on a mobile device, we invite them to open our native app. Inside the app, we use our standard means of requiring authentication, and then we open a web view with the user’s access token appended in a URL fragment to an oauth2-style callback web address:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com/activate_oauth2_callback#access_token=ACCESS_TOKEN&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This allows the web app to get the access token from the URL fragment. We put secrets in a fragment instead of a query parameter so that secrets are not sent to the web server and therefore not stored in the web server logs. This is the recommended way of passing access tokens and other secrets in a web address.&lt;/p&gt;
&lt;p&gt;We also instruct the web app how to display the page using a query parameter:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com/activate_oauth2_callback?display=android#access_token=ACCESS_TOKEN&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You could of course build this form into your native app directly. We like that our implementation allows us to iterate quickly on this brand new feature. We can easily make security or usability improvements to the interface without shipping a new version of our native apps.&lt;/p&gt;
&lt;h2&gt;Choosing codes that are easy to read&lt;/h2&gt;
&lt;p&gt;You want the code to be as short as possible while still having enough possible codes available so that everyone who wants to sign in at the same time can.&lt;/p&gt;
&lt;p&gt;You also need to have a sparse enough usage of the possible codes such that it is unlikely that a typo will result in the person entering a valid code that has been issued at the same time to someone else.&lt;/p&gt;
&lt;p&gt;Imagine if your codes were one number long, ranging from 0 through 9. The risk of an unnoticed typo is very low, but you could still only issue 10 codes at one time.&lt;/p&gt;
&lt;p&gt;If your codes were two letters long, then you could issue 26 x 26 = 676 codes at the same time. There is now a risk of an unnoticed typo, which means you can’t safely issue all 676 codes at the same time.&lt;/p&gt;
&lt;p&gt;If you use just numbers, and your codes are 6 digits long, then there are one million codes available, assuming you allow leading zeros. Pretty good! That might be enough for your needs.&lt;/p&gt;
&lt;p&gt;If you use just letters, and your codes are 4 letters long, then there just under half a millions codes available. Still pretty good, and the codes are shorter. Not bad!&lt;/p&gt;
&lt;p&gt;If you chose to use a mix of letters and numbers, then do not use characters that are hard to tell apart. Specifically, the letter O and the number 0 are hard to tell apart, as are the letter I and the number 1. Even if you use fonts that make these characters distinct, you should avoid these altogether.&lt;/p&gt;
&lt;p&gt;Definitely don’t use special characters. That’s just asking for usability problems. If you use letters, display the codes in uppercase for readability, but verify the codes with case insensitivity. Our form for entering the code uses CSS to transform the characters into uppercase for ease of entry.&lt;/p&gt;
&lt;p&gt;We chose to use a mix of letters and numbers, and we chose to use 6 characters, which gives us over a billion codes that we can issue at one time. If a billion seems like way more than one needs, read on for further security considerations.&lt;/p&gt;
&lt;h2&gt;Security considerations&lt;/h2&gt;
&lt;p&gt;In its most basic form, this flow has a number of security problems ranging from mild and unlikely to wildly irresponsible. Mitigating these security considerations requires adding details to the basic implementation.&lt;/p&gt;
&lt;h3&gt;Accidentally granting Device A access to the wrong user&lt;/h3&gt;
&lt;p&gt;When the person visits the your service on Device B, a user may already be signed in there. People who share a device, or those with more than one account on your service, may want to sign in with a different user than the one that is currently authenticated. If they are not informed which user is authenticated on Device B, they might accidentally grant Device A access to the wrong user.&lt;/p&gt;
&lt;h4&gt;To mitigate this risk&lt;/h4&gt;
&lt;p&gt;Show the person which user is authenticated on Device B, or display several authenticated users, and allow them to choose which user will be granted access.&lt;/p&gt;
&lt;h3&gt;Accidentally granting access to someone else’s device&lt;/h3&gt;
&lt;p&gt;Imagine that two similar codes, such as XXN and XXM, were issued at the same time. XXN was issued to Device AN and shown to Person N. XXM was issued to Device AM and shown to Person M. Person N is authenticated as User N. If they mistype and enter code XXM before Person M does, then Device AM will become authenticated as User N. Oops!&lt;/p&gt;
&lt;h4&gt;To mitigate this risk&lt;/h4&gt;
&lt;p&gt;Have a big enough range of possible codes that typo collisions are unlikely. If you have very a large range of possible codes, the chances that a typo is also a valid code is very small.&lt;/p&gt;
&lt;p&gt;If possible, collect information from Device A when you create the code that the person could recognize when they are granting access. For example, if the device has a name like “Deejay’s TV”, then collect that from Device A, and show this information to the person when they are confirming that they want to grant the device access. We found that we did not have access to such data on Xbox One, however, so the best we could do was capture the type of device.&lt;/p&gt;
&lt;h3&gt;Using up the possible codes so no one can sign in&lt;/h3&gt;
&lt;p&gt;An attacker could destroy the ability for people to sign in by exhausting all the available codes. If your codes are generated on request, then as your possible codes are being used up, your service may have a near impossible time finding one it can issue.&lt;/p&gt;
&lt;h4&gt;To mitigate this risk&lt;/h4&gt;
&lt;p&gt;Implement a rate limit for creating codes. Limit based on all the facets of the request that you can, such as the IP and any unique device identifier. You will need to investigate to determine what the peak rate would be for legitimate traffic.&lt;/p&gt;
&lt;p&gt;Your codes should expire so you can reuse codes after a time. If you reuse codes, then you need a large enough pool of codes so that you don’t re-issue the same code too soon between uses. Your pool of codes should be able to carry you through at least several days before you need to start re-using them.&lt;/p&gt;
&lt;h3&gt;Guessing codes and using them to get access tokens&lt;/h3&gt;
&lt;p&gt;An attacker could try to poll for random codes, hoping to find one that is activated so they can exchange the code for a user access token.&lt;/p&gt;
&lt;h4&gt;To mitigate this risk&lt;/h4&gt;
&lt;p&gt;You could have such a very large number of possible codes that guessing a valid one is nearly impossible. However, a code that works for a person needs to be short and use a limited character set, and a bot can burn through guessing many codes if they are easy for people.&lt;/p&gt;
&lt;p&gt;You could add rate limiting for polling, and then cross your fingers and hope that you have enough possible codes, and your rate limits are good enough that the attacker won’t be able to guess a valid code while staying within your rate limits. You should add rate limiting for polling, but there is a much more secure way to mitigate this risk.&lt;/p&gt;
&lt;p&gt;When you issue a code, you should also issue a very long, hard to guess polling token. You should require this polling token for unauthenticated requests for the code. You can see this secure feature described in the  Google Sign-In for TVs and Devices documentation. They call the easy-to-read code “&lt;code class=&quot;language-text&quot;&gt;user_code&lt;/code&gt;”, and they call the polling token “&lt;code class=&quot;language-text&quot;&gt;device_code&lt;/code&gt;”.&lt;/p&gt;
&lt;h3&gt;Tricking people into giving away access to their account&lt;/h3&gt;
&lt;p&gt;There is a category of attacks where an attacker attempts to influence their victim to take an action that is not in their best interest. These are called “social engineering attacks”.&lt;/p&gt;
&lt;p&gt;For example, the attacker could take advantage of your UI design choices and their ability to supply some aspect of the display text, and use it to trick people into thinking the UI does something different than it does.&lt;/p&gt;
&lt;p&gt;Take this example, where “Deejay’s TV” is an open text field called device_name that is supplied when the code is created:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 506px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ea8c73f9856fe0af532ed2e8d841c2f4/26532/confirm-naive.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.51778656126483%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAABpElEQVQoz41Ry0rDQBQdFNy5duHOnxBcKNqlH+AHiPgZLgUt+CgUxa0KUnxiW2tRm6bVVtqFdWmfi1ZLBR+TJk1mkutNNDFGqx4uYebee07OSYiu64YNcMFz9YzeQZwlTdM6qirLsiRRRVFUVcUDKjONtdsS4xybOMU1R4I476xVq4KQEJLCZTqVSqVLxdJJ9LhQuBVFMZkQcvlcLB4XEhe5fP4L2fGDqpwzDMIYxz7nHDtoATuaBWbBS9a53lGU78Hc+Z3OZ2ZUxXtwPTDvn3t6ei7e3ZXK5UqlqnY60B1fyJnspZA6xzul9JVSWVZ0C2jS+Anv/4h8t+cA06KW26eXjGlxHN4KbQc3aFuqViqNer31aOK/tvNJUYzGNFVpNuqth/s2fVEkCpx1K4Mz27aj55H/vUwybjXLRnpHz+wa2T0Dn9YBMli7cL0PWaw9V+3DVQhuz5BokQ8WYZiArxfGyUeNEZjsg5kBmB2EqX4YJTBhj3w9MEJgesgm127gcAEiKxBetmsJIqtwugaxIEQDrv6yuXbkh+SmSTY/+p/xumR+A9HlNaoCgKrzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screenshot. Title: Confirm the activation. Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the words “Deejay’s TV”. There is a call-to-action button that says “Confirm”.&quot;
        title=&quot;Screenshot. Title: Confirm the activation. Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the words “Deejay’s TV”. There is a call-to-action button that says “Confirm”.&quot;
        src=&quot;/blog/static/ea8c73f9856fe0af532ed2e8d841c2f4/26532/confirm-naive.png&quot;
        srcset=&quot;/blog/static/ea8c73f9856fe0af532ed2e8d841c2f4/9ec3c/confirm-naive.png 200w,
/blog/static/ea8c73f9856fe0af532ed2e8d841c2f4/c7805/confirm-naive.png 400w,
/blog/static/ea8c73f9856fe0af532ed2e8d841c2f4/26532/confirm-naive.png 506w&quot;
        sizes=&quot;(max-width: 506px) 100vw, 506px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;From a design perspective, this UI seems succinct and clear.&lt;/p&gt;
&lt;p&gt;Now imagine an attacker sends a victim an email saying “Visit this URL and enter this code to get a FREE subscription!” When the victim gets to the page and enters the code it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 506px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0f2e7215e4132593d5394400b04f1c38/26532/confirm-exploit.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.51778656126483%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAAB0ElEQVQoz41Ry07bQBQdFYld112w4yeQuigClnwAH1BVfAZLJIoELVIE6hYqVYhHERACgsZxAomULArLPEggwcQ2ScCOnzNzuQ6xMQEER0f2zJk5d86dIYwx7gNC6Jn2LD2ABJscx7Fs2zAMXddM07RtGwdY2XXcdlt3KUURV3FbUIIEZ1bKZUGICwnhOJVMJlPFQnEvun16eiaKYiIuZHPZ2MGBEP+XzeWemIM8WJVSFxtxXYo6pRQVjICK04HbQa+ZUWaZ5vPGwv0HymPPWBXnkaWF6dmpZrNVyOeLpdL5edm2LHgdT8zpzLGQPMK5pml3mma0DcxMGfMyc858BGc+jMnzeAFo54ZffKSuGbtFdWdl9Xfkl97Wq9XaxWW1VqvKch1/0tWVJEl1Wa5clBVFwSAvxM4lRDEacyxDuZYaqtJU1daNcttsNJQ6EgeqfN26UR3LxLsF6nJkN3aQCt4AD9Mzo1Yv8dQfll7jmXWO3zA95VGEzDpkNuBkFc4O0dgxb36HIQJjfTBCuhwmMN4P3z7B5ABMfIQvBEb9pbEP8JnA10HfXPkPf2dg9wfszPucg92fsL8IsQhEF0L6vLdtaxYSy57Zu/SeZt5Jzu8BXNwyh4mOdnkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screenshot. Title: Confirm the activation. Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the words “SoundCloud Premium Add-on”. There is a call-to-action button that says “Confirm”.&quot;
        title=&quot;Screenshot. Title: Confirm the activation. Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the words “SoundCloud Premium Add-on”. There is a call-to-action button that says “Confirm”.&quot;
        src=&quot;/blog/static/0f2e7215e4132593d5394400b04f1c38/26532/confirm-exploit.png&quot;
        srcset=&quot;/blog/static/0f2e7215e4132593d5394400b04f1c38/9ec3c/confirm-exploit.png 200w,
/blog/static/0f2e7215e4132593d5394400b04f1c38/c7805/confirm-exploit.png 400w,
/blog/static/0f2e7215e4132593d5394400b04f1c38/26532/confirm-exploit.png 506w&quot;
        sizes=&quot;(max-width: 506px) 100vw, 506px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The attacker has created a code with the device_name “SoundCIoud Premium Add-on”. The design makes it possible to fool people into giving away access to their account.&lt;/p&gt;
&lt;h4&gt;To mitigate this risk&lt;/h4&gt;
&lt;p&gt;Use wording and design elements that make it clear what is happening. Some labels and text might seem redundant in the standard scenario, but are necessary to prevent this type of attack.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 506px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9d66726967ac1ac4ac9244c609b0d002/26532/confirm-mitigated.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100.59288537549406%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAACXUlEQVQ4y41TzWsTURDPRfAP8CBeehM8CXqwFfFvEBG1HgUFPYgHUUgQz1pMelGooEe9ePQUEQPdqqlbhRZpL8kmGjfJfme/3n6+cd7bzTaaNnV4OzvvMb+Zeb95U0rTlI4FJuSf7cRxLggsTToRLiPbdl03CII4jsMwjKLI9/0gDANCcDsZJgdnEURxQxCEer2+vi5ubW1uiOLHRmNne0dYXV0ThLW1T1+aTToNzgQrwTxpkqARR5HnebiNx8LtZI/M6I0aq9J1HWsejUbWyLL4z3Fcx7bRNC0L8bBf5mnCmKb5nqlJxgrwV7EpfG4QEpgGJrBs285q2U9ycOZUXV6qPXsckLDdanV/dmW5jzc/GFzcOZ7yxm5h52b0Os+8/W3zh/id8lYjbayxQeC4LhJOfOIzIVksQnwkPicsA79defXm+YsoCpRB39S1jtTutFuoux2p1+mgLbWlfu9XV5K67ZZpGLzYcdlRSuNkFkN/lT2mvcShBFwDPBMcndoqGDI1+9ToUeM3mDJqqvM1Uqit86UiBIEc/H4Frh2GG0fh+hG4NQeVBbh3Eh6eg8pZKC9AZR7K8+zw9hxzuHkMrh6Cd9UxWHgNd09A+Qw8OM0A1cvw5ALUrkBtEZYuwtNL7GR5ER6dh/unWKA7x+HDSw4unk7xgOIQkhg1W8yIIOGLpsWdc7bZJPqerqmqqmiqYpmGqmm6hiaeqLphoNY0zTCRY11VhgNZdm17F+y5znA4VPAbDHgQBb2xw6y5vMWu6zAbk3gItKMo3B0MNonY+GL0cPBmvs2Dp+p/wH8AZ21am+uZf7UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screenshot. Title: Confirm your device. Subtitle: Sign in to SoundCloud on this device? Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the label “Device” and the words “SoundCloud Premium Add-on”. There is a call-to-action button that says “Yes, activate this device”. Under the call-to-action is the following text: “By activating, you allow this device to sign you in. Only activate if you are doing this intentionally. Do not activate if you followed a link someone sent to you.”&quot;
        title=&quot;Screenshot. Title: Confirm your device. Subtitle: Sign in to SoundCloud on this device? Then the word “theophani” with an avatar image beside it, followed by a link symbol, followed by the label “Device” and the words “SoundCloud Premium Add-on”. There is a call-to-action button that says “Yes, activate this device”. Under the call-to-action is the following text: “By activating, you allow this device to sign you in. Only activate if you are doing this intentionally. Do not activate if you followed a link someone sent to you.”&quot;
        src=&quot;/blog/static/9d66726967ac1ac4ac9244c609b0d002/26532/confirm-mitigated.png&quot;
        srcset=&quot;/blog/static/9d66726967ac1ac4ac9244c609b0d002/9ec3c/confirm-mitigated.png 200w,
/blog/static/9d66726967ac1ac4ac9244c609b0d002/c7805/confirm-mitigated.png 400w,
/blog/static/9d66726967ac1ac4ac9244c609b0d002/26532/confirm-mitigated.png 506w&quot;
        sizes=&quot;(max-width: 506px) 100vw, 506px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;When the code is created, you could store the requesting IP or resolved geography, and then provide a warning if the code is entered in suspiciously different geography.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 506px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/56eb450495d9020860d124750176995b/26532/confirm-mitigated-w-warning.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 115.61264822134387%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAC20lEQVQ4y41UvU8UURC/lkYr/gRbo8FKCxMTexMLDfiRaG1HoCHRxEYKCRBKE2ksTCwoDYWScEfgPArBjxMucLt73O3u7fd7b793x3m7C7ccYJz73cvMvpn3MfN7U0mSJD0WKMmQWfpcCAZWyk5eJrbjUEp934+iKAiCMAxd1/WDwPc8NMvLFMH5Co3GdrVaXV1drdcbu7s7243G17W15u9mdX29Vq3WahubW1vp2WAejSeJY9wHR0QUBozSMAiiMMzB9SjKPU8HZxJGkWHb1PMdyixCbcpsQonrEeaibhIaxvHQsQGSGEwVHBMcA5gDxAbbAEvnYASoA4YKxOKzCDtDlrMK39dnIO5xCHvQbvJRaHJlfwcOfoHYyr7/gYOffOwcgtTCRTEwP3b2DwPotUGXQenwg2g9kCXoHYLWzXQRNBnkDuC1j0s2uPNQWdOLUJIiuF7fXK998ZhrGoZlmo5tY8KzxCZFek9Q4koFiYLG2/nZuaU3vh+0WvvttnB0dITFgtP7nDJOds6ZeZaNSDIk3D8oWuz8Y2v7++Y3tDkTfZ9Q6jLmOA5jjBLicMIynKJoUpqTdBC88v7Dp3fLyCqtr+KdRVGUREEUBEkUu51OB21BUnpdNCVBsCwLQ/BBFQmLkzSKE/g/OUl7RpLATU05tRQOo5v2xVST0r7AoYnc5LrIp9ANYfSQVxlJMPjzEtyvwJNLMDECz0Zh6jq8uALTN2BqDCavFZgeg+ejMD4CTy/DvQqszB4Hb3zkAS9vw8wteHUHFh/D3AM+IuYfwsI4LEzA4iN4fRdmbnK3yauwtlzQM28L+BATrsRx4CVhkODIFR+RZuAemSf6FdnOG4hpmrrBBduIjiwzLV03EJhY3cAp07Jt00bdUNU+Vm0QjKVTFEVVVUWWtT6KqmkaFtTDymK5GSOEcNXz0BNrjn1h0AyyM8fYJWL+41pcevQXFmyok5zfJs+T3OEvE6MEvYGh5hUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screenshot. Title: Confirm your device. Subtitle: Sign in to SoundCloud on this device? Under the title and subtitle is the following text: “Warning: your current IP address is in a different region than the one your activation code was requested in. Only continue if you are trying to sign in to SoundCloud on another device.”&quot;
        title=&quot;Screenshot. Title: Confirm your device. Subtitle: Sign in to SoundCloud on this device? Under the title and subtitle is the following text: “Warning: your current IP address is in a different region than the one your activation code was requested in. Only continue if you are trying to sign in to SoundCloud on another device.”&quot;
        src=&quot;/blog/static/56eb450495d9020860d124750176995b/26532/confirm-mitigated-w-warning.png&quot;
        srcset=&quot;/blog/static/56eb450495d9020860d124750176995b/9ec3c/confirm-mitigated-w-warning.png 200w,
/blog/static/56eb450495d9020860d124750176995b/c7805/confirm-mitigated-w-warning.png 400w,
/blog/static/56eb450495d9020860d124750176995b/26532/confirm-mitigated-w-warning.png 506w&quot;
        sizes=&quot;(max-width: 506px) 100vw, 506px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Have tight enough expirations on your codes such that this kind of attack would need to occur in a tight time frame. If your codes are only valid for 5 minutes, then the attacker has to find a way to provoke an action from their victim within 5 minutes.&lt;/p&gt;
&lt;h2&gt;Designing and implementing a new authentication flow&lt;/h2&gt;
&lt;p&gt;From the very beginning, we included colleagues from our Security Team to perform a  Threat Model analysis. We built out our API design using this analysis as a basis, instead of waiting until later to consider the security implications. During development, we had an eye on how to secure our new feature at all times. We adjusted the graphical interface based on a security review as well.&lt;/p&gt;
&lt;p&gt;Our team really enjoyed thinking through this problem. We were able to make the painful experience of entering text with a game controller seamless for our users. We had a lot of fun delivering a shiny new authentication experience that’s secure and feels like magic.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A Better Model of Data Ownership]]></title><description><![CDATA[Once upon a time, we had a single monolith of software, one mothership running everything. At SoundCloud, the proliferation of microservices came from moving functionality out of the mothership. There are plenty of benefits to splitting up features in this way. We want the same benefits for our data as well, by defining ownership of datasets and ensuring that the right teams own the right datasets.]]></description><link>https://developers.soundcloud.com/blog/a-better-model-of-data-ownership</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/a-better-model-of-data-ownership</guid><pubDate>Tue, 20 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once upon a time, we had a single monolith of software, one mothership running everything. At SoundCloud, the &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;proliferation of microservices&lt;/a&gt; came from moving functionality out of the mothership. There are plenty of benefits to splitting up features in this way. We want the same benefits for our data as well, by defining ownership of datasets and ensuring that the right teams own the right datasets.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;In this post we’ll briefly recap the motivations for splitting up the monolith, describe what we mean by ownership of (micro)services, and see that the responsibilities of ownership apply to datasets too. With the help of some examples, we’ll see how to decide who should be the owner of a dataset, where the costs arise when ownership is in the wrong place, and the visibility concerns that are addressed with this stronger definition of ownership. Finally, a brief word on the task of getting from where we are to a closer adherence to this model.&lt;/p&gt;
&lt;h2&gt;Recap: why split up a monolith?&lt;/h2&gt;
&lt;p&gt;It’s much easier to keep cohesion high and coupling low when there’s a boundary between components. We know this from writing code, where we split code into smaller pieces when it gets too large.&lt;/p&gt;
&lt;p&gt;At the level of building services, that split might be across modules or code repositories, and access might be over HTTP. Defining these interfaces between components allows you to design a clearer architecture, and often leads to simpler maintenance, though at the cost of complexity that comes from communicating over the network.&lt;/p&gt;
&lt;p&gt;Clear interfaces also reduce the coordination required when multiple teams work together. The teams agree on the interface, what the data and schema looks like, and can then work independently to build and maintain components. This also allows the system to scale as teams grow independently.&lt;/p&gt;
&lt;p&gt;We saw the lack of clear interfaces within our monolith create problems. It cost more time than it should have to make progress on features, and cost more to maintain them. Importantly, these costs of working with a feature were often not visible because they were distributed around everyone who touched it – which, in the monolith, meant everyone.&lt;/p&gt;
&lt;p&gt;All of this has been &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;covered before&lt;/a&gt;. The important point is that clear interfaces allow us to define ownership of the features exposed through these interfaces.&lt;/p&gt;
&lt;h2&gt;What is ownership?&lt;/h2&gt;
&lt;p&gt;Ownership of a service brings a number of responsibilities to a team. These include&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;availability and timeliness&lt;/strong&gt; – clients should be able to access data and operations provided by the service, and expect the data to be up to date (whatever that means for this service).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;quality and correctness&lt;/strong&gt; – the service should do what was agreed with its users, and when it doesn’t the owner should fix the bugs. This puts a burden on the service provider to manage, for example, the lifecycle of response schemas in a way that its clients can handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;identification of consumers&lt;/strong&gt; – the service owner should know who their users are, so that when changes are made they can be negotiated with the consumers before breaking things elsewhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key insight is that &lt;strong&gt;none of this is specific to services&lt;/strong&gt;. All of these responsibilities apply equally well to an owner of a dataset, a piece of shared infrastructure, or really anything that is an integration point between two teams! This is because a service is just one way to provide an interface to some data, giving a way to read and modify the backing dataset.&lt;/p&gt;
&lt;h3&gt;What does ownership mean for datasets?&lt;/h3&gt;
&lt;p&gt;Similarly to owning a service, owning a dataset means that you have the responsibility to produce data according to some contract with your consumers. Data should be available within some understood time window and should be correct according to that contract. As the producer, you have the responsibility to fix problems with the data.&lt;/p&gt;
&lt;p&gt;You should also be able to know who your consumers are. This visibility makes it possible to make changes to what you publish over time. There are technological fixes for this. With services this can be done by authentication/authorisation on the service, something simple like requiring a request header stating who the client is, or more sophisticated such as distributed tracing. With datasets you can monitor access to wherever the dataset is stored (e.g. reads from HDFS or an RDBMS), or adopt more asynchronous ideas like &lt;a href=&quot;https://research.google.com/pubs/pub45390.html&quot;&gt;scraping for dataset&lt;/a&gt; locations and the jobs that read/write them, and building a dependency graph between them.&lt;/p&gt;
&lt;h2&gt;How do you decide who’s the owner?&lt;/h2&gt;
&lt;p&gt;To begin with, the owner of a dataset must also be the producer – the team that runs the process that creates the data. This is because the owner can only fulfil their responsibilities if they have control over writing the data.&lt;/p&gt;
&lt;p&gt;There are two main factors that come into deciding which team should be the owner. We’ll go through them both here with an example. These two ideas are enough to reason about the ownership of a lot of datasets. (There are also cases where it may be clear that ownership is currently in the wrong place, but where there isn’t a clear team that should own it. This includes datasets that are populated from multiple sources, where ownership of an entity is shared between teams.)&lt;/p&gt;
&lt;h3&gt;Semantics&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The team producing a dataset should be the team responsible for the semantics of that dataset. Equivalently, a team not responsible for the semantics should not be producing the dataset.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A dataset is only meaningful with some definition of the information in the dataset. Many teams might have some idea of what a dataset means, but only one team has the responsibility of designing and evolving the semantics. That team should be the owner of the dataset.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 540px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/553cec1abb4f689ca597fbe7d124edd8/22492/dataset-semantics.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.62962962962963%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABTUlEQVQoz42RXc+CMAyF+f+/zEsSL3yNCTCNEj5UEEEZ0HpOHZdv4pJmXds9PeuiaZp0GAb13pu93287P59P21+vl47jqKxjjjbPs8WaptHH48Fd+r5njY+YWJbFjEAWF0Whp9NJz+ezdl1nsTW3CqCfZZnVOeeEcNT4iB3Zpa5rvV6vWpalqVsXgbfbzfL0qYRA+rzHO8gLY8j5iAUMVlWl+/1eL5eLAWvE/nDu4Dso2e12BmNdmqamjsDNZsOccDQAfxVSLhXwYkUlUDkCOIR45py641FzjKINKgngfr/fCRZyEPMRZ8EDZ0mfhZyTR/ECBRMujIDOAHmoNcM8FxF7Nl/Ttq2ET/WR/rrQEJIU3XXCs7vtViu8Io5jPRwOAuh3hoJOPxmYEtge8B7qCEmSRPM8l/BZ/ygEwGz1wy7BX8fEn+XH4OnCkcH8Bxzjte+Gtt0kAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dataset semantics implemented in the wrong place&quot;
        title=&quot;Dataset semantics implemented in the wrong place&quot;
        src=&quot;/blog/static/553cec1abb4f689ca597fbe7d124edd8/22492/dataset-semantics.png&quot;
        srcset=&quot;/blog/static/553cec1abb4f689ca597fbe7d124edd8/9ec3c/dataset-semantics.png 200w,
/blog/static/553cec1abb4f689ca597fbe7d124edd8/c7805/dataset-semantics.png 400w,
/blog/static/553cec1abb4f689ca597fbe7d124edd8/22492/dataset-semantics.png 540w&quot;
        sizes=&quot;(max-width: 540px) 100vw, 540px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We see teams trying to rebuild views of datasets from elsewhere in order to fit their precise needs. In this toy example, suppose that this Tracks team creates an event (in the event sourcing sense) each time a new track becomes available on the platform, and uses that to build a dataset summarising all of the tracks. Parts of that process might be simple (e.g. showing counts of tracks, totals of media length), but some may be much more complicated (e.g. tracks have geographical permissions modelled in a different system, and we want to summarise the number of tracks per country).&lt;/p&gt;
&lt;p&gt;Now consider a Reports team using this information to build reports. If they need more than a summary, the published dataset may not be enough. A direct way to get the information they need would be to read all of the events that went into that summary, and build the new dataset themselves. But that requires knowledge of all of the details of tracks and the permissions model and for all of that to be exposed in the events. The logic will be duplicated in the two teams, separated from where it should be. This leads to a high risk of inconsistency in different views of data.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 540px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/96849b6e142dda28942baa092aceabf5/22492/dataset-semantics-fixed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.62962962962963%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABPUlEQVQoz32SyY6CQBCGef8344gHTAwEDBoWCaAoYANdw1fSZi4zJpW2a/mXajxjjEzTJL/P+/0ubdvK4/GQ5/Mp8zx/6wT39/v97em6zr5eL/LGG8dRG9Z1lWVZNIqikMvlItfrVcHJAejqzNCfpqlkWSbn89nSt5EYDyZrrfz1g+x2u0lZltL3vSpGFeoAobaFHYYB9caDmSGs0OwsoYShOI7ldDrJ4XCQpmmkrmuN4/Godd/3qVtINlDjgUwDNp1VBmGnRuO+J7WKG0ixzN52pRZhW/0DSBLQMAzVGsPEzqqD2OU/4fZJbq/Z/UE/O+TihjjJMRBFkZIlSaJk1CBmDTwIpEEQqGWINmfGA+y/RwFwe0UFrapKVwEQa0EA4Hme2309H0AX7Ah17nTKCfdNknffIXdAscwMln8AIUuyJa8FCUAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dataset semantics done right&quot;
        title=&quot;Dataset semantics done right&quot;
        src=&quot;/blog/static/96849b6e142dda28942baa092aceabf5/22492/dataset-semantics-fixed.png&quot;
        srcset=&quot;/blog/static/96849b6e142dda28942baa092aceabf5/9ec3c/dataset-semantics-fixed.png 200w,
/blog/static/96849b6e142dda28942baa092aceabf5/c7805/dataset-semantics-fixed.png 400w,
/blog/static/96849b6e142dda28942baa092aceabf5/22492/dataset-semantics-fixed.png 540w&quot;
        sizes=&quot;(max-width: 540px) 100vw, 540px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;At the start of a project using this data, it would be difficult to predict the cost of the work, because the expertise is in the other team. Over time the cost of maintenance of the dataset is also hidden (and larger) because the work is distributed around the consumers. When the semantics of the data change later, it will be easy to miss making the required change everywhere. Handling this all in events can be particularly difficult, because changes to the schema require manipulating the history of events.&lt;/p&gt;
&lt;p&gt;A better solution is for the work to be done in the team that has full knowledge of the semantics and the responsibility of maintaining the dataset. In this case, the Tracks team should build that dataset with enough information for the consumer, and the Reports team should use the published dataset. It’s clearer what the costs are and work is not duplicated. When changes happen to the semantics, the owning team simply publishes a new dataset.&lt;/p&gt;
&lt;h3&gt;Encapsulation&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Don’t consume data from other systems’ internal data stores&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The internal data structures of a component (of any size: system, service or single class) are non-public so that invariants can be maintained. Other parties (calling code, other teams) get access through an interface. This level of indirection through a published interface allows the owner of a system to make changes to the data store without other users having to coordinate.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 517px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d0bd698936a521b8e3e1f5e3087dd37f/60eaf/dataset-encapsulation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.928433268858804%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABW0lEQVQoz52T247CMAxE8///xgOPFVAKqqAXrqLcmhLjY8mrPrC72o00auLE4xknDVHH9XqNl8sldl0Xn89nHIYhikhMKf0ZQflkv9/LfD6X7XYrj8dDXq+X/HcEVZWOx2Ni9H2fdrudfVVp0mI258uaua99PgbxcLvdZLPZSFVVcjqdTC0q1bYBBz4vikIWi4WBM+w5lFDu97sE7Z8RNk0jqtTmJC6XS9G+WgEKkfTTUINWJJCEOrVuiWrZiCBdr9eSZZmUZSl1XVv8fD5bImqAzymobZFAACKUAYIcwCJFIMEFF0V8MplInudWZLVamX0vyHkjbNvWNiEkGVKIiBPDNu0gBgEuAPu0yl2ZZVix4X1gE7vT6dTIZrOZfTnD/rhnDh9GyC1DQK94h4BklFLVwS2Oe4cL1g54rIcoxA49orEoHFf9pOg72MNGifcBHA6Hrz/lk63fns4bktufz+gosHgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dataset encapsulation violated&quot;
        title=&quot;Dataset encapsulation violated&quot;
        src=&quot;/blog/static/d0bd698936a521b8e3e1f5e3087dd37f/60eaf/dataset-encapsulation.png&quot;
        srcset=&quot;/blog/static/d0bd698936a521b8e3e1f5e3087dd37f/9ec3c/dataset-encapsulation.png 200w,
/blog/static/d0bd698936a521b8e3e1f5e3087dd37f/c7805/dataset-encapsulation.png 400w,
/blog/static/d0bd698936a521b8e3e1f5e3087dd37f/60eaf/dataset-encapsulation.png 517w&quot;
        sizes=&quot;(max-width: 517px) 100vw, 517px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In this example, consider how data gets from various systems in the company to a data warehouse used by data analysts. Such a tool can be great for analysis (find all of the data in one place, easy to query) but can lead to problems of maintenance later.&lt;/p&gt;
&lt;p&gt;Consider a tool to take a full snapshot from a database and push it to the data warehouse, where it’s available with the same schema and data as at the source. This provides a really easy way to get your data into one place. But it also couples the data warehouse to the internal database of your service – specifically it couples together the schema of your database, the data warehouse, and any query that reads that data and expects a certain schema. When you want to change your supposedly-internal database schema, you suddenly have to coordinate with a lot of downstream users. Worse, given that the data warehouse is a centrally accessible resource, you probably can’t know who those users are.&lt;/p&gt;
&lt;p&gt;This setup has &lt;strong&gt;made the internal database an interface to the data&lt;/strong&gt;, breaking the system’s encapsulation.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 605px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4a306802f50ee060998f06283978e5ce/b1c25/dataset-encapsulation-fixed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.06611570247933%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABTklEQVQoz22S7a6CMAyGuf87JCESBCIoICIowlhPnyY98YdLmvXzbftuybZtAdn33eTz+YR1XUPf92GaJrO/c7C5ReSnJMuyyPP5lNfrJeicGKPcbrd/230hBFFgUVBRUHm/35ZDLX4kud/vcRzHqIGIrkHTubWR6QoUu66LwzDYTQwfdVVVxcvlEo/jiNogAihN08j1ehVd03RNkvP5LHmeS9u28ng8JMsyKYrCbCZEqKVOm9iUSpUk2kUQHDqBaCdbDRoQ1uIASh6A0AEg8TRNbQhsAwSIZILwMc+z6XVdWzFxfN8xdKZlI24aAUjzxEl1hx/W+X4UHoEccskry9LAoYKVyaUmYU2EoN9OAxNwswoAiD6A8cZkrA7P8K1fzEAN0HkBgGSKKDidTuanCTYxhEk5/n38y8C9PQqg8MON0/+b/zlsVvXpHfDX+QPSpgAgt8JaAgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dataset encapsulation with a proper interface&quot;
        title=&quot;Dataset encapsulation with a proper interface&quot;
        src=&quot;/blog/static/4a306802f50ee060998f06283978e5ce/b1c25/dataset-encapsulation-fixed.png&quot;
        srcset=&quot;/blog/static/4a306802f50ee060998f06283978e5ce/9ec3c/dataset-encapsulation-fixed.png 200w,
/blog/static/4a306802f50ee060998f06283978e5ce/c7805/dataset-encapsulation-fixed.png 400w,
/blog/static/4a306802f50ee060998f06283978e5ce/b1c25/dataset-encapsulation-fixed.png 605w&quot;
        sizes=&quot;(max-width: 605px) 100vw, 605px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Indirection to the rescue. Rather than using the snapshot tool to copy from the database to the data warehouse, you as the team producing the data should copy the data somewhere else, say on a shared filesystem such as HDFS. You’ll need to define a schema for the snapshot, even if this is (initially) only a representation of the source schema. You’ll want to consider the design of this interface, too. For example, include only the fields you need first, adding others later as required. As the schema evolves you’ll need to think about backwards compatibility, to ensure that consumers will continue to be able to read the data.&lt;/p&gt;
&lt;p&gt;This approach gives a new integration point, and a way to decouple the lifecycle of your database schema from that of your consumers. This doesn’t solve all of your problems (what if my schema has to change in a way that isn’t backwards-compatible?) but it does give a place to start and makes it easier to manage those more difficult changes.&lt;/p&gt;
&lt;h2&gt;What does this mean for my team?&lt;/h2&gt;
&lt;p&gt;Overall, the point of arranging ownership better is that as a company we spend less on maintenance. This will mean work moving between teams and consolidating in a single place per dataset. In some cases this will mean a team needs to take on more work; in some cases less.&lt;/p&gt;
&lt;p&gt;Let’s look at the viewpoints for a few different roles.&lt;/p&gt;
&lt;h3&gt;I’m a producer&lt;/h3&gt;
&lt;p&gt;Where you produce data there is an expectation that it should be exposed appropriately to consumers who need it. This may mean some new work to understand their requirements and build the integration. You should understand the severity of failure or data loss on your consumers.&lt;/p&gt;
&lt;p&gt;Where there are existing dependencies on data you own, but where you don’t own the process that produces that dataset, there is an opportunity to remediate technical debt. This can happen by introducing a publishing workflow that you own and that makes sense in your domain, and negotiating usage with consumers. Your consumers will then have much easier access to this data, and you will be in control of all of the moving parts when you need to make changes.&lt;/p&gt;
&lt;h3&gt;I’m a consumer&lt;/h3&gt;
&lt;p&gt;Where you consume data, there is an expectation that you do less rebuilding of datasets, and instead, negotiate with the producer of your data to have them produce it in an appropriate form.&lt;/p&gt;
&lt;p&gt;Where you are already building datasets whose semantics you don’t control, work with the team that should own the dataset so that they produce and publish the data you need in a way that’s appropriate for both. This may be as simple as the producing team running an existing job, or may involve asking them to build something new to expose the data.&lt;/p&gt;
&lt;h3&gt;I’m a product owner&lt;/h3&gt;
&lt;p&gt;Where you own a feature, consumers of the data generated by your feature are your users too. When considering priorities for your team, you’ll need to balance the needs of your internal data consumers with those of your external users.&lt;/p&gt;
&lt;h2&gt;Show me the data!&lt;/h2&gt;
&lt;p&gt;How can this work in practice? A common fear arises when you need some data, when it isn’t available in the right form and when your project is short on time. It may appear quicker to build the data yourself than to request that the work be prioritised in another team, where you’re competing for attention among their other priorities.&lt;/p&gt;
&lt;p&gt;There are essentially three options that distribute the initial cost (writing the job to produce the data) and the ongoing maintenance cost. Either way, the code to produce the dataset has to be built somewhere.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;consumer builds and owns – the consumer takes on all of the cost and the wider organisation takes no shared benefit&lt;/li&gt;
&lt;li&gt;consumer builds, producer owns – the consumer, in cooperation with the producer, builds the dataset and then hands off ownership to the producer. The consumer takes the up-front cost, the producer takes the longer term cost, and the total future cost is reduced because no-one needs to duplicate the implementation.&lt;/li&gt;
&lt;li&gt;producer builds and owns – the producer does the initial work and takes all of the cost, and again the benefit is shared. This is the no-compromise approach, and may not be possible in short-term due to balancing other priorities.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In any case, the outcome of this project should be that a curated dataset exists, the dataset is owned by the producer of the data, and consumers integrate with this and not the source data.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When accessing and using data that is owned by other teams, certain patterns lead to high costs of maintenance that are not apparent at the start. It can be difficult to track these costs later when they become distributed around multiple teams.&lt;/p&gt;
&lt;p&gt;As a consumer of data, avoid building datasets whose semantics you don’t own, because it’s duplicate effort and you leave yourself at the mercy of future changes you don’t control. Use published interfaces to access data; asking the producer of the data to create one is preferable to doing it yourself or accessing their internal data stores directly. As a producer, it’s important to think about how the data your feature generates will be used, and how to expose to other teams in a way that doesn’t spread maintenance responsibilities.&lt;/p&gt;
&lt;p&gt;Following these two principles should make it easier to keep your data integrations well understood and maintainable, by giving better visibility over data that is shared between teams.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Lessons in resilience at SoundCloud]]></title><description><![CDATA[Building and operating services distributed across a network is hard. Failures are inevitable. The way forward is having resiliency as a key part of design decisions. This post talks about two key aspects of resiliency when doing RPC at scale - the circuit breaker pattern, and its power combined with client-side load balancing.]]></description><link>https://developers.soundcloud.com/blog/lessons-in-resilience-at-SoundCloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/lessons-in-resilience-at-SoundCloud</guid><pubDate>Wed, 07 Dec 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Building and operating services distributed across a network is hard. Failures are inevitable. The way forward is having resiliency as a key part of design decisions.&lt;/p&gt;
&lt;p&gt;This post talks about two key aspects of resiliency when doing RPC at scale - the circuit breaker pattern, and its power combined with client-side load balancing.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Circuit breakers v1.0&lt;/h2&gt;
&lt;p&gt;A basic definition of the Circuit Breaker pattern and its applicability is explained in &lt;a href=&quot;http://martinfowler.com/bliki/CircuitBreaker.html&quot;&gt;Martin Fowler’s post on the topic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SoundCloud’s original implementation meant a client’s view of a remote service transitioned between three pre-defined states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Closed&lt;/strong&gt; -&gt; Everything seems fine, let all requests go through. After &lt;em&gt;n&lt;/em&gt; consecutive failures, transition into &lt;em&gt;Open&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open&lt;/strong&gt; -&gt; The request, if let through, has a high probability of failing, given recent data. Fail the request pre-emptively and allow the remote service to recover. The circuit remains open for a pre-defined duration, and all requests are failed during that time.
Thereafter an attempt to reset is made by transitioning into &lt;em&gt;Half-Open&lt;/em&gt; mode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Half-Open&lt;/strong&gt; -&gt; Something bad happened a while back, let’s be cautious and let a single request through. If it passes, transition to &lt;em&gt;Closed&lt;/em&gt;, else to &lt;em&gt;Open&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this implementation, a &lt;strong&gt;single failure&lt;/strong&gt; becomes the deciding factor for opening a circuit, blocking all requests to the remote service for a period. This meant degraded end user experience and having to manually restart client systems to reset the state.&lt;/p&gt;
&lt;h2&gt;The missing parts&lt;/h2&gt;
&lt;p&gt;As you start operating systems under high load, the following questions arise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can we make more accurate predictions about the health of a remote service by using a moving window and tracking success as a &lt;strong&gt;percentage&lt;/strong&gt; of total requests, as opposed to opening on &lt;strong&gt;n consecutive failures&lt;/strong&gt; with unbounded duration?&lt;/li&gt;
&lt;li&gt;What happens when one or a few instances (limited to a specific network zone in a data-center, maybe) of a service are unhealthy, but a majority of other instances are healthy and available? Is it fair to &lt;strong&gt;fail all requests&lt;/strong&gt; to a service in such a situation? This is especially relevant in a microservices architecture, where scaling horizontally is common.&lt;/li&gt;
&lt;li&gt;How can remote services convey more information about their health? A HTTP-based service may be up and running but repeatedly responding with &lt;strong&gt;5xx&lt;/strong&gt;. Can we make clients smarter by using application specific information to decide when it’s a good idea to avoid overloading services?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A better alternative&lt;/h2&gt;
&lt;p&gt;In &lt;a href=&quot;https://developers.soundcloud.com/blog/category/finagle&quot;&gt;a series of previous posts&lt;/a&gt;, we’ve described how &lt;a href=&quot;http://twitter.github.io/finagle/guide/&quot;&gt;Finagle&lt;/a&gt; plays a key role in building our services.&lt;/p&gt;
&lt;p&gt;Given JSON over HTTP is used for inter-service communication at SoundCloud by most services, resiliency at this layer is critical to our uptime and dealing with all possible classes of failures.&lt;/p&gt;
&lt;p&gt;Earlier this year, we started exploring solutions aimed at improving resilience for our HTTP clients.&lt;/p&gt;
&lt;p&gt;We found that most of the gaps detailed above could be addressed through a combination of modules included in the Finagle stack for building clients.&lt;/p&gt;
&lt;p&gt;This was good news, but we still needed custom tweaks. The most important of these was to identify and compose multiple available modules, to help us define policies for how clients behave in failure scenarios and meet our expectations for high availability and a great user experience.&lt;/p&gt;
&lt;p&gt;Specifically, we could map each question to a module in Finagle that could potentially address the gaps:&lt;/p&gt;
&lt;h3&gt;Better than n consecutive failures &lt;br/&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; : An unbounded duration, instead of a moving window, means clients infer inaccurate information about the health of remote services.
A &lt;a href=&quot;https://twitter.github.io/finagle/guide/Clients.html#failure-accrual&quot;&gt;failure accrual module in Finagle&lt;/a&gt; allows clients to define a policy for success in terms of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a moving window specified as a duration or number of requests&lt;/li&gt;
&lt;li&gt;a success rate, expressed as a percentage of requests that should return an acceptable response&lt;/li&gt;
&lt;li&gt;a duration for which to mark a specific instance of a service as unavailable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This captures all dimensions by which we want to define success thresholds.&lt;/p&gt;
&lt;h3&gt;Client’s view of individual service instances&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; : A client’s aggregated view of a remote service means a single rogue instance can trick you into believing that the whole service is unhealthy.&lt;/p&gt;
&lt;p&gt;Finagle monitors health of services, per instance, and applies the success policy defined by clients to decide if it can allow requests through to a given instance.&lt;/p&gt;
&lt;p&gt;A few unhealthy nodes can no longer render your service completely unavailable. Big win!&lt;/p&gt;
&lt;p&gt;In conjunction with &lt;a href=&quot;https://twitter.github.io/finagle/guide/Clients.html#load-balancing&quot;&gt;client side load balancing&lt;/a&gt;, this creates a dynamic view of a service for clients.&lt;/p&gt;
&lt;p&gt;Clients can now continue fetching data, with requests smartly routed only to service instances marked healthy, while unhealthy instances are temporarily ignored and allowed to recover.&lt;/p&gt;
&lt;h3&gt;Using the Application layer for decision making&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; : network layer information alone is often not good enough to make informed decisions about health of remote services.&lt;/p&gt;
&lt;p&gt;The ability for clients to define what classifies as failures, using information available in Application layer protocols, is powerful. This gives fine-grained control, and is an improvement over the standard network layer signals to classify remote invocations as success or failure.&lt;/p&gt;
&lt;p&gt;Clients should be able to classify failures from services, based on protocol specific data (e.g.: a HTTP &lt;strong&gt;5xx&lt;/strong&gt; response code).&lt;/p&gt;
&lt;p&gt;This provides much needed application specific context to circuit breakers and results in accurate decisions regarding the health of individual instances of a service.&lt;/p&gt;
&lt;p&gt;Support for &lt;a href=&quot;https://finagle.github.io/blog/2016/02/09/response-classification/&quot;&gt;response classification&lt;/a&gt; was introduced in Finagle earlier this year, just in time for us to put it to good use.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It is important to acknowledge that even the task of composing appropriate modules and configuring thresholds takes a fair bit of learning until you  get it right.&lt;/p&gt;
&lt;p&gt;We switched to using a moving window of &lt;em&gt;duration&lt;/em&gt; instead of &lt;em&gt;number of requests&lt;/em&gt;, as we realised it was hard to define thresholds that worked equally well for high and low traffic services.&lt;/p&gt;
&lt;p&gt;Similar implementations exist for services that talk to database systems, cache nodes etc.&lt;/p&gt;
&lt;p&gt;Given that we were already on the Finagle stack, the capabilities listed were compelling. A combination of &lt;a href=&quot;https://github.com/Netflix/ribbon&quot;&gt;Ribbon&lt;/a&gt; + &lt;a href=&quot;https://github.com/Netflix/hystrix&quot;&gt;Hystrix&lt;/a&gt; (Java-based libraries, aimed at fault tolerance, from Netflix) make for a decent alternative.&lt;/p&gt;
&lt;p&gt;The above points aim to provide a high level summary of capabilities to look for when choosing to implement or use a framework aimed at improving resiliency.&lt;/p&gt;
&lt;h2&gt;Related concerns&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;What should be the policy around clients retrying requests?&lt;/li&gt;
&lt;li&gt;When dealing with sudden surge in incoming traffic, how can we cordon off services at the edge layer and keep critical core systems available?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We hope to share our experiences on these aspects, as we move forward, and scale out further.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[API Sign-up Changes]]></title><description><![CDATA[Today we are launching a new API application process. Starting today, developers will need to fill out an application form to request access…]]></description><link>https://developers.soundcloud.com/blog/api-sign-up-changes</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/api-sign-up-changes</guid><pubDate>Fri, 23 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Today we are launching a new API application process.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Starting today, developers will need to fill out an application form to request access to our API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here is why.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As SoundCloud has grown, we’ve seen our API used in countless innovative ways that support and enhance the SoundCloud experience for our creators and listeners around the globe. At the same time, we’ve also seen a number of apps or services that act on behalf of our users without their explicit permission or attempt to use creator content in ways that are not permitted by our API Terms of Use. Apps or services that operate in this way do not support our creators, listeners or the SoundCloud community.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here is what it means.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Developers who currently have API credentials and comply with our API Terms of Use may continue to use the SoundCloud API. All new requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with our API Terms of Use. If your application is successful, you will receive an API key.&lt;/p&gt;
&lt;p&gt;To review our API Terms of Use, &lt;a href=&quot;https://developers.soundcloud.com/docs/api/terms-of-use&quot;&gt;click here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you wish to submit an API Application Form, &lt;a href=&quot;https://soundcloud.com/you/apps/new&quot;&gt;click here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Microservices and the monolith]]></title><description><![CDATA[In a previous series of blog posts, we covered our decision to move away from a monolithic architecture, replacing it with microservices, interacting synchronously with each other over HTTP, and asynchronously using events. In this post, we review our progress toward this goal, and talk about the conditions and strategy required to decommission our monolith.]]></description><link>https://developers.soundcloud.com/blog/microservices-and-the-monolith</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/microservices-and-the-monolith</guid><pubDate>Fri, 26 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;In a previous series of blog posts&lt;/a&gt;, we covered our decision to move away from a monolithic architecture, replacing it with microservices, interacting synchronously with each other over HTTP, and asynchronously using events. In this post, we review our progress toward this goal, and talk about the conditions and strategy required to decommission our monolith.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Let’s review briefly. We are presented with a problem; namely, we are unable to confidently make changes to the monolithic system powering SoundCloud, and that the growth of its database schema exceeds our ability to support it. So we need to decommission the monolith, and decided to migrate to a microservices architecture. Our plan for migrating away from a monolithic architecture to one based on microservices has been:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invest in internal tools and libraries to make the creation of microservices easier.&lt;/li&gt;
&lt;li&gt;Expect that new features can be built outside of the monolith, using only the internal API it provides to access its data, or the events it emits on state changes.&lt;/li&gt;
&lt;li&gt;When making significant changes to a feature or entity which exists inside the monolith, consider implementing these in a new service which is outside the monolith. We call these &lt;em&gt;extraction projects&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This strategy has given us some great results. We have drastically reduced the time and number of decisions required to create a new service. These services benefit from world-class telemetry capabilities provided by &lt;a href=&quot;https://prometheus.io&quot;&gt;Prometheus&lt;/a&gt;; confidence in deployments thanks to &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt;, &lt;a href=&quot;http://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; and the work we do to improve testing of service interactions; and an efficient and powerful network stack in &lt;a href=&quot;https://twitter.github.io/finagle/&quot;&gt;Finagle&lt;/a&gt;. Work done in any of these areas has a powerful effect on the productivity of all engineers using these tools and libraries. If an engineer working in the core group makes a change which increases the productivity of many other engineers, the net effect can be equal to hiring an additional engineer. As we grow, these small effects result in dramatic improvements visible across the entire engineering organization.&lt;/p&gt;
&lt;p&gt;As a strategy for decommissioning our monolith, though, this strategy has not served us well. We have noticed that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An extraction project, if its goal is to remove all code related to an entity from the monolithic codebase, is a multiple-month undertaking with dependencies on all teams who use that entity.&lt;/li&gt;
&lt;li&gt;Deploying a new service which serves all needs around an entity requires understanding — and, in cases where database joins are performed, changing — the code related to that entity in the monolithic codebase.&lt;/li&gt;
&lt;li&gt;Investing heavily in the skills and tools needed for our new microservices architecture makes it harder to find the engineers who are willing and able to work on the monolithic codebase. Many engineers feel intimidated by it and do not feel a sense of ownership for it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That being said, we have been able to extract some services. Most often, this has happened in teams where engineers have experience with the monolith. In these cases, the engineers were able to make the case for an extraction project: the feature they were beginning to work on required integration with the monolith, and could only be expected to work for as long as the monolith continued to work. With database growth rates as they are, and diminishing knowledge of the codebase, this was a short enough period that the decision to do an extraction project was clear.&lt;/p&gt;
&lt;p&gt;In many other cases, though, developers were not able to make this case, or were not even aware that they should, because they felt so removed from the monolith. In these cases, we have even seen services bypass the monolith and access its database directly, because the developers working on a new feature lacked the knowledge of how to modify its code, and were far quicker on our newer, better-supported, Scala stack. When we come to work on an extraction later, services accessing the database directly present a problem for the project. They require a special negotiation about how to integrate, rather than the standard “from this date on, please use the Foo service for this endpoint, rather than the monolith.” Our investment in making services easier to build has created the conditions for behaviors which make it harder to decommission our monolith.&lt;/p&gt;
&lt;p&gt;How, then, should we revise our strategy, and decommission the monolith faster? We are exploring a new approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recognize that, for an extraction project whose definition of done is “remove all code related to an entity from the monolith,” the part that will take the longest and be the most complicated is to change all call sites related to this entity. This is especially difficult in cases where the calling code relies on an endpoint which joins two entities, and the future architecture will see these entities belonging to different services. To accelerate this effort, we must distribute the work.&lt;/li&gt;
&lt;li&gt;Change the definition of done for an extraction project to “there is a service available which supports all access patterns for this entity which we can support in future, but there is still use of the monolith by some downstreams integrating with this entity.” Since this means an extraction will not be complete until downstream services make changes, the below points ensure progress toward the eventual goal of complete extraction from the monolith.&lt;/li&gt;
&lt;li&gt;Use monitoring to measure progress in migrating calling code.&lt;/li&gt;
&lt;li&gt;Produce guidelines which explain how and why to compose responses from multiple backing services. This guidance should include what to do about the resulting consistency issues (“the tracks service has returned me a track belonging to user 123, but the users service tells me that user doesn’t exist”).&lt;/li&gt;
&lt;li&gt;Produce clear guidance around the ways in which services of different kinds can integrate with an entity. For example, a service should never access another service’s database directly for online use cases. Hold engineers to account for ensuring their systems follow this guidance.&lt;/li&gt;
&lt;li&gt;Prioritize extraction projects based on their size and rate of growth in the monolith’s database, since this is the most pressing risk to the health of that codebase.&lt;/li&gt;
&lt;li&gt;Make use of the monolith experts we have to guide and accelerate the creation of services which will replace parts of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We hope that this approach will get us to the goal of decommissioning our monolith faster, and continue to make use of the investment we have made in our microservices ecosystem, while dealing with the forces which led it to work against this goal.&lt;/p&gt;
&lt;p&gt;Are you working to decommission a monolith and move to microservices? We’d love to hear from you about ideas or approaches which have worked for you.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[React Native at SoundCloud]]></title><description><![CDATA[About a year ago we faced an interesting question at SoundCloud: can we build SoundCloud Pulse — our app for creators — with React Native? Is a five-month-old technology mature enough to become part of SoundCloud’s tech stack?]]></description><link>https://developers.soundcloud.com/blog/react-native-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/react-native-at-soundcloud</guid><pubDate>Wed, 03 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;About a year ago we faced an interesting question at SoundCloud: can we build SoundCloud Pulse — our app for creators — with React Native? Is a five-month-old technology mature enough to become part of SoundCloud’s tech stack?&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;A couple of months prior to this question, we started to design our second set of native apps for Android and iOS. We focused on iOS first because that is where the majority of the creators were. Finding iOS engineers proved harder than we thought, which is a situation likely familiar to you. For Android, we partnered with &lt;a href=&quot;https://www.novoda.com/&quot;&gt;Novoda&lt;/a&gt;. We did not want to have many months between the iOS and Android releases. Meanwhile and independently, the design team was running user-testing sessions with React Native-based prototypes and we also had capacity from very eager web engineers.&lt;/p&gt;
&lt;h2&gt;Warming up&lt;/h2&gt;
&lt;p&gt;We had two main goals at the start. Including all teams whose input was valuable for this project and convincing ourselves of React Native. Input from our iOS team with many years of mobile app development experience was very valuable and reassuring. At the same time we were able to give them context as to why we were investigating React Native. Openly talking about the project in the company and including people in the decision making process helped us a lot in learning what React Native could and couldn’t do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which elements can and should be written natively?&lt;/li&gt;
&lt;li&gt;Which elements can and should be written in JavaScript?&lt;/li&gt;
&lt;li&gt;Can it compete with native apps in a way that users won’t notice it is “different”?&lt;/li&gt;
&lt;li&gt;Does React Native support the native share dialogue?&lt;/li&gt;
&lt;li&gt;Does it support deep linking?&lt;/li&gt;
&lt;li&gt;Does it support securely storing access tokens?&lt;/li&gt;
&lt;li&gt;Does it support Facebook &amp;#x26; Google sign-in SDKs?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also had questions about how well it would integrate with our existing native libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can we run acceptance tests with our existing iOS tooling?&lt;/li&gt;
&lt;li&gt;How can we implement internationalization?&lt;/li&gt;
&lt;li&gt;Can we integrate it into our continuous-integration pipeline?&lt;/li&gt;
&lt;li&gt;What does the performance look like for long lists? In general?&lt;/li&gt;
&lt;li&gt;Does it have a negative effect on battery life?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During a two-week &lt;a href=&quot;https://en.wikipedia.org/wiki/Spike_(software_development)&quot;&gt;spike&lt;/a&gt;, we built a prototype of the SoundCloud Pulse app to have a better understanding of React Native in practice. After the first week, we had already fleshed out most screens of the app and were surprised how quickly we were progressing. During the second week, we wanted to focus on the unanswered questions and write some native code. It was easy to bridge our existing iOS libraries with our React Native prototype. We completed the prototype with support from our iOS engineers and concluded that we had enough confidence in React Native to go ahead with the project full time.&lt;/p&gt;
&lt;h2&gt;The journey&lt;/h2&gt;
&lt;p&gt;We started working on the app with two JavaScript engineers and one iOS engineer. There were no designs for the iOS app yet but we knew that we needed to reach feature parity with the Android app and so we used the Android app as a blueprint for the iOS version.&lt;/p&gt;
&lt;p&gt;It turned out to be a great idea to have an iOS developer working side by side with the JavaScript developers. There was a lot of knowledge sharing in the initial couple of weeks and we were able to keep up the fast pace from the spike sprint. We finished entire screens in single days and even built more features into the app than we had on Android in the beginning (e.g. 1Password integration).&lt;/p&gt;
&lt;p&gt;In contrast to the spike sprint we focused more on achieving high test coverage. Coming from a mostly web background we were used to be able to deploy hot fixes within a couple of minutes in case of a runtime error. On mobile this is not possible and so we had to test more thoroughly before shipping a new version of our app. We thought about using services like &lt;a href=&quot;https://microsoft.github.io/code-push/&quot;&gt;CodePush&lt;/a&gt; that allow you to push an updated version of your app without needing to wait for the app store review process. However, all those services were not stable enough in the beginning and we decided not to use them to reduce the amount of sources of errors.&lt;/p&gt;
&lt;p&gt;Development of the app started mid-October 2015 and we shipped the first version mid-February. It took us four months to write the app with three developers. Four months from a spike code base to a unit-tested, component-tested, and integration-tested application. We estimated that it would take us six months to finish the app but we underestimated how fast React Native allowed us to be.&lt;/p&gt;
&lt;h2&gt;Things we’ve learned along the way&lt;/h2&gt;
&lt;h3&gt;Native libraries&lt;/h3&gt;
&lt;p&gt;For many popular native libraries / APIs / SDKs someone probably has written a bridge to React Native already. Of course the &lt;a href=&quot;https://github.com/facebook/react-native-fbsdk&quot;&gt;Facebook SDK&lt;/a&gt; is one of them, so is &lt;a href=&quot;https://github.com/devfd/react-native-google-signin&quot;&gt;Google Signin&lt;/a&gt; but also things like &lt;a href=&quot;https://github.com/Appboy/appboy-react-sdk&quot;&gt;Appboy&lt;/a&gt;, access to the &lt;a href=&quot;https://github.com/oblador/react-native-keychain&quot;&gt;iOS Keychain&lt;/a&gt; and many more.&lt;/p&gt;
&lt;p&gt;If your biggest fear of jumping on the React Native bandwagon is that you have already amassed many great Objective-C libraries that you don’t want to lose, don’t worry, it is very easy to use them. React Native’s &lt;a href=&quot;http://facebook.github.io/react-native/docs/native-modules-ios.html&quot;&gt;native modules&lt;/a&gt; are well equipped to bridge anything you have over to be accessed by JavaScript, and they are really quick to implement. It helps having someone with Objective-C knowledge in your team or someone who can help out, but the API is very understandable even for non iOS developers.&lt;/p&gt;
&lt;p&gt;A perfect example of this was our event-logging library. It was battle tested in our listener app, it had support for batch sending events, so it would be lighter on the network traffic, and it could handle offline / bad connectivity. We were able to extract it from the listener application without too much effort. The rest was as simple as bringing a few methods like &lt;code class=&quot;language-text&quot;&gt;initialize&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pageView&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;click&lt;/code&gt; to JavaScript. \o/&lt;/p&gt;
&lt;h3&gt;Native infrastructure&lt;/h3&gt;
&lt;p&gt;We didn’t need to reinvent the wheel when it came to building and testing our application either. Since React Native applications are built pretty much like any other iOS app (not counting the extra step of compiling JavaScript of course ;)) our iOS and testing teams were able to set up a CI environment very similar to the current structure within a couple of days. The acceptance tests were written in the same style as our iOS developers benefiting a lot from their experience and it making it easier to set up.&lt;/p&gt;
&lt;p&gt;Another great help was knowledge around how to distribute an application to the app store and the process that revolves around that. Since we were mostly web developers and did not have any experience with that process, setting it up on the CI saved us a considerable amount of time running builds manually and uploading them to the app store by hand.&lt;/p&gt;
&lt;h3&gt;Web libraries&lt;/h3&gt;
&lt;p&gt;We could reuse a lot of our internal web modules with little to no modification. One good example is the way we handle internationalization. We are using the same library that we are using on all of our web-based applications, so we only needed to adapt our string parsing slightly.&lt;/p&gt;
&lt;p&gt;Another example is a library for defining API endpoints in a centralized way. We were able to use it straight up, no changes necessary, by passing React Native’s &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; function as the transport layer. These were just two examples but there were many more small utilities / helpers that we were able to just drop in.&lt;/p&gt;
&lt;h3&gt;React Native Gotchas&lt;/h3&gt;
&lt;p&gt;Of course no technology is perfect and so React Native comes with its own set of small gotchas. None of them are deal breakers for us, but you should still know about them before starting a React Native project.&lt;/p&gt;
&lt;p&gt;The biggest one by far is the fact that React Native is evolving very quickly, which of course is great, but it does incur a cost. There is a new version every two weeks (along with a release candidate of the next version) and the “Breaking Changes” section is often extensive. If you update on a regular basis you’ll be mostly fine; the bigger potential issues come from the third party libraries you use. As an example, now you import &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; from &lt;code class=&quot;language-text&quot;&gt;react&lt;/code&gt; instead of from &lt;code class=&quot;language-text&quot;&gt;react-native&lt;/code&gt; which is a problem if the third party libraries have not been updated to do the same. On top of that, Facebook definitely values pushing forward more than safely updating which was most apparent in an update to Babel 6 that &lt;a href=&quot;https://github.com/facebook/react-native/issues/4062&quot;&gt;broke many libraries&lt;/a&gt; and took weeks for everyone to catch up. All of this basically means you need to put a little more time aside for keeping up-to-date than with more mature frameworks.&lt;/p&gt;
&lt;p&gt;The other little pain point is documentation. It is not as extensive as we wish and there are still some completely undocumented features / components, but this is getting better and you can see a &lt;a href=&quot;https://twitter.com/reactjs/status/743937815705059330&quot;&gt;big investment&lt;/a&gt; happening in this area.&lt;/p&gt;
&lt;h2&gt;Why React Native worked well for us&lt;/h2&gt;
&lt;p&gt;The main driver for us at SoundCloud to use React Native was the lack of enough mobile engineers to start development on a new mobile app. This is probably one of the main arguments for React Native if you are in a similar position of having more web engineers than mobile engineers. While this situation still holds true for us to some extent we have come to appreciate React Native for many more reasons than its JavaScript roots.&lt;/p&gt;
&lt;p&gt;It is quite obvious that a better developer experience was a top priority for Facebook when creating React Native and it really shows. Automatically live reloading your app when your code changes massively shortens the feedback loop and using Chrome to debug your application feels very much at home to any web developer. If you also happen to use Redux and a logger middleware, knowing what is going on in your app at any point is very straightforward. It is a very comforting feeling to know the tools are there for you when you need them and its effect on developer happiness, development speed and confidence can not be understated. If you need one reason to give it a try, this is it. Happy developers and a faster turnaround are hard to beat. Product managers, decision makers &amp;#x26; designers listen up, I’m talking to you here :)&lt;/p&gt;
&lt;p&gt;But there’s more: the open source community provides many ready-to-use libraries and components and is very responsive to React Native’s changes, questions and improvement suggestions. It feels good to be part of this positive community.&lt;/p&gt;
&lt;p&gt;Another huge benefit that we only recently started to explore is the cross platform nature of React Native. Facebook mentions &lt;a href=&quot;https://code.facebook.com/posts/1189117404435352/react-native-for-android-how-we-built-the-first-cross-platform-react-native-app/&quot;&gt;about 85 percent code reuse&lt;/a&gt; between their iOS and Android implementation of Ads Manager, and with the right app structure that number is very realistic to expect. This means that once you have the structure there, building a new feature for your app on both platforms will only have an additional cost of 10 - 20 percent instead of 100 percent. That’s huge.&lt;/p&gt;
&lt;p&gt;The ability to update your application on the fly, as mentioned earlier, also allows immediate bug fixes, a/b tests and the warm, cozy feeling that you can push an update whenever you want: a dream come true in the mobile development space (as long as Google and Apple allow it, which is always a little hard to predict).&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Based on the great experience we had with React Native, we plan on using it in more places at SoundCloud. Right now there is a team of designers working on a replacement for our prototyping / user-testing application written in React Native. Our designers found it much simpler to work on a React-based application than on a native application and are capable of building the application by themselves without frequent developer input. They are also working on bringing this application to Android to increase the pool of users that we can test with and to test Android-specific features.&lt;/p&gt;
&lt;p&gt;Speaking of Android, we have spent some time to prepare the app for running on Android and got a stripped-down version of the app working in just a couple of &lt;a href=&quot;https://developers.soundcloud.com/blog/stop-hacker-time&quot;&gt;hacker time&lt;/a&gt; sessions. We are currently investigating further steps in that direction, such as replacing the current Android Pulse app with the React Native version. Another area we are looking into is integrating React Native views inside our native applications. We’re looking forward to these experiments and hope they go as well as this one.&lt;/p&gt;
&lt;p&gt;Get in touch with us&lt;/p&gt;
&lt;p&gt;Jan Monschke&lt;br&gt;
Peter Minarik &lt;a href=&quot;https://twitter.com/pietropizzi&quot;&gt;@pietropizzi&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Removal of Groups endpoints]]></title><description><![CDATA[As well as adding new features and updates, we review existing features to see if they’re still beneficial to the community. As we dug into…]]></description><link>https://developers.soundcloud.com/blog/removal-of-groups-endpoints</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/removal-of-groups-endpoints</guid><pubDate>Mon, 01 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As well as adding new features and updates, we review existing features to see if they’re still beneficial to the community.&lt;/p&gt;
&lt;p&gt;As we dug into the best ways for curators to connect with artists and fans, we found that Groups aren’t working as well as reposts and curated playlists.&lt;/p&gt;
&lt;p&gt;With that in mind, we’ve decided to phase out Groups on Monday, August 22nd to make room for future updates.&lt;/p&gt;
&lt;p&gt;From this date, all Groups-related GET requests will no longer return results, and PUT requests will not persist any changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If your application relies on Groups, please update it by August 22nd, 2016.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Synchronous communication for microservices: current status and learnings]]></title><description><![CDATA[Since we started breaking our monolith and introduced a microservices architecture we rely a lot on synchronous request-response style communication.  In this blog post we’ll go over our current status and some of the lessons we learned.]]></description><link>https://developers.soundcloud.com/blog/synchronous-communication-for-microservices-current-status-and-learnings</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/synchronous-communication-for-microservices-current-status-and-learnings</guid><pubDate>Wed, 27 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since we started &lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-2-breaking-the-monolith&quot;&gt;breaking our monolith&lt;/a&gt; and introduced a microservices architecture we rely a lot on synchronous request-response style communication.  In this blog post we’ll go over our current status and some of the lessons we learned.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Finagle and jvmkit&lt;/h2&gt;
&lt;p&gt;Most of our microservices are built on top of &lt;a href=&quot;https://github.com/twitter/finagle&quot;&gt;Finagle&lt;/a&gt; and are written in Scala. Finagle is a highly extensible and protocol agnostic RPC framework developed by Twitter.&lt;/p&gt;
&lt;p&gt;Finagle clients and servers use non blocking I/O on top of Netty. Non blocking I/O improves our scalability since we have a lot of scatter-gather style microservices or microservices that are I/O bound on external resources like databases.&lt;/p&gt;
&lt;p&gt;Finagle is also highly configurable and extensible which means we can tailor it to work well with our infrastructure.&lt;/p&gt;
&lt;p&gt;To make it easy for developers to build microservices that play nicely with the SoundCloud infrastructure and conventions we have an internal library called jvmkit which is maintained by our Core Services team. Some of the functionality jvmkit provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wrappers around Finagle HTTP, memcached and MySQL with reasonable default configurations and integration with &lt;a href=&quot;https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud&quot;&gt;Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DNS based service discovery implementation&lt;/li&gt;
&lt;li&gt;HTTP request routing on top of Finagle&lt;/li&gt;
&lt;li&gt;JSON parsing support for request / response entities&lt;/li&gt;
&lt;li&gt;Logging support according to our standards &lt;/li&gt;
&lt;li&gt;API support for rollout our feature toggle system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Teams are responsible from development till running their microservices in production so they can decide not to use jvmkit as long as they stick to our standard protocol. In practice however most of our microservices do use jvmkit. By using jvmkit teams have more time to focus on delivering business features.&lt;/p&gt;
&lt;h2&gt;HTTP/1.1 + JSON&lt;/h2&gt;
&lt;p&gt;Almost all our services use HTTP/1.1 + JSON for communication.  &lt;/p&gt;
&lt;p&gt;The advantage of HTTP + JSON is that it is supported everywhere so we don’t lock ourselves into a specific technology or programming language.  &lt;/p&gt;
&lt;p&gt;There is very good tooling support for HTTP + JSON which makes it easy to debug and interact with our services.  &lt;/p&gt;
&lt;p&gt;The HTTP method indicates useful properties of requests. It indicates if a request is idempotent and safe (read-only). These properties tell us if requests can be safely retried or cached. We found this useful when replaying requests to test new revisions of a service. Indication of these properties are missing from other protocols we looked into.&lt;/p&gt;
&lt;p&gt;Of course HTTP + JSON results in more overhead compared to other more optimized protocols.  We have some services using Finagle &lt;a href=&quot;http://twitter.github.io/finagle/guide/Protocols.html&quot;&gt;ThriftMux&lt;/a&gt; and have thought about making ThriftMux the recommended protocol but we stepped away from that idea.  ThriftMux is not ubiquitous and would lock us into Finagle if we want to get all the benefits.&lt;/p&gt;
&lt;h2&gt;Client side service discovery and load balancing&lt;/h2&gt;
&lt;p&gt;When we started using microservices we used separate load balancer processes as entry points for our services.  With the increase in traffic we ran into failing load balancer processes which caused several outages. Our load balancers were single points of failure.&lt;/p&gt;
&lt;p&gt;These outages made us move to client side service discovery and load balancing. We wrote a Java service discovery implementation that queries DNS SRV records to get the instances for a given service.  Writing it in Java means it can be integrated in any JVM based language and framework that supports Service Discovery.  We obviously integrated it in Finagle by using the Finagle Resolver api. Having support in Finagle means we have client side service discovery and load balancing support for HTTP, ThriftMux, MySQL and memcached.&lt;/p&gt;
&lt;h2&gt;Resilience against failures&lt;/h2&gt;
&lt;p&gt;When investigating the root cause of some of our outages we realized that we had to invest more in improving our resilience against failures.  It quickly showed how important it is to use a mature library or system like Finagle which is already equipped with important features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timeouts: every request across the network needs realistic timeouts. This protects you from issues with the systems you depend on.&lt;/li&gt;
&lt;li&gt;Transport and application level Circuit Breakers: supports ignoring hosts that become unavailable or unhealthy with automatic recovery when they become healthy again. It protects you from issues with systems you depend on and allows them to recover.&lt;/li&gt;
&lt;li&gt;Conditional retries: retrying can be effective but you shouldn’t retry unconditionally as this can result in retry storms that can overload the systems you depend on. Retry budgets allow you to configure a percentage of requests that can be retried on top of a minimum number within a time frame.&lt;/li&gt;
&lt;li&gt;Limiting concurrent requests: If you know the capacity your service can handle you can protect it from overload by limiting the maximum number of concurrent requests and waiters (queue size).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using and configuring these features have allowed us to better deal with failures which are inevitable in a distributed system like a microservices architecture.&lt;/p&gt;
&lt;p&gt;We also introduced automated integration tests for jvmkit which show and document how our systems will react to failures. These tests also notify us about potential behavioural changes when upgrading Finagle.&lt;/p&gt;
&lt;h2&gt;No more client libraries&lt;/h2&gt;
&lt;p&gt;When we started implementing microservices teams who implemented a service typically also provided a client library that could be used by other teams to access their service.  Those client libraries were implemented in the same programming language as the service and were hiding network communication and serializing / deserializing of requests and responses behind an easy to use api. The idea was that the code to access a service only needed to be written once and could be re-used by many.&lt;/p&gt;
&lt;p&gt;After a while we noticed the client libraries approach also brings a lot of disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A client library brings in dependencies. It might bring in a HTTP library, JSON parsing library etc. This might result in conflicts with the libraries already used by your service. If you depend on more than 1 client library this problem only increases resulting in unnecessary dependencies and conflicts between teams.&lt;/li&gt;
&lt;li&gt;A client library might become bloated with custom logic. Using the client library might be the only way to successfully use the service and prevents it from being used by applications using other technologies. This imposes the use of a programming language and technology on other teams.&lt;/li&gt;
&lt;li&gt;A client library can have default error handling logic which is probably not a good fit for all use cases. Users of a service should be aware of the error scenario’s that can happen and act upon them in the best way for their use case.&lt;/li&gt;
&lt;li&gt;A client library will likely parse all returned fields of a response even if the client is only interested in a subset.  This not only might have performance implications but also violates the tolerant reader principle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We think these disadvantages far outweigh the benefits. The use of client libraries results in increased coupling and dependencies. Instead of publishing clients we encourage teams to publish their API’s. Consumers write their own clients in a way they find most appropriate using their technology of choice.&lt;/p&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;We hope to introduce Consumer Driven Contract tests soon. These tests will catch breaking changes between services and consumers during build time. Some teams are currently working on proof of concepts and are building out and validating the process.&lt;/p&gt;
&lt;p&gt;A logical step for us would be to move from HTTP/1.1 to HTTP/2 as our default protocol. This would improve latency while sticking with a widely supported protocol without technology or programming language lock-in.&lt;/p&gt;
&lt;p&gt;We might also replace JSON with a more efficient serialization protocol like &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;protocol buffers&lt;/a&gt;. This would further improve performance and should result in less handwritten serialization and deserialization code but this still needs more experimentation and investigation.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Prometheus has come of age – a reflection on the development of an open-source project]]></title><description><![CDATA[On Monday this week, the Prometheus authors have
released
version 1.0.0
of the central component of the Prometheus monitoring and alerting system, the
Prometheus server. (Other
components will follow suit over the next months.) This is a major milestone
for the project. Read more about it on the
Prometheus blog,
and check out the
announcement of the
CNCF, which has recently accepted Prometheus as a hosted
project.]]></description><link>https://developers.soundcloud.com/blog/prometheus-has-come-of-age-a-reflection-on-the-development-of-an-open-source-project</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/prometheus-has-come-of-age-a-reflection-on-the-development-of-an-open-source-project</guid><pubDate>Tue, 19 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On Monday this week, the &lt;a href=&quot;https://prometheus.io&quot;&gt;Prometheus&lt;/a&gt; authors have
released
&lt;a href=&quot;https://github.com/prometheus/prometheus/releases/tag/v1.0.0&quot;&gt;version 1.0.0&lt;/a&gt;
of the central component of the Prometheus monitoring and alerting system, the
&lt;a href=&quot;https://github.com/prometheus/prometheus&quot;&gt;Prometheus server&lt;/a&gt;. (Other
components will follow suit over the next months.) This is a major milestone
for the project. Read more about it on the
&lt;a href=&quot;https://prometheus.io/blog/2016/07/18/prometheus-1-0-released/&quot;&gt;Prometheus blog&lt;/a&gt;,
and check out the
&lt;a href=&quot;https://cncf.io/news/blogs/2016/07/prometheus-10-here&quot;&gt;announcement&lt;/a&gt; of the
&lt;a href=&quot;http://cncf.io&quot;&gt;CNCF&lt;/a&gt;, which has recently accepted Prometheus as a hosted
project.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;In just a few years, Prometheus has gone a long way from its beginnings as a
pet project of Matt T. Proud and Julius Volz to a widely adopted monitoring
system with an amazingly active community of more than 300 contributors and a
user-base spanning many different organizations, including companies like
&lt;a href=&quot;https://www.coreos.com/&quot;&gt;CoreOS&lt;/a&gt;,
&lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt;,
&lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt;, &lt;a href=&quot;http://www.ericsson.com/&quot;&gt;Ericsson&lt;/a&gt;,
&lt;a href=&quot;https://www.percona.com/&quot;&gt;Percona&lt;/a&gt;, and
&lt;a href=&quot;https://www.weave.works/&quot;&gt;Weaveworks&lt;/a&gt;. A pivotal midpoint in the history of
the project was the public announcement in January 2015 in an
&lt;a href=&quot;https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud&quot;&gt;earlier backstage blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this article, I would like to reflect a bit on the childhood of Prometheus
at SoundCloud, and on the benefits a company can gain from developing projects
in the open under a free software license.&lt;/p&gt;
&lt;p&gt;SoundCloud has a very liberal open-source policy. We have published a large
number of projects in our
&lt;a href=&quot;https://github.com/soundcloud/&quot;&gt;GitHub organization&lt;/a&gt;. Matt and Julius, who had
joined SoundCloud as engineers back in 2012, knew from the beginning that
Prometheus would have a much larger scope than any of those projects. When they
suggested that SoundCloud should invest in the development of a completely new
monitoring system, the resistance they had to overcome was not so much related
to the open-source character of the project but to its scope. After all, why
should a music-streaming company with a relatively small engineering force dare
to fundamentally change the way open-source monitoring is done? It was a bold
move, indeed. But it was also the time of
&lt;a href=&quot;https://twitter.com/search?q=%23monitoringsucks&quot;&gt;#monitoringsucks&lt;/a&gt;, and no
open-source effort in sight to make a real dent. So we took the torch and went
on our Promethean mission, cautiously avoiding the eagles.&lt;/p&gt;
&lt;p&gt;In hindsight, we know that it has paid off. For one, SoundCloud has just
finished the quarter with the highest availability in the history of the
company — which was also the quarter with the most features and the highest
complexity the site ever had. This tremendous accomplishment would have been
impossible without a novel way of monitoring our growing microservice
architecture — the way enabled by Prometheus.&lt;/p&gt;
&lt;p&gt;As the main source of early contributions to the project, SoundCloud was also
the earliest adopter. A nice strategic technical advantage, which – naively
thought – could have been so much larger if Prometheus had been developed in
secret.  However, that would ignore the overwhelming advantages of an
open-source development. First and foremost, without the countless
contributions from the community, Prometheus would not be what it is today, and
the technical advantage would be much smaller. Kickstarting a successful
open-source project that is then joined by many other parties is a good way for
a small company to get something going it could never have accomplished on its
own. The SoundCloud open-source policy states “free bug fixes from the
community” as one of the key benefits of open-sourcing projects. In the case of
Prometheus, that’s quite an understatement, to say the least.&lt;/p&gt;
&lt;p&gt;Tech credibility and the resulting impact on recruiting is another important
benefit. Excellent engineers like to work for companies with excellent
projects, and they like to work on those projects in the open, where they
benefit a whole community and provide visibility for the engineers, too. And
then there is a perhaps surprising side-effect we can observe by now with
Prometheus: If an open-source project is popular, some of your new-hires will
already be familiar with it, resulting in a smoother and shorter onboarding.&lt;/p&gt;
&lt;p&gt;Prometheus has grown up to a real titan. We are eagerly looking forward to the
further evolution of the project and to what humankind will do with the fire
that was given to them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Björn is a production engineer at SoundCloud. He joined the company and the
Prometheus project in 2013 and has been a Prometheus core developer ever
since.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building radio stations at SoundCloud]]></title><description><![CDATA[Over the last 100 years we have dialed into radio stations at home, on the road, or in the office to access a curated mix of top hits delivered to us by our favorite DJ. With more and more of our daily activities taking place online, we find our source of music now comes from a mix of our mobile phones, our desktop computers and the radio - and is available to us whenever we need it.
The amount of music available today is endless. The music scene has never been so vibrant. A proliferation of production tools at low cost and the ease at which an artist can share their sounds, gives everyone the opportunity to share their work at any time, with only a few clicks. Keeping this in mind, we set out to determine: How can we bring a radio like experience to SoundCloud that would enable you to tune-in and lean back?]]></description><link>https://developers.soundcloud.com/blog/building_radio_stations_at_soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building_radio_stations_at_soundcloud</guid><pubDate>Tue, 05 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the last 100 years we have dialed into radio stations at home, on the road, or in the office to access a curated mix of top hits delivered to us by our favorite DJ. With more and more of our daily activities taking place online, we find our source of music now comes from a mix of our mobile phones, our desktop computers and the radio - and is available to us whenever we need it.
The amount of music available today is endless. The music scene has never been so vibrant. A proliferation of production tools at low cost and the ease at which an artist can share their sounds, gives everyone the opportunity to share their work at any time, with only a few clicks.&lt;/p&gt;
&lt;p&gt;Keeping this in mind, we set out to determine: How can we bring a radio like experience to SoundCloud that would enable you to tune-in and lean back? &lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;The lean back experience&lt;/h2&gt;
&lt;p&gt;We consider the lean back experience those moments when you need music but don’t have the time to curate or choose something specific - at the gym, during your commute or those moments in the office when you need something to help your through the day. &lt;/p&gt;
&lt;h2&gt;The secret sauce for a successful radio station&lt;/h2&gt;
&lt;p&gt;Imagine a radio station that jumps from a jazz instrumental to the new EDM ‘floorkiller’. That just wouldn’t work. &lt;/p&gt;
&lt;p&gt;To create an enjoyable radio experience, it’s important the experience maintains consistency and context for the user. To create this experience on SoundCloud, we considered the following key ingredients that have lasted the test of time for radio as we built our latest discovery feature, Artist Stations. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Novelty.&lt;/strong&gt;
You want to hear something new. That’s why the mainstream radio stations revamp their playlists periodically, usually every month, so listeners return to hear the newest hits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Familiarity.&lt;/strong&gt;
At the same time, you also want to hear something that’s familiar. You know the lyrics, or it’s an artist you like, or simply you heard that track before. Music often it closely tied with life events and experiences and hearing a familiar song can trigger memories that make us smile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context.&lt;/strong&gt;
Since we are in a lean back experience mode, our brain works better when changes in external factors are minimal.
Imagine a radio station that jumps from a country song to the new EDM ‘floorkiller’.
That would not work, that’s why providing context and maintaining that context consistently is crucial for every radio experience. &lt;/p&gt;
&lt;h2&gt;The future of radio stations.&lt;/h2&gt;
&lt;p&gt;Two weeks ago we launched &lt;strong&gt;&lt;a href=&quot;https://developer.soundcloud.com/blog/tracks&quot;&gt;Suggested Tracks&lt;/a&gt;&lt;/strong&gt; on Mobile and &lt;strong&gt;&lt;a href=&quot;https://soundcloud.com/discover&quot;&gt;Discover&lt;/a&gt;&lt;/strong&gt; tab on Web, the first of a series of features built on top of our machine learning infrastructure.&lt;/p&gt;
&lt;p&gt;We strongly believe the content available on our platform is unique, and so are the millions of listeners that tune-in on SoundCloud every day. Each of them has their own specific taste and  sense of what is novel and familiar.&lt;/p&gt;
&lt;p&gt;By unlocking our catalog to algorithms trained to behave as radio curators, we can get the best of both worlds — an exceptional  traditional radio experience coupled with an endless catalog of content available to you with just a few taps on your screen. &lt;/p&gt;
&lt;p&gt;This is the ultimate democratization of the radio experience. Our machines are composing a radio experience by detecting what is &lt;em&gt;new&lt;/em&gt;, what is &lt;em&gt;familiar to the user&lt;/em&gt;, and what is &lt;em&gt;trending&lt;/em&gt; or &lt;em&gt;popular among listeners&lt;/em&gt; in the SoundCloud community. All while keeping the context consistent with the Station you’ve selected, and your personal taste profile.&lt;/p&gt;
&lt;h2&gt;Continuous improvement&lt;/h2&gt;
&lt;p&gt;We are extremely excited to share this feature with you, and I hope you’ll enjoy it as much as we do. Track and Artist Stations are currently available on mobile, and will be coming to web in late summer.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/a66c8/suggestedStations.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.313932980599645%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAGQAAAQUAAAAAAAAAAAAAAAAAAAECAwQF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB3oLCDgD/xAAZEAABBQAAAAAAAAAAAAAAAAACAQMSIDH/2gAIAQEAAQUCXAckVP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAAIDAAAAAAAAAAAAAAAAAAABERIg/9oACAEBAAY/AiKtZ//EABkQAAIDAQAAAAAAAAAAAAAAAAERABAhMf/aAAgBAQABPyE0R6oRzK9o3//aAAwDAQACAAMAAAAQEA//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAcEAEAAQUBAQAAAAAAAAAAAAABEQAQQWFxITH/2gAIAQEAAT8QQwUJgzU5keg5rdvijHbf/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;suggestedStations&quot;
        title=&quot;suggestedStations&quot;
        src=&quot;/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/a296c/suggestedStations.jpg&quot;
        srcset=&quot;/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/f544b/suggestedStations.jpg 200w,
/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/41689/suggestedStations.jpg 400w,
/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/a296c/suggestedStations.jpg 800w,
/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/c35de/suggestedStations.jpg 1200w,
/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/8179c/suggestedStations.jpg 1600w,
/blog/static/4a3d759a8b8db7b67c1f9fd4931419e9/a66c8/suggestedStations.jpg 2835w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As our algorithms are constantly in evolution and our data models continue to refine themselves, the more you listen, the better suggestions you will receive and the more input we will receive, the better stations perform for everyone on SoundCloud. Stay tuned for more iterations of stations and more ways to discover the more than 125 million tracks available on SoundCloud.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Can a machine surprise you? We believe so.]]></title><description><![CDATA[With more than 125 million tracks from over 12 million creators heard each month on our platform, SoundCloud is uniquely positioned to offer listeners a full spectrum of music discovery.  Classic hits, the latest releases, gems from underground talent and the best of what’s up-and-coming – all in one place. How can you make great content discoverable and available at ease? How can you create a unique experience for every single user?]]></description><link>https://developers.soundcloud.com/blog/introducing_suggested_tracks</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing_suggested_tracks</guid><pubDate>Tue, 21 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With more than 125 million tracks from over 12 million creators heard each month on our platform, SoundCloud is uniquely positioned to offer listeners a full spectrum of music discovery. &lt;/p&gt;
&lt;p&gt;Classic hits, the latest releases, gems from underground talent and the best of what’s up-and-coming – all in one place.&lt;/p&gt;
&lt;p&gt;How can you make great content discoverable and available at ease? How can you create a unique experience for every single user?&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;a href=&quot;https://soundcloud.com/discover&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.380457380457386%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAemoapH/xAAaEAADAAMBAAAAAAAAAAAAAAAAAQIDERMx/9oACAEBAAEFAvCd0c4KxyzSlYW6j//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB8QAAIBAgcAAAAAAAAAAAAAAAABAiFBAxIjMlFhkf/aAAgBAQAGPwKWnFO3Ys+HE2R8Kq5Jrgqf/8QAGxAAAwACAwAAAAAAAAAAAAAAAAERITFRcfH/2gAIAQEAAT8hVR30sRQedOHgBG8uw1ZjFQ9dP//aAAwDAQACAAMAAAAQKw//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPxBZ/8QAFhEBAQEAAAAAAAAAAAAAAAAAAREQ/9oACAECAQE/ECNz/8QAHRABAAICAgMAAAAAAAAAAAAAAQARMUEhUXGR8P/aAAgBAQABPxAiSLSxXV9ajwSKSha9y0H4PERCeRXMqLfBzH1QZdan/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;personalized recommendations&quot;
        title=&quot;personalized recommendations&quot;
        src=&quot;/blog/static/6834021f21553f7ea0a8a88ab6794bda/a296c/personalized_recommendations.jpg&quot;
        srcset=&quot;/blog/static/6834021f21553f7ea0a8a88ab6794bda/f544b/personalized_recommendations.jpg 200w,
/blog/static/6834021f21553f7ea0a8a88ab6794bda/41689/personalized_recommendations.jpg 400w,
/blog/static/6834021f21553f7ea0a8a88ab6794bda/a296c/personalized_recommendations.jpg 800w,
/blog/static/6834021f21553f7ea0a8a88ab6794bda/c35de/personalized_recommendations.jpg 1200w,
/blog/static/6834021f21553f7ea0a8a88ab6794bda/fa43f/personalized_recommendations.jpg 1443w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Beginning today, we’re introducing &lt;strong&gt;Suggested Tracks&lt;/strong&gt;, on web and on mobile, to bring listeners even more ways to discover music on SoundCloud. &lt;/p&gt;
&lt;h2&gt;Digital Serendipity&lt;/h2&gt;
&lt;p&gt;Months before Suggested Tracks developed to what it is today, SoundCloud’s recommendation team set out to answer one simple: &lt;/p&gt;
&lt;center&gt;**_Can a machine surprise us?_**&lt;/center&gt;
&lt;p&gt;For quite some time, the SoundCloud Core Data Science team has been working on state of the art machine learning projects to help its users discover new content. The idea of surprise in relation to discovery was a new concept. In order to further explore and develop this idea, it required a very different approach and perspective. &lt;/p&gt;
&lt;p&gt;We concluded that music generates emotion, and therefore we would need to view the element of surprise from the perspective of emotion. &lt;/p&gt;
&lt;p&gt;We knew if we really wanted to infuse the element of surprise into music discovery, we had to push our systems to understand the emotional element of our users.&lt;/p&gt;
&lt;p&gt;It was about taking traditional ways of discovering music to a new level. To a place where feelings were taken into consideration, and the “how” along with the “what,” was addressed in the computational effort.&lt;/p&gt;
&lt;p&gt;Overall, we wanted to create a serendipity effect. The moment where you find something new, something engaging that makes you move naturally from a passive listening experience to an active state of mind. The moment where….&lt;/p&gt;
&lt;center&gt;*The bass kicks in, you’re 30 seconds into the song, it’s a catchy groove. It sounds familiar, but you’ve never heard the track before. At 45 seconds, you have already liked the track by pushing our iconic heart button, and by the time the lead singer gets to the bridge, you have already shared the track on your favorite social network.* &lt;/center&gt;
&lt;p&gt;It’s a eureka moment. You just found a new, undiscovered track none of your friends know. And you love it. You feel it is the right track at the right moment. And it feels good.&lt;/p&gt;
&lt;p&gt;What if that track was recommended by a machine? An extremely sophisticated algorithm that just picked the track for you with the simple goal of surprising you?  &lt;/p&gt;
&lt;p&gt;This is what we call &lt;em&gt;Digital Serendipity&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;It’s all about you&lt;/h2&gt;
&lt;p&gt;So can it be true? Can a machine know you that well? Not just recommending good tracks but amazing tracks? &lt;/p&gt;
&lt;p&gt;We believe the answer is yes. The very same machine has been learning from you all this time. It’s as if you had a skilled musicologist right next to you, carefully listening to what you’ve been playing and interacting with, all this time. &lt;/p&gt;
&lt;p&gt;With Suggested Tracks, this musicologist is not only a passive observer but a proactive entity. The more you listen, like, repost and engage with a track, the better our artificial agent gets to know you and your musical taste. The more you interact, the more it learns and perfects itself day after day.&lt;/p&gt;
&lt;h2&gt;Micro Moments&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Suggested Tracks&lt;/strong&gt; is not a recurrent playlist or a weekly chart. Our new feature updates more frequently than any other service out there so you always have fresh new content for your listening pleasure. Now all of life’s micro moments will have the perfect soundtrack. &lt;/p&gt;
&lt;p&gt;Enough about Suggested Tracks, go and &lt;strong&gt;&lt;a href=&quot;https://soundcloud.com/discover&quot;&gt;try it out&lt;/a&gt;&lt;/strong&gt;! &lt;/p&gt;</content:encoded></item><item><title><![CDATA[Data pipelines with Apache Crunch and Java 8]]></title><description><![CDATA[With Java 8 now in the mainstream, Scala and Clojure are no longer the only choices to develop readable, functional code for big data technology on the JVM. In this post we see how SoundCloud is leveraging Apache Crunch and the new Crunch Lambda module to do the high-volume data processing tasks which are essential at early stages in our batch data pipeline efficiently, robustly and simply in Java 8.]]></description><link>https://developers.soundcloud.com/blog/data-pipelines-apache-crunch-java-8</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/data-pipelines-apache-crunch-java-8</guid><pubDate>Wed, 01 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With Java 8 now in the mainstream, Scala and Clojure are no longer the only choices to develop readable, functional code for big data technology on the JVM. In this post we see how SoundCloud is leveraging Apache Crunch and the new Crunch Lambda module to do the high-volume data processing tasks which are essential at early stages in our batch data pipeline efficiently, robustly and simply in Java 8.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Hadoop MapReduce is not dead&lt;/h2&gt;
&lt;p&gt;It may not be cool or trendy any more, but Hadoop MapReduce (based on the Google &lt;a href=&quot;http://research.google.com/archive/mapreduce.html&quot;&gt;MapReduce&lt;/a&gt; paper from 2004) still has an important role to play in today’s world of big data technology. Whilst &lt;a href=&quot;http://spark.apache.org/&quot;&gt;Spark&lt;/a&gt; captures people’s imagination as a replacement for it, with its interactive response times and suitability for machine learning, it’s not a silver bullet to every batch processing requirement. It can be unstable at times, and uses far more cluster resources (especially memory) than its simpler predecessor. Spark is used in the application domain for products like search, recommendations and classification tasks, but for preparing our datasets for downstream usage, MapReduce still has a huge part to play. It still provides a robust and relatively reliable platform which is ideally suited to transforming, cleaning and partitioning our huge data volumes ready for use by the insights and product engineering teams.&lt;/p&gt;
&lt;h2&gt;Type safety is crucial&lt;/h2&gt;
&lt;p&gt;Working with data at this end of the pipeline has different developer requirements too. Whilst at the application end of the pipeline the workflows are often based on picking out individual fields from the data and aggregating them in various ways (such functionality is well provided on MapReduce by &lt;a href=&quot;http://hive.apache.org&quot;&gt;Hive&lt;/a&gt; and &lt;a href=&quot;https://github.com/twitter/scalding&quot;&gt;Scalding&lt;/a&gt;), at early stages of our pipelines we are working with complete objects with strict schemas to adhere to, so trying to work with a fields-based API would be very dangerous.&lt;/p&gt;
&lt;p&gt;Event data at SoundCloud is stored in the structured binary &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;Protobuf&lt;/a&gt; format, so being able to work directly with Java Protobuf objects rather than an API-specific view saves us lots of mapping code, and prevents errors if we were to get it wrong.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://crunch.apache.org&quot;&gt;Crunch&lt;/a&gt; provides a useful abstraction for this kind of workload, because it works with type safety and structured records at its heart. It’s based on Google’s &lt;a href=&quot;http://research.google.com/pubs/pub35650.html&quot;&gt;FlumeJava&lt;/a&gt; paper, and provides a general-purpose boilerplate-free developer-friendly typesafe Java API for developing data jobs to run on Hadoop MapReduce.&lt;/p&gt;
&lt;p&gt;Crunch code is relatively simple to understand, as this classic “word count” example demonstrates:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Pipeline&lt;/span&gt; crunch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MRPipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WordCountJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
crunch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;From&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;textFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/path/on/hdfs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallelDo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DoFn&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Emitter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; word&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;word&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallelDo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapFn&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; wordCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wordCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; wordCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;To&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;textFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/path/to/output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
crunch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Reading from top to bottom, we see that we’re reading a collection of Strings from a text file, then applying a &lt;code class=&quot;language-text&quot;&gt;DoFn&lt;/code&gt; which tokenizes each line into words and outputs each word. We then use the built-in &lt;code class=&quot;language-text&quot;&gt;count()&lt;/code&gt; method to count occurrences, then we apply a &lt;code class=&quot;language-text&quot;&gt;MapFn&lt;/code&gt; operation which formats those pairs back into &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt; lines and writes them to a text file. The execution is lazy, so &lt;code class=&quot;language-text&quot;&gt;crunch.done()&lt;/code&gt; tells Crunch to run everything.&lt;/p&gt;
&lt;p&gt;Unfortunately this code snippet suffers in the same way that a lot of functional-style Java code has suffered with over the years. Those anonymous inner classes are ugly and add a huge amount of redundant information, making it difficult to read and irritating to write.&lt;/p&gt;
&lt;h2&gt;Java 8 to the rescue&lt;/h2&gt;
&lt;p&gt;Luckily, Java 8 gave us the gift of Lambda expressions, method references and &lt;code class=&quot;language-text&quot;&gt;Stream&lt;/code&gt;s; all of which are intended specifically to make functional programming in Java more user friendly. With the rest of our code starting to look more functional and concise, the Crunch parts of the code started to look even more archaic then they did before, so I set about building a thin Java-8-friendly wrapper around the Crunch API to make the situation better. That wrapper became Crunch Lambda, which as of version 0.14.0 is now part of the main Crunch project (in the crunch-lambda submodule, artifact org.apache.crunch:crunch-lambda:0.14.0)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mini side-note about Crunch versions&lt;/em&gt;: Some people are worried about using Crunch because it’s not version “1” yet, but Crunch version numbers are incredibly conservative. We and many other large organisations have been using Crunch in production for some time and we consider it very stable.&lt;/p&gt;
&lt;h2&gt;Crunch Lambda&lt;/h2&gt;
&lt;p&gt;Let’s revisit the above example with Crunch Lambda instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Pipeline&lt;/span&gt; crunch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MRPipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WordCountJobLambda&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Lambda&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  crunch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;From&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;textFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/path/on/hdfs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wc &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; wc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; wc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;write  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;To&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;textFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/path/to/output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
crunch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See how the type information is only required once now for each transformation and there’s no distracting superfluous information.&lt;/p&gt;
&lt;h2&gt;What about the real world?&lt;/h2&gt;
&lt;p&gt;Let’s take a look at how we’re using Crunch at SoundCloud in one of our data jobs.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;EventStatsJob&lt;/em&gt; counts the number of events that have successfully made it through to the end of the core event pipeline and splits it out by application and event type. We use this data to compare against data collected in real-time as events are first collected to check for any anomalies in our computation.&lt;/p&gt;
&lt;p&gt;Once it is collected the data, it emits it as data points to a &lt;a href=&quot;http://prometheus.io&quot;&gt;Prometheus&lt;/a&gt; PushGateway so they can be graphed over time.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Pipeline&lt;/span&gt; crunch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MRPipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EventStatsJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Prometheus setup&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;PushGateway&lt;/span&gt; pushGateway &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PushGateway&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PUSH_GATEWAY_ADDRESS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Gauge&lt;/span&gt; gauge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Gauge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event_count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;labelNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event_type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Read events from Protobuf SequenceFile&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;LTable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; events &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
  &lt;span class=&quot;token class-name&quot;&gt;Lambda&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    crunch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;From&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sequenceFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        dataPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Writables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;longs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;PTypes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;protos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WritableTypeFamily&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Count events by their stats key and set Gauge values&lt;/span&gt;
events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;materialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statsRecord &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
          gauge&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                  statsRecord&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  statsRecord&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statsRecord&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Send the gauge contents to the PushGateway&lt;/span&gt;
pushGateway&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gauge&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;event_stats_job&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we see how simply Crunch code can interoperate with normal Java code. It calls out to a nearby normal method to perform a distributed map operation (via the &lt;code class=&quot;language-text&quot;&gt;StatsKey::fromEvent&lt;/code&gt; method reference) and then brings the results back locally via the &lt;code class=&quot;language-text&quot;&gt;materialize()&lt;/code&gt; method to operate on as a normal Java &lt;code class=&quot;language-text&quot;&gt;Stream&lt;/code&gt;. Note that we didn’t even specify an output file, because they only output from this job is to feed metrics to Prometheus; Crunch handles all the temporary files and IO for you.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;StatsKey&lt;/code&gt; custom type is a simple immutable wrapper for the &lt;code class=&quot;language-text&quot;&gt;eventType&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;application&lt;/code&gt; (it has a few more fields in real life, but these are omitted here for brevity). We can easily work with this in Crunch by giving it a conversion mechanism in and out of a built-in Crunch type, as seen in the &lt;code class=&quot;language-text&quot;&gt;pType()&lt;/code&gt; method here:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; eventType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; eventType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; eventType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PType&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;derivedImmutable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token function&quot;&gt;mapFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token function&quot;&gt;mapFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token function&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatsKey&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This &lt;code class=&quot;language-text&quot;&gt;PType&lt;/code&gt; serialization/deserialization behaviour descriptor is provided built-in to Crunch for many serialization types, including Protobuf (as you can see with the &lt;code class=&quot;language-text&quot;&gt;PTypes.protos&lt;/code&gt; call above) and &lt;a href=&quot;http://avro.apache.org&quot;&gt;Avro&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;A more complex example - Creating toplists&lt;/h2&gt;
&lt;p&gt;As part of the Soundcloud Stats product, we present toplists to sound creators in different dimensions - for example “my most-favourited tracks” or “top countries where my sound is played”. This is the main part of the code to compute these:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;LTable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; summedBuckets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  events
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFactsFromEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;token function&quot;&gt;tableOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;longs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;groupByKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduceValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filterByValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Remove negative counts&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; summedBuckets
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remapDimensionKeys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;tableOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FactKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;groupByKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rowSet &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTopK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rowSet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;       
               &lt;span class=&quot;token function&quot;&gt;collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here you can see that we are doing some much more complex operations than in our previous example.&lt;/p&gt;
&lt;p&gt;Firstly we transform each events into a set of “facts”. A “sound played” event, for example, might generate several facts. One based on which country it occured in, one based on which creator published the sound, and so on. The &lt;code class=&quot;language-text&quot;&gt;createFactsFromEvent&lt;/code&gt; method takes an Event as its parameter, and returns a Java &lt;code class=&quot;language-text&quot;&gt;Stream&lt;/code&gt; of facts corresponding to each of these so-called “dimensions”. We group by the key (eg. ‘my&lt;em&gt;awesome&lt;/em&gt;sound’), dimension (eg. “country”) and its value (eg. “Sweden”) - together called a ‘bucket’ - and sum the Long values as a reduce operation. As some events (such as “sound un-favourited”) can cause a negative impact on the counts, we filter out any totals which are non-positive, as these tend to look strange to users.&lt;/p&gt;
&lt;p&gt;Following this, the &lt;code class=&quot;language-text&quot;&gt;remapDimensionKeys&lt;/code&gt; operation effectively moves the dimension value (eg. “Sweden”) from the key part of the record to the value part, so we’re left with a FactKey(“my&lt;em&gt;awesome&lt;/em&gt;sound”, “country”) and a TopListPair(“Sweden”, 581). We then &lt;code class=&quot;language-text&quot;&gt;groupByKey()&lt;/code&gt; with this new key to collect all the &lt;code class=&quot;language-text&quot;&gt;TopListPair&lt;/code&gt;s for each &lt;code class=&quot;language-text&quot;&gt;FactKey&lt;/code&gt;, then run them through the &lt;code class=&quot;language-text&quot;&gt;findTopK&lt;/code&gt; to obtain the K &lt;code class=&quot;language-text&quot;&gt;TopListPair&lt;/code&gt;s with the greatest count.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTopK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;SortedSet&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; set &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TreeSet&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
           &lt;span class=&quot;token class-name&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reversed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenComparing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TopListPair&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDimensionValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pair &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       set&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pair&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;set&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           set&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;set&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; set&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, there’s absolutely nothing unusual about this part of the computation. We use a standard streaming top-k algorithm to create the result set for the key.&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;Finally, one of the greatest advantages of using Crunch is how it facilitates easy local testing of your pipeline logic. A combination of an in-memory implementation of the &lt;code class=&quot;language-text&quot;&gt;Pipeline&lt;/code&gt; object &lt;a href=&quot;http://crunch.apache.org/apidocs/0.14.0/org/apache/crunch/impl/mem/MemPipeline.html&quot;&gt;MemPipeline&lt;/a&gt; and the fact that the API encourages using normal Java methods to compose your pipes (for simple low-level unit testing) means that it’s really easy to have confidence in your pipeline before it even makes it on to the cluster.&lt;/p&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;We’ve got our eyes firmly on the &lt;a href=&quot;http://beam.incubator.apache.org/&quot;&gt;Apache Beam&lt;/a&gt; project as it progresses. Inspired by work from FlumeJava, Crunch, Google DataFlow, Spark and Flink (and also running on at least the latter 3) it aims to generalise the expression of data pipelines like this away from Hadoop altogether and break the cycle of having to rewrite everything to keep up with the latest technology. This sounds like a big step in the right direction, but we’re confident writing new Crunch code now isn’t a waste of time either, as it looks very likely so far that translating from Crunch to Beam would be a relatively simple operation when the time comes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;David is a Data Engineer at SoundCloud and a committer to the Apache Crunch project. You can learn more by reading the &lt;a href=&quot;https://crunch.apache.org/user-guide.html&quot;&gt;Crunch User Guide&lt;/a&gt; and the &lt;a href=&quot;https://crunch.apache.org/apidocs/0.14.0/org/apache/crunch/lambda/Lambda.html&quot;&gt;Javadocs for Crunch Lambda&lt;/a&gt;. If you’re interested in working on problems like these, check our open positions on the &lt;a href=&quot;https://soundcloud.com/jobs/product_design_engineering&quot;&gt;SoundCloud Jobs page&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Playlist search returns only compact representations as of July 15, 2016]]></title><description><![CDATA[We previously introduced new playlist representations. The compact representation returns only the playlist itself without any of the tracks…]]></description><link>https://developers.soundcloud.com/blog/compact-playlist-search</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/compact-playlist-search</guid><pubDate>Fri, 13 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We previously introduced &lt;a href=&quot;https://dev.soundcloud.com/blog/representations&quot;&gt;new playlist representations&lt;/a&gt;. The compact representation returns only the playlist itself without any of the tracks. For most users, this is preferable – the compact representation significantly reduces the size of the JSON payload for large playlists and provides faster API response times.&lt;/p&gt;
&lt;p&gt;Beginning July 15, 2016, all queries using the &lt;code class=&quot;language-text&quot;&gt;q&lt;/code&gt; parameter will only return this compact representation for playlists.  Other playlist representations will no longer be available when using the &lt;code class=&quot;language-text&quot;&gt;q&lt;/code&gt; parameter. &lt;/p&gt;
&lt;p&gt;To fetch the track listings for one of the returned playlists, you can make an additional request to the &lt;code class=&quot;language-text&quot;&gt;/playlists/{id}&lt;/code&gt; resource by using the corresponding playlist ID.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If your application expects track listings from playlist search results, update your application by July 15, 2016.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Open-sourcing LightCycle for Android]]></title><description><![CDATA[Last week, we open-sourced LightCycle, an Android library that helps break logic out of  and  classes into small, self-contained components called LightCycles. Components that typically need to be aware of  and  lifecycle events include presenters, UI tracking code, input processors and more. We’ve been using LightCycle extensively in the SoundCloud Music & Audio and SoundCloud Pulse apps over the last year.]]></description><link>https://developers.soundcloud.com/blog/Open-sourcing-LightCycle-for-Android</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/Open-sourcing-LightCycle-for-Android</guid><pubDate>Mon, 21 Mar 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, we open-sourced LightCycle, an Android library that helps break logic out of &lt;code class=&quot;language-text&quot;&gt;Activity&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Fragment&lt;/code&gt; classes into small, self-contained components called LightCycles.&lt;/p&gt;
&lt;p&gt;Components that typically need to be aware of &lt;code class=&quot;language-text&quot;&gt;Activity&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Fragment&lt;/code&gt; lifecycle events include presenters, UI tracking code, input processors and more. We’ve been using LightCycle extensively in the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.soundcloud.android&quot;&gt;SoundCloud Music &amp;#x26; Audio&lt;/a&gt; and &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.soundcloud.creators&quot;&gt;SoundCloud Pulse&lt;/a&gt; apps over the last year.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;LightCycle lets self-contained classes respond to Android’s lifecycle events. This encourages composition over inheritance and promotes components that follow the single responsibility principle. We believe it helps us write more readable, maintainable and testable code.&lt;/p&gt;
&lt;p&gt;If this sounds familiar, RoboGuice has a similar concept called &lt;a href=&quot;https://github.com/roboguice/roboguice/wiki/Using-Events-in-your-RoboGuice-application&quot;&gt;Events&lt;/a&gt;. However, LightCycle is a small, standalone solution that works well with all dependency injection frameworks.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public class MyActivity extends LightCycleAppCompatActivity&amp;lt;MyBaseActivity&amp;gt; {

    @LightCycle MyController controller = new MyController();

    @Override
    protected void setActivityContentView() {
        setContentView(R.layout.main);
    }
}

public class MyController extends DefaultActivityLightCycle&amp;lt;MyActivity&amp;gt; {

    @Override
    public void onPause(MyActivity activity) {
        // MyActivity was paused
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Activitiy&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Fragment&lt;/code&gt; classes are responsible for configuring Android specifics and declaring LightCycles. LightCycles themselves can focus on business or presentation logic. Not being tightly coupled to the framework makes these components simpler to unit test (even without &lt;a href=&quot;http://robolectric.org&quot;&gt;Robolectric&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Get it&lt;/h2&gt;
&lt;p&gt;The code and documentation is available &lt;a href=&quot;https://github.com/soundcloud/lightcycle&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt;. Huge thanks to all the contributors: &lt;a href=&quot;https://github.com/brianegan&quot;&gt;Brian Egan&lt;/a&gt;, &lt;a href=&quot;https://github.com/android10&quot;&gt;Fernando Cejas&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdamcd&quot;&gt;Jamie McDonald&lt;/a&gt;, &lt;a href=&quot;https://github.com/jonschmidt&quot;&gt;Jon Schmidt&lt;/a&gt;, &lt;a href=&quot;https://github.com/iluu&quot;&gt;Karolina Kafel&lt;/a&gt;, &lt;a href=&quot;https://github.com/mttkay&quot;&gt;Matthias Käppler&lt;/a&gt;, &lt;a href=&quot;https://github.com/lopespm&quot;&gt;Pedro Lopes&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Congratulations, you have a lot of code!"Congratulations, you have a lot of code!" Remedying Android’s method limit - Part 2]]></title><description><![CDATA[In part one we described how running into Android’s method limit may leave you unable to build, and offered strategies you can employ to make your app fit into a single DEX file. In this part we share an alternative option: using multiple DEX files.]]></description><link>https://developers.soundcloud.com/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-2</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-2</guid><pubDate>Tue, 06 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;a href=&quot;/blog/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-1&quot;&gt;part one&lt;/a&gt; we described how running into Android’s method limit may leave you unable to build, and offered strategies you can employ to make your app fit into a single DEX file. In this part we share an alternative option: using multiple DEX files.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;MultiDex&lt;/h2&gt;
&lt;p&gt;If you have exhausted your options for slimming down your DEX, then there is really only one option left: &lt;a href=&quot;https://developer.android.com/tools/building/multidex.html&quot;&gt;MultiDex&lt;/a&gt;. Enabling MultiDex mode is a simple switch in your build scripts, and it allows you to grow your app beyond the 65k method limit by letting extra code spill over into additional DEX files. This sounds like a great option, but it has a number of repercussions you should be aware of, especially when using it in production and targeting pre-Lollipop versions of Android. The &lt;a href=&quot;https://developer.android.com/tools/building/multidex.html#limitations&quot;&gt;official documentation&lt;/a&gt; has more information about these problems.&lt;/p&gt;
&lt;p&gt;In a nutshell, multidexing means chopping up your code into multiple DEX files, where normally just one DEX file is created. ART—Android’s new runtime—deals with this effortlessly, since any number of DEX files will be compiled into a single ELF binary before your application loads. However, if you’re aiming at a &lt;code class=&quot;language-text&quot;&gt;minSdkLevel&lt;/code&gt; below 21 (Lollipop) and need to run on devices that use Dalvik, the tool chain must perform some extra bookkeeping for this to work. This comes with some strings attached:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You must include the &lt;a href=&quot;https://developer.android.com/tools/support-library/features.html#multidex&quot;&gt;MultiDex support library&lt;/a&gt; and modify your application to either inherit from &lt;code class=&quot;language-text&quot;&gt;MultiDexApplication&lt;/code&gt; or invoke the MultiDex loader manually. This is because Dalvik cannot natively deal with multiple DEX files. Luckily, this is safe to do even if you don’t use MultiDex in production builds.&lt;/li&gt;
&lt;li&gt;Your build times will increase, since the tool chain will employ ProGuard internally to split out your main application components and their direct dependencies into a primary DEX file, while moving all remaining code to a minimum number of additional DEX files. According to Android SDK lead Xavier Ducrohet, the Android Tools Team is “more than aware than this is a huge problem right now and we’re working on fixing this, but it’s going to be a while.” (quoted from a Slack conversation)&lt;/li&gt;
&lt;li&gt;The &lt;code class=&quot;language-text&quot;&gt;preDexLibaries&lt;/code&gt; flag will become inoperable, because the above step cannot be carried out without taking into account all class files, including those from external dependencies. This means that whenever you change a single line of code, the tools need to completely re-dex your app, including all library dependencies. ART does not have this limitation, as all DEX files (one for each runtime library plus those containing your app code) will be embedded in a single OAT file when being compiled down to machine code. Libraries can still be pre-dexed at build time, because they don’t need to be combined at build time.&lt;/li&gt;
&lt;li&gt;To use libraries which need to be aware of the full class graph, like Dagger, make sure that additional DEX files are loaded before you create your object graph. That means you cannot create your object graph in your application constructor anymore; instead, you can override &lt;code class=&quot;language-text&quot;&gt;attachBaseContext&lt;/code&gt; and create it after calling through to &lt;code class=&quot;language-text&quot;&gt;super&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A workaround we adopted to get back some of the lost build performance is to create a product flavor which specifies &lt;code class=&quot;language-text&quot;&gt;minSdkLevel&lt;/code&gt; 21. The toolchain can assume ART as the sole runtime at this API level, and &lt;a href=&quot;https://developer.android.com/tools/building/multidex.html#dev-build&quot;&gt;using this product flavor during development&lt;/a&gt; is a fair compromise. Keep in mind though that this will affect things like the lint tool, which checks for misuse of platform APIs taking into account your range of API levels. That said, with the workaround in place your build server should still build product flavors that specify the shipping API levels.&lt;/p&gt;
&lt;p&gt;To get an idea of just how much build times would be improved by applying the suggested workaround, we ran a few tests and timed different kinds of builds both on the CLI and in Android Studio using MultiDex. What we measured were 4 things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;time to test (TTT) using gradle-aware make: time it takes for the test results window to appear in the IDE when running a unit test, i.e. actual test run time is not accounted for&lt;/li&gt;
&lt;li&gt;TTT using Android Studio’s default IntelliJ style make&lt;/li&gt;
&lt;li&gt;time to launcher (TTL): time it takes for the launcher window to appear in the IDE when building/running the app,&lt;/li&gt;
&lt;li&gt;building on the CLI using the &lt;code class=&quot;language-text&quot;&gt;assemble[Flavor]Debug&lt;/code&gt; task.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of those were run in three variations: after a &lt;code class=&quot;language-text&quot;&gt;clean&lt;/code&gt;, after no changes, and after a single source file had changed.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 671px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/19234e7e3b35bc10753e8bfd33a724b4/ef3f5/build_times_min14.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.642324888226526%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABm0lEQVQoz12S304TURDG+2A+gi/hK3Cj3DYpXqASbjThhnBBNIEQ0JhQCNUAkQTBNooSFgLLhuLGlrR1/7V7/vw85+wuGudizpn5dr6dM9/UtNZYk1Li+z6e5xEEAWEYYqEK/99svsBdROVr9jIej0nTlCRJiKLInTYuS1FKI5VCSIWWwsSqJLWn4OZOcNkdmoQsCPM8dyT3f3de0WznDM824WK5RKQpL6zbS4mz4svk2ueq02EUxwWhfa4ldZ2Yu5LCFT9ZGnC9twA/Glz0Yb/Tg2DdYBGzqz2+fulAfxeGl4RnB/z8dVcQTiYTIsNe9ZaXvrEac9NeMR0+p3WimV+/hcNHkAa8eJtxctiEz1OuKk1GpFlWPVm4Wex++82HvXMItxzl4/lzTusP4WqWjyb9rLEBKw8M0mfmzYCjxRk4nkL9MyhHKIREi4wjb8T26w2iT3PkZuDvm6d8fzWNDPfxB7CztEay/dSUjmkddGm/rJN47xyhFcgqXyvlQjn9rZqCeFKtiyazIxF/O5BUqyIMrdkQUSR0uUp/AIIcV+rgj4eGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MultiDex build times (minSdkLevel 14)&quot;
        title=&quot;MultiDex build times (minSdkLevel 14)&quot;
        src=&quot;/blog/static/19234e7e3b35bc10753e8bfd33a724b4/ef3f5/build_times_min14.png&quot;
        srcset=&quot;/blog/static/19234e7e3b35bc10753e8bfd33a724b4/9ec3c/build_times_min14.png 200w,
/blog/static/19234e7e3b35bc10753e8bfd33a724b4/c7805/build_times_min14.png 400w,
/blog/static/19234e7e3b35bc10753e8bfd33a724b4/ef3f5/build_times_min14.png 671w&quot;
        sizes=&quot;(max-width: 671px) 100vw, 671px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 671px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ba8c16495806d8ea168036311e941d56/ef3f5/build_times_min21.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.642324888226526%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABmElEQVQoz1WSbUsVQRSA76/qB/R//BxUHzQlokCMwOib0ZcbqAgRSfTmCxkaKFmK7lV379Va1xa77OzrzOzTzOx2tYHdOefMOc/MeenUdY1dSil838fzPIIgIAxDri/rp1vfdrsmt3bzdaxQFAVZlpGmKUmSuL3Ic06jnKJUJkiPgpTSTheZpKyUs5/GkuOzP0ZUDbCqKgf5/1YYnzthcG7tmjAuOOgLi3RnjxcG7B4NnSz8gJOdHYZCNECbroVqXTtZK+kcJ+aO6EeN/Gk75slCf3Tpo67Pt17SKPEe4eEXfkVxAyzLkkSIUVqV+0smuzGD3hb8Xmb1e8GzN5GxX9gIpud/shuYF1bnrr5ZmpCZMrUpS4fZ2BesrR1AtOKQt2Y89u/ehN4UHz14OLUEL2846P3uJVuzd2BzDM1VYxxQSlN4mfPVG/L2xTzJ+jSVKfzr5T1+PL2NCj/jX8L754uk7x6Y0IIPG2dsz94jPXzlgLZR9qWdf11oRsLU0HRKmJxr3ei5TVBevUCNmiYN1kyIbAx1O1p/AeYHWbph1OQNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MultiDex build times (minSdkLevel 21)&quot;
        title=&quot;MultiDex build times (minSdkLevel 21)&quot;
        src=&quot;/blog/static/ba8c16495806d8ea168036311e941d56/ef3f5/build_times_min21.png&quot;
        srcset=&quot;/blog/static/ba8c16495806d8ea168036311e941d56/9ec3c/build_times_min21.png 200w,
/blog/static/ba8c16495806d8ea168036311e941d56/c7805/build_times_min21.png 400w,
/blog/static/ba8c16495806d8ea168036311e941d56/ef3f5/build_times_min21.png 671w&quot;
        sizes=&quot;(max-width: 671px) 100vw, 671px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Here, time is measured in seconds, so lower is better. From the graphs, looking at the orange bars, you can see that incremental build times have improved drastically when targeting 21 in development, since library pre-dexing is functional again, both in the IDE and on the command line.
Another interesting find here is to leverage what in IntelliJ/AndroidStudio is referred to as “Gradle-aware Make”, which consistently outperforms the default setup when running tests. In the configuration of your IntelliJ runners you can specify which jobs will be executed before launching a test or the app. The default is to use “Make”, which apparently will undermine some of the sophistication we get out of a Gradle based build in terms of incremental builds. By changing this step to &lt;code class=&quot;language-text&quot;&gt;app:assemble[Flavor]DebugUnitTest&lt;/code&gt; you can shave off unnecessary time from your test runs.&lt;/p&gt;
&lt;h2&gt;Moving to native&lt;/h2&gt;
&lt;p&gt;Just to look at the problem from multiple angles, another way to sidestep the DEX method problem is to simply not make code end up in the DEX file to begin with. Pushing code down to the native layer has the benefit that it can live in separate binary modules that can be side loaded at runtime, thus not adding to your overall method count during build time. The decision of whether this is a sensible step to make for you depends largely on your product, organization and team expertise and shouldn’t be made lightly, so we’re merely pointing this out for the sake of completeness.&lt;/p&gt;
&lt;h2&gt;Method count visibility&lt;/h2&gt;
&lt;p&gt;So far we focused on means to solve the DEX count issue. In order to not have it happen at the most inopportune time, it would be desirable to raise awareness around the current method count and notify developers when approaching the method limit. At SoundCloud we have started to monitor application size by plotting the DEX method count trend to a Jenkins graph using the &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Plot+Plugin&quot;&gt;Plot plugin&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/55086764b356e07e99a979d16552e9f4/b9fb9/method_plot.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.107095046854084%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABZElEQVQoz62TSXOCQBSE5///LysHD4ogcYEBxKVwYcAKGBbp9OASqZTJwRy+6jdvmkfN0IhwsUB0OCBVCsf1GsfN5k7C9WwwwHu/D6PXg/X2hmS3Q0yv2u+hWKvtFiqKoOjXPWGOx4gnE9SOg1RKHH2/Qx6GyEi+XCIjn/Q19NXSI/Ib10XJF4nZdIoZp8uyhA/8wLsim+ai9MnTCR7Vq6oWWddwuK+owpnPESUJKppBE2j4jZIPfZzPyKkdrntiwuPGPHtDU8GBf1GR+gl6T9i2jR0v98yBJRuv0hlYFMX/DNzrI/NSK97Dq4glo7Bllk78clmW3cnzvOWx94ybT6s4MNSWZWE0Gt0xTbNF18ZwiCExDKPVAYOu0eubt/VxvVqtLgMX/FuCIGi51T5DrQkY6pBMmQbd9zzv0mctGWiXgXYYdq0R/xgRxzEUE54wi5pOTSIabYbfpLrMbJKmHe8jetYXBA6M/frEqEMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Plotting DEX method counts&quot;
        title=&quot;Plotting DEX method counts&quot;
        src=&quot;/blog/static/55086764b356e07e99a979d16552e9f4/8ff1e/method_plot.png&quot;
        srcset=&quot;/blog/static/55086764b356e07e99a979d16552e9f4/9ec3c/method_plot.png 200w,
/blog/static/55086764b356e07e99a979d16552e9f4/c7805/method_plot.png 400w,
/blog/static/55086764b356e07e99a979d16552e9f4/8ff1e/method_plot.png 800w,
/blog/static/55086764b356e07e99a979d16552e9f4/6ff5e/method_plot.png 1200w,
/blog/static/55086764b356e07e99a979d16552e9f4/b9fb9/method_plot.png 1494w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We leverage a &lt;a href=&quot;https://github.com/KeepSafe/dexcount-gradle-plugin&quot;&gt;Gradle plugin&lt;/a&gt; to collect the method counts for different build types and store them for every build. Once we go past a certain threshold, we will mark the build as unstable and inform the developers via email. This will hopefully give us enough leeway to address the issue before maneuvering ourselves into a corner again.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Working in a fast moving environment is challenging, and being able to keep delivering product updates is essential to the business. To prevent worst case scenarios like the inability to build, developing both more sensitivity around the cost of third party libraries and getting deeper insights into application health can help. Make sure you understand the impact of leaning on external libraries: how much hidden complexity are you dragging into your app by using it? In case of hitting the limit, know your options: trimming down dependencies and MultiDex is what we ended up leaning on. Last but not least, visibility is king: raising awareness around application size and the looming method limit will allow you to take action ahead of time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you’d like to join the Android team, check out our &lt;a href=&quot;https://soundcloud.com/jobs/2015-05-22-android-engineer-berlin-germany&quot;&gt;current openings here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing SoundCloud JavaScript SDK 3.0.0]]></title><description><![CDATA[We are happy to announce version 3.0.0 of our SoundCloud JavaScript SDK. The new SDK improves stream security and content uploading…]]></description><link>https://developers.soundcloud.com/blog/introducing-javascript-sdk-3-0-0</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-javascript-sdk-3-0-0</guid><pubDate>Thu, 01 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are happy to announce version 3.0.0 of our SoundCloud JavaScript SDK.&lt;/p&gt;
&lt;p&gt;The new SDK improves stream security and content uploading functionality, and modernizes the technology stack.&lt;/p&gt;
&lt;p&gt;Version 3 of the SoundCloud JavaScript SDK is a major update and is not backwards compatible. That said, the changes that you need to make your web app work with the new SDK are easy to implement. Please refer to &lt;a href=&quot;/blog/docs/api/migrating-to-javascript-sdk-3&quot;&gt;Migrating to JavaScript SDK 3.0.0&lt;/a&gt; to upgrade.&lt;/p&gt;
&lt;h2&gt;ECMAScript 2015 and CommonJS&lt;/h2&gt;
&lt;p&gt;The original version of the SDK was written in CoffeeScript, which is no longer a core technology at SoundCloud. This update provided us the opportunity to migrate our source code from CoffeeScript to ECMAScript 2015.&lt;/p&gt;
&lt;p&gt;The new SDK is now using the &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt; compiler for ES2015 support, and &lt;a href=&quot;http://webpack.github.io/&quot;&gt;webpack&lt;/a&gt; as our bundler. This has the additional benefit of making the SoundCloud JavaScript SDK 3.0.0 compliant with CommonJS.&lt;/p&gt;
&lt;p&gt;Because of this, we can now take advantage of the variety of packages that are available via &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt;, and users can install the SDK via npm as well. Please refer to the &lt;a href=&quot;https://www.npmjs.com/package/soundcloud&quot;&gt;npm page&lt;/a&gt; for details.&lt;/p&gt;
&lt;h2&gt;JavaScript Promises&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;Promises&lt;/a&gt; have become a core part of JavaScript with the new ES2015 specification, and they allow for better composability and a easier control flow. Internally, we work with Promises a lot and wanted to give external developers the ability to benefit from an easier API.&lt;/p&gt;
&lt;h2&gt;Web Audio API&lt;/h2&gt;
&lt;p&gt;The previous SDK neither provided a way to record sounds from Web Audio applications nor a way to upload Web Audio recordings. It shipped with a Flash component that handled recording and uploading of recordings. There was no way to specify an external file for uploading.&lt;/p&gt;
&lt;p&gt;The new SDK ships with a &lt;a href=&quot;/blog/docs/api/sdks#recording&quot;&gt;recorder component&lt;/a&gt; that uses Web Audio and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;getUserMedia&lt;/code&gt;&lt;/a&gt; instead of Flash, and the component allows you to pass in arbitrary AudioNodes that will be recorded. This makes it much easier to integrate the SoundCloud JavaScript SDK 3.0.0 into creator applications that rely on Web Audio. The SDK provides a &lt;a href=&quot;/blog/docs/api/sdks#uploading&quot;&gt;dedicated method&lt;/a&gt; to publish recordings directly from your web app.&lt;/p&gt;
&lt;h2&gt;Secure streaming&lt;/h2&gt;
&lt;p&gt;The new SDK now includes a new player component. This component improves security for creator content and provides the improved playback stability.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;We also took the time to all of rewrite our &lt;a href=&quot;/blog/docs/api/sdks&quot;&gt;documentation&lt;/a&gt; and &lt;a href=&quot;/blog/docs/api/guide&quot;&gt;code examples&lt;/a&gt; so that you can start with the new SDK immediately.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Congratulations, you have a lot of code!"Congratulations, you have a lot of code!" Remedying Android’s method limit - Part 1]]></title><description><![CDATA[At SoundCloud we have been building for the Android platform since 2010. Much has changed since then: the team has grown, the list of features has grown, and our audience has grown. Today, eight engineers are working full time on the official SoundCloud app, across various areas, with contributions pouring in from other parts of the organization. Due to the growing complexity and number of contributions, the app’s size has grown substantially. Currently the app consists of approximately 1200 Java source files, not counting tests, containing approximately 86000 lines of code. This doesn’t include native code, such as our playback or recording stacks. We’re not the first to run into Android’s limits in terms of build tools. An internal limitation of Dalvik’s byte code format (DEX), which I will explain in more detail, can leave you unable to build after your codebase reaches a certain size. If you fail to anticipate this, it might happen during the most inconvenient time, such as when you are preparing for a release. Part of our job in Core Engineering at SoundCloud is to make sure our developers are happy and productive; not being able to build our app anymore makes for neither happy nor productive developers. While there are a number of posts on this topic, I would like to describe in more detail what we have done to combat Android’s method limit, what things worked well and what didn’t work so well, what it actually means to use the  tool’s  switch and what you can do to improve application health with regards to size.]]></description><link>https://developers.soundcloud.com/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-1</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-1</guid><pubDate>Mon, 21 Sep 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At SoundCloud we have been building for the Android platform since 2010. Much has changed since then: the team has grown, the list of features has grown, and our audience has grown. Today, eight engineers are working full time on the official SoundCloud app, across various areas, with contributions pouring in from other parts of the organization. Due to the growing complexity and number of contributions, the app’s size has grown substantially. Currently the app consists of approximately 1200 Java source files, not counting tests, containing approximately 86000 lines of code. This doesn’t include native code, such as our playback or recording stacks.&lt;/p&gt;
&lt;p&gt;We’re not the first to run into Android’s limits in terms of build tools. An internal limitation of Dalvik’s byte code format (DEX), which I will explain in more detail, can leave you unable to build after your codebase reaches a certain size. If you fail to anticipate this, it might happen during the most inconvenient time, such as when you are preparing for a release. Part of our job in Core Engineering at SoundCloud is to make sure our developers are happy and productive; not being able to build our app anymore makes for neither happy nor productive developers.&lt;/p&gt;
&lt;p&gt;While there are a number of posts on this topic, I would like to describe in more detail what we have done to combat Android’s method limit, what things worked well and what didn’t work so well, what it actually means to use the &lt;code class=&quot;language-text&quot;&gt;dx&lt;/code&gt; tool’s &lt;code class=&quot;language-text&quot;&gt;--multi-dex&lt;/code&gt; switch and what you can do to improve application health with regards to size.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The article is in two parts: part one lays out the basic problem and describes a number of options that you have to stay under the method limit. Part two focuses on Android’s MultiDex mode, which you can use to grow your app beyond the method limit.&lt;/p&gt;
&lt;h2&gt;The DEX Method limit&lt;/h2&gt;
&lt;p&gt;Android’s DEX format dates back to a time when applications were a lot smaller and devices were more resource constrained. To accommodate for this, the size of a DEX file’s method index is &lt;a href=&quot;https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html&quot;&gt;limited to 16 bits&lt;/a&gt;, which means if your application references more than 65,536 (2^16) methods in total, the dx tool will refuse to build your app. That number includes &lt;a href=&quot;https://developer.android.com/tools/building/multidex.html&quot;&gt;“Android framework methods, library methods, and methods in your own code”&lt;/a&gt;. 65k might sound like a lot, but when Android’s own support libraries account for about a third of that, and if on top of that you’re using one or two large libraries like Guava or Jackson, you rapidly approach that limit. To find out where you’re at, there are &lt;a href=&quot;https://github.com/mihaip/dex-method-counts&quot;&gt;convenient tools&lt;/a&gt; to count the number of methods in your app’s DEX file.&lt;/p&gt;
&lt;p&gt;To address a common point of confusion, the method limit described above is unrelated to “jumbo mode”, but it has a similar cause. Jumbo mode pertains to the number of strings that can be referenced in a DEX file, which by default are indexed using 16 bit wide integers. Therefore, if your application encodes more than 2^16 strings, the dx tool will fail as well. For string references however, there is a remedy: DEX supports &lt;a href=&quot;https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html&quot;&gt;“jumbo opcodes”&lt;/a&gt; which allow for 32 bit wide string references. The &lt;code class=&quot;language-text&quot;&gt;jumboMode&lt;/code&gt; flag in an Android Gradle build script enables this mode, allowing up to 2^32 strings to be referenced. However no equivalent switch exists for method references, so jumbo mode does nothing for you when your DEX file exceeds the number of methods allowed.&lt;/p&gt;
&lt;p&gt;The following sections will outline attempts we made to address the method limit imposed on an application.&lt;/p&gt;
&lt;h2&gt;Overcoming the method limit&lt;/h2&gt;
&lt;p&gt;It looks like we’ve hit a wall–not being able to assemble an APK anymore is not where you want to be. There are a number of options to overcome the limit, each with drawbacks and benefits. Let’s touch on the most obvious one first: putting your app on a diet!&lt;/p&gt;
&lt;h3&gt;Trimming dependencies&lt;/h3&gt;
&lt;p&gt;When we first hit the DEX method limit, we made the mistake of taking shortcuts to dodge it, such as staying on outdated but smaller versions of libraries like the Play services. Don’t do that!&lt;/p&gt;
&lt;p&gt;Sure, old dependencies wil buy you time for the short term if you’re stuck with a broken build. However, do invest effort into making sure you don’t immediately hit the limit again. Since removing dependencies from your app isn’t something you do in an afternoon and since MultiDex sounded scary and was something we initially considered a last resort, we opted for a hack: trimming down libraries using ProGuard and use those in debug versions of the app.&lt;/p&gt;
&lt;p&gt;If the release build is healthy in terms of method count, but the debug build isn’t, it means we’re dragging around a lot of unused stuff, so why not get rid of it in the first place? Thus was born: The Repacker!&lt;/p&gt;
&lt;p&gt;The idea is simple: have a Gradle task that performs a debug build with a special ProGuard config that only removes, neither optimizing nor obfuscating, finds all surviving classes for a particular library we’re targeting, rolls them into a new JAR and deploys it to our artifact repository under a new version. Then change the app to depend on the trimmed down version of the library which contains only the methods it actually needs. We’ve done this successfully for Guava, Jackson and the Facebook SDK, but it works best for libraries that change infrequently.&lt;/p&gt;
&lt;p&gt;This has several drawbacks. First, if you do this with a library like Guava, where you don’t know in advance what parts of the library you want to use, you will have to keep repacking the library as you need different parts. Second, since ProGuard sometimes accidentally removes parts that are actually in use, one might have to repack multiple times with different configurations to achieve the desired result. Lastly, we lost support for source JARs and JavaDocs, although we could have brought them back with further workarounds. While it sounded good on paper, we found this solution difficult to work with in the end, and it will not help at all if you’re exceeding the method count even after ProGuard strips away classes.&lt;/p&gt;
&lt;h3&gt;Losing dependencies&lt;/h3&gt;
&lt;p&gt;Cutting down the number of dependencies might sound obvious, but there are a few things worth pointing out.&lt;/p&gt;
&lt;p&gt;First, we as developers are not always conscious enough of the possible repercussions of our lasting choices. Here’s an example: choosing your building blocks. Take &lt;a href=&quot;https://github.com/google/guava&quot;&gt;Guava&lt;/a&gt; for instance. If you’re not familiar with the library, it contains a wealth of utilities that aim to close many of the gaps in Java’s language library, especially when you’re stuck with Java 6 (which you are unless you’re targeting KitKat and above). Unfortunately, the library is massive; it’s a single JAR clocking in at roughly 5MB and leaves a substantial footprint on your method allowance. It’s easy to just drop that dependency in your build script and unlock all the niceties it offers, but since it operates at a foundational level, it is bound to appear everywhere. Libraries like these are very difficult to rip out of your application later on, making these choices expensive to revisit. Moreover, you’d be surprised how much stuff a single method you use can drag on board along with it. Guava in particular is highly self-referential, so using one part of it will most likely make you unknowingly use a good chunk of its other—superficially unrelated—parts.&lt;/p&gt;
&lt;p&gt;We didn’t have this foresight and used Guava extensively; fortunately, it was one of those 80/20 things where in the majority of our code, we’d use a minority of the library. We took two weeks to remove Guava from our code base and salvage those high-value parts that we were using frequently into a utility library that is now fully owned and maintained by us. The great thing is that in the process we shed a lot of unnecessary code, such as Guava’s workarounds for GWT and older Java versions, which are all meaningless on Android. This made the code easier to read, test, and understand. Jackson is another offender in terms of method count. Because we didn’t encapsulate it sufficiently behind APIs we own, we haven’t yet been able to replace it. In summary: before you bake something you have little or no control over into the foundations of your app, think twice. The safest decisions are those that are reversible. Especially in &lt;a href=&quot;https://www.facebook.com/notes/kent-beck/taming-complexity-with-reversibility/1000330413333156&quot;&gt;phases of rapid growth and uncertainty which are inherent to software start-ups&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;/blog/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-2&quot;&gt;next and final part&lt;/a&gt;, we will turn to MultiDex and what you should know about when and how to use it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing Rate Limits]]></title><description><![CDATA[At SoundCloud, we’re building an ecosystem where creativity thrives. Developers
are an important part of that ecosystem. We’re continually…]]></description><link>https://developers.soundcloud.com/blog/introducing_rate_limits</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing_rate_limits</guid><pubDate>Tue, 16 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At SoundCloud, we’re building an ecosystem where creativity thrives. Developers
are an important part of that ecosystem. We’re continually inspired by how you
use the SoundCloud API to support creators and listeners in innovative ways.&lt;/p&gt;
&lt;p&gt;But as the ecosystem has grown, we’re dealing with an increasing number of
applications that abuse creator content by violating our &lt;a href=&quot;https://developers.soundcloud.com/docs/api/terms-of-use&quot;&gt;developer Terms of
Use&lt;/a&gt;. To help control
this type of behaviour, we’re introducing a daily rate limit on API play
requests.&lt;/p&gt;
&lt;p&gt;Beginning July 1, client applications will be limited to 15,000 play requests
per 24 hour period. These limits are applied to developer applications using the
SoundCloud API, and have no impact on the SoundCloud embedded player.&lt;/p&gt;
&lt;p&gt;Only a small number of developers will be affected by this change, and we’ve
contacted them via email to ensure a smooth transition.&lt;/p&gt;
&lt;p&gt;What this means for you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You should review our &lt;a href=&quot;https://developers.soundcloud.com/docs/api/rate-limits&quot;&gt;developer documentation on rate limiting&lt;/a&gt;. Limiting API access is common for API providers. If you’re unfamiliar with rate limits or how they work, &lt;a href=&quot;https://developers.soundcloud.com/support&quot;&gt;we’re here to help&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You should monitor your play requests.&lt;/li&gt;
&lt;li&gt;Make sure the email account associated with your app is up-to-date, and keep an eye out for communication from our team.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;This change does not affect the SoundCloud embedded player,
and if you have not heard from us, your app is unaffected.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the coming months we’ll be introducing an application process for developers
who’d like additional access.&lt;/p&gt;
&lt;p&gt;This change is an opportunity for us to refocus our efforts and renew our
commitment to developers. You’re an integral part of the SoundCloud community,
and we look forward to seeing what you build next.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New playlist representations]]></title><description><![CDATA[Requests for playlists have always included the full track objects contained
within. This representation may be convenient for playlists…]]></description><link>https://developers.soundcloud.com/blog/new_playlist_representations</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new_playlist_representations</guid><pubDate>Mon, 08 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Requests for playlists have always included the full track objects contained
within. This representation may be convenient for playlists with ten or twenty
tracks but can cause problems for playlists that contain hundreds or thousands
of tracks. Requesting such large playlists could result in requests that take a
long time to respond and that eventually timeout.&lt;/p&gt;
&lt;p&gt;Today, we introduce two new representations for the &lt;code class=&quot;language-text&quot;&gt;/playlists&lt;/code&gt; resource:
&lt;code class=&quot;language-text&quot;&gt;compact&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you add &lt;code class=&quot;language-text&quot;&gt;representation=compact&lt;/code&gt; to a playlist request, the request will
return only the playlist itself, without any of the tracks it contains. This
representation may be useful if you’re purely interested in data about the
playlist itself and not the tracks contained within.&lt;/p&gt;
&lt;p&gt;Alternatively, if you set the &lt;code class=&quot;language-text&quot;&gt;representation=id&lt;/code&gt; parameter, it will return the
playlist along with IDs of the tracks contained within, without all the
associated track meta data (artist, artwork, duration, etc.) If necessary, you
can then individually fetch additional information about each track by filling
those IDs into the &lt;code class=&quot;language-text&quot;&gt;/tracks&lt;/code&gt; resource. This allows for greater parallelization
and can help make your application more responsive when working with large
playlists.&lt;/p&gt;
&lt;p&gt;By default, requests for playlists will continue to include all the contained
tracks. There is no need to update your application but we encourage you to
take advantage of these new, more efficient representations of playlists.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Apple's June 1 64-bit deadline]]></title><description><![CDATA[In October 2014, Apple announced
that all submissions to the App Store must include 64-bit support
by June 1, 2015. The SoundCloud API for…]]></description><link>https://developers.soundcloud.com/blog/apple_64_bit_deadline</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/apple_64_bit_deadline</guid><pubDate>Mon, 18 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In October 2014, Apple &lt;a href=&quot;https://developer.apple.com/news/?id=10202014a&quot;&gt;announced&lt;/a&gt;
that all submissions to the App Store must include &lt;nobr&gt;64-bit&lt;/nobr&gt; support
by June 1, 2015. The &lt;a href=&quot;https://github.com/soundcloud/CocoaSoundCloudAPI&quot;&gt;SoundCloud API for Cocoa&lt;/a&gt;
contains &lt;nobr&gt;32-bit&lt;/nobr&gt; dependencies and will not be updated, because it
has been discontinued. Anyone using the SoundCloud API for Cocoa will need to
will need to migrate away from it if they wish to update their app after June 1.&lt;/p&gt;
&lt;p&gt;To ease this transition we have built a &lt;a href=&quot;https://github.com/soundcloud/iOSOAuthDemo&quot;&gt;sample
app&lt;/a&gt; that demonstrates how to
authorize a user via OAuth using only built-in Foundation libraries.&lt;/p&gt;
&lt;p&gt;Once an &lt;code class=&quot;language-text&quot;&gt;access_token&lt;/code&gt; has been obtained via OAuth, you can make &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; requests like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; urlSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSURLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sharedSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; urlString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.soundcloud.com/me&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; urlComponents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSURLComponents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; urlString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
urlComponents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queryItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSURLQueryItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;oauth_token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;insert an OAuth token here&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlComponents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dataTask &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTaskWithRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NSURLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; jsonOutput &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSJSONSerialization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;JSONObjectWithData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;AnyObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token comment&quot;&gt;// do stuff with JSON&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

dataTask&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;POST&lt;/code&gt; requests that contain multipart data (e.g. uploading a track) look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIKit&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ViewController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;UIViewController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadTrack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; trackPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; urlSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSURLSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sharedSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; urlRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getURLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; trackPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; task &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataTaskWithRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urlRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Void&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; httpResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSHTTPURLResponse&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;returned &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;httpResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statusCode&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; encoding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getURLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSURLRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; boundary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;UUIDString&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSMutableURLRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.soundcloud.com/tracks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;HTTPMethod&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;HTTPBody&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;insert an OAuth token here&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundary&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; boundary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; contentType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data; boundary=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; boundary
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forHTTPHeaderField&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundary&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; boundaryStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;boundary&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;\r\n&quot;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; boundaryEnd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\r\n--&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;boundary&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;--\r\n&quot;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; bodyData &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;NSMutableData&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSMutableData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// add the token&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; tokenSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boundaryStart
        tokenSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Disposition: form-data; name=\&quot;oauth_token\&quot;\r\n\r\n&quot;&lt;/span&gt;
        tokenSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;token&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;\r\n&quot;&lt;/span&gt;
        bodyData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokenSection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataUsingEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// add the track title&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; titleSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boundaryStart
        titleSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Disposition: form-data; name=\&quot;track[title]\&quot;\r\n\r\n&quot;&lt;/span&gt;
        titleSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;title&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;\r\n&quot;&lt;/span&gt;
        bodyData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titleSection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataUsingEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// add the audio file&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; trackData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentsOfFile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; audioPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; trackSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boundaryStart
        trackSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Disposition: form-data; name=\&quot;track[asset_data]\&quot;; &quot;&lt;/span&gt;
        trackSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;filename=\&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter variable&quot;&gt;\(&lt;/span&gt;audioPath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastPathComponent&lt;span class=&quot;token delimiter variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;\&quot;\r\n&quot;&lt;/span&gt;
        trackSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type: application/octet-stream\r\n&quot;&lt;/span&gt;
        trackSection &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;\r\n&quot;&lt;/span&gt;
        bodyData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trackSection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataUsingEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        bodyData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trackData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        bodyData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boundaryEnd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dataUsingEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; bodyData
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;  This example assumes access tokens will never expire, however, we
encourage you not to make this assumption in your production code. Instead,
build your app assuming that tokens will periodically expire and can be
refreshed using a refresh token. For details on how to use a refresh token,
see &lt;a href=&quot;https://tools.ietf.org/html/rfc6749#section-1.5&quot;&gt;Section 1.5 of the OAuth 2.0 specification&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Linked partitioning to replace offset-based pagination]]></title><description><![CDATA[The SoundCloud API will be dropping support for offset-based pagination on
March 2, 2015, in favor of linked partitioning. To page through a…]]></description><link>https://developers.soundcloud.com/blog/offset-pagination-deprecated</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/offset-pagination-deprecated</guid><pubDate>Mon, 02 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The SoundCloud API will be dropping support for offset-based pagination on
March 2, 2015, in favor of linked partitioning.&lt;/p&gt;
&lt;p&gt;To page through a JSON response, pass the &lt;code&gt;linked_partitioning=1&lt;/code&gt;
parameter along with your request and it will return a collection, along with a
&lt;code&gt;next_href&lt;/code&gt; property if there are additional results. To fetch the
next page of results, simply follow that URI. If the response does not contain
a &lt;code&gt;next_href&lt;/code&gt; property, you have reached the end of the results.&lt;/p&gt;
&lt;p&gt;You can read more about linked partitioning in the &lt;a href=&quot;https://developers.soundcloud.com/docs/api/guide#pagination&quot;&gt;Pagination section of our
HTTP API Guide&lt;/a&gt;,
including code examples in JavaScript, PHP, Python, and Ruby.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;limit&lt;/code&gt; parameter continues to be supported with linked
partitioning. The default &lt;code&gt;limit&lt;/code&gt; is 50 with a maximum value of 200.&lt;/p&gt;
&lt;p&gt;Please update your code to replace the &lt;code&gt;offset&lt;/code&gt; parameter with
&lt;code&gt;linked_partitioning&lt;/code&gt;. If you have any questions about this update,
&lt;a href=&quot;mailto:api@soundcloud.com&quot;&gt;please notify us via email&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Prometheus: Monitoring at SoundCloud]]></title><description><![CDATA[In
previous
blog
posts,
we discussed how SoundCloud has been moving towards a microservice architecture.
Soon we had hundreds of services, with many thousand instances running and
changing at the same time. With our existing monitoring set-up, mostly based on
StatsD and Graphite, we ran into a number of serious limitations. What we
really needed was a system with the following features: A multi-dimensional data model, so that data can be sliced and diced
at will, along dimensions like instance, service, endpoint, and method. Operational simplicity, so that you can spin up a monitoring server
where and when you want, even on your local workstation, without
setting up a distributed storage backend or reconfiguring the world. Scalable data collection and decentralized architecture, so that you
can reliably monitor the many instances of your services, and
independent teams can set up independent monitoring servers. Finally, a powerful query language that leverages the data model for
meaningful alerting (including easy silencing) and graphing (for
dashboards and for ad-hoc exploration). All of these features existed in various systems. However, we could not
identify a system that combined them all until a colleague started an ambitious
pet project in 2012 that aimed to do so. Shortly thereafter, we decided to
develop it into SoundCloud’s monitoring system:
Prometheus was born.]]></description><link>https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/prometheus-monitoring-at-soundcloud</guid><pubDate>Mon, 26 Jan 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In
&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;previous&lt;/a&gt;
&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-2-breaking-the-monolith&quot;&gt;blog&lt;/a&gt;
&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-3-microservices-in-scala-and-finagle&quot;&gt;posts&lt;/a&gt;,
we discussed how SoundCloud has been moving towards a microservice architecture.
Soon we had hundreds of services, with many thousand instances running and
changing at the same time. With our existing monitoring set-up, mostly based on
StatsD and Graphite, we ran into a number of serious limitations. What we
really needed was a system with the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;multi-dimensional&lt;/strong&gt; data model, so that data can be sliced and diced
at will, along dimensions like instance, service, endpoint, and method.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operational simplicity&lt;/strong&gt;, so that you can spin up a monitoring server
where and when you want, even on your local workstation, without
setting up a distributed storage backend or reconfiguring the world.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable data collection&lt;/strong&gt; and decentralized architecture, so that you
can reliably monitor the many instances of your services, and
independent teams can set up independent monitoring servers.&lt;/li&gt;
&lt;li&gt;Finally, a &lt;strong&gt;powerful query language&lt;/strong&gt; that leverages the data model for
meaningful alerting (including easy silencing) and graphing (for
dashboards and for ad-hoc exploration).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these features existed in various systems. However, we could not
identify a system that combined them all until a colleague started an ambitious
pet project in 2012 that aimed to do so. Shortly thereafter, we decided to
develop it into SoundCloud’s monitoring system:
&lt;a href=&quot;http://prometheus.io&quot;&gt;Prometheus&lt;/a&gt; was born.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Prometheus is written in &lt;a href=&quot;http://golang.org/&quot;&gt;Go&lt;/a&gt; and has been &lt;a href=&quot;https://github.com/prometheus&quot;&gt;open source from the
beginning&lt;/a&gt;. Despite being intentionally quiet
about it, we gained a few committed external users and contributors, most
notably from &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; and
&lt;a href=&quot;http://www.boxever.com/&quot;&gt;Boxever&lt;/a&gt;. Since then, Prometheus has become the
standard monitoring solution at SoundCloud. With the level of maturity reached,
we would like to tell you more about it.&lt;/p&gt;
&lt;p&gt;Our friends from Boxever and Docker are also publishing blog posts
simultaneously about their usage of Prometheus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.boxever.com/prometheus-a-next-generation-monitoring-system&quot;&gt;Prometheus: A Next-Generation Monitoring System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://5pi.de/2015/01/26/monitor-docker-containers-with-prometheus&quot;&gt;Monitor Docker Containers with Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;p&gt;This diagram illustrates the overall architecture of Prometheus and some of its
ecosystem components:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/6ad4784882b3758430eea84a3c25486b/prometheus_architecture.svg&quot; alt=&quot;Prometheus architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;Prometheus servers scrape metrics from instrumented jobs, either directly or
via an intermediary push gateway for short-lived jobs. They store all scraped
samples locally and run rules over this data to either record new timeseries
from existing data or generate alerts. PromDash (a web-based dashboard builder)
or other API consumers can be used to visualize the collected data.&lt;/p&gt;
&lt;p&gt;Prometheus’s main features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A multi-dimensional data model.&lt;/li&gt;
&lt;li&gt;A flexible query language to leverage this dimensionality.&lt;/li&gt;
&lt;li&gt;No reliance on distributed storage; single server nodes are autonomous.&lt;/li&gt;
&lt;li&gt;Time series collection happens via a pull model over HTTP.&lt;/li&gt;
&lt;li&gt;Pushing time series is supported via an intermediary gateway.&lt;/li&gt;
&lt;li&gt;Targets are discovered via service discovery or static configuration.&lt;/li&gt;
&lt;li&gt;Multiple modes of graphing and dashboarding support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also the &lt;a href=&quot;http://prometheus.io/docs/introduction/overview/&quot;&gt;Overview
section&lt;/a&gt; of
the documentation.&lt;/p&gt;
&lt;h2&gt;Data model&lt;/h2&gt;
&lt;p&gt;Prometheus fundamentally stores all data as time series: streams of timestamped
values belonging to the same metric and the same set of labeled dimensions.
Timestamps have a millisecond resolution, while values are always 64-bit
floats.&lt;/p&gt;
&lt;p&gt;The metric name specifies the general feature of a system that is measured. For
example, a metric to count the total number of HTTP requests received by an API
server might be called &lt;code class=&quot;language-text&quot;&gt;api_http_requests_total&lt;/code&gt;. Adding labels (key/value
pairs) to this metric enables Prometheus’s dimensional data model: any given
combination of labels for the same metric name results in a separate time series.
One example time series for this metric might represent all the received HTTP
requests that used the method &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; for querying the endpoint &lt;code class=&quot;language-text&quot;&gt;/api/tracks&lt;/code&gt; and
resulted in a &lt;code class=&quot;language-text&quot;&gt;200&lt;/code&gt; HTTP status code. For naming a time series like that, we
use this notation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;api_http_requests_total{method=&amp;quot;GET&amp;quot;, endpoint=&amp;quot;/api/tracks&amp;quot;, status=&amp;quot;200&amp;quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The query language then allows filtering and aggregation based on these
dimensions. Prometheus’s label-based data model is fundamentally the same as
the one of &lt;a href=&quot;http://opentsdb.net/&quot;&gt;OpenTSDB&lt;/a&gt; and even has a similar syntax.&lt;/p&gt;
&lt;p&gt;Metrics in Prometheus have a type to indicate their meaning and usage.
Prometheus currently supports three metric types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;counter&lt;/strong&gt; is a cumulative metric that represents a value
that only ever goes up, like a request count.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;gauge&lt;/strong&gt; is a metric that
represents a value that can arbitrarily go up and down, like a temperature or
a queue length.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;summary&lt;/strong&gt; is a client-side calculated histogram of
observations, usually used to measure request durations or sizes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more information on Prometheus’s data model, see the &lt;a href=&quot;http://prometheus.io/docs/concepts/data_model/&quot;&gt;Data
model&lt;/a&gt; and &lt;a href=&quot;http://prometheus.io/docs/concepts/metric_types/&quot;&gt;Metric
types&lt;/a&gt; sections in the
documentation.&lt;/p&gt;
&lt;h2&gt;Query language&lt;/h2&gt;
&lt;p&gt;The Prometheus query language allows you to slice and dice the dimensional
data for ad-hoc exploration, graphing, and alerting. We’ll talk about
the use of the query language for dashboards and alerting in the next
two sections. Your first steps with the query language are best done
using the &lt;a href=&quot;http://prometheus.io/docs/visualization/browser/&quot;&gt;expression
browser&lt;/a&gt; offered by each
Prometheus server.&lt;/p&gt;
&lt;p&gt;As an example, we have instrumented Bazooka, our internal Heroku-style
deployment system, with Prometheus. This provides us with per-instance metrics
about memory usage, memory limits, CPU usage, out-of-memory failures, instance
restarts, and so on. Each of these metrics is labeled by application, process
type, Git revision, environment, and instance ID. This has frequently been useful
for debugging both single-instance problems, as well as for doing cluster-wide
resource usage analysis. For instance, badly-configured Bazooka applications
might set large container memory limits, yet not use a lot of the reserved
memory. This screenshot shows a query for the worst RAM wasters across a
Bazooka cluster (click the image to view a full-sized version):&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5938801e9e8897edc802c48ebde57744/c2b5a/prometheus_bazooka_memory_wasters.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA9klEQVQoz5WQwW6EIBCGeZu2B3APvmGPfY9e+hpN+hBqso1NNFJAEIUFxA7u1jSbrJt+pxkm//w/g7LskOc5JpgQnJEsIwmM8dMuD48JVNd1VVVlUZRFWZXVRlGk/pfUbcPP4/H59f3l7QNZH6wL3rm4/AMTllNYkJSyo3QY1DiO8zzHlT9FvEIIPk1TWhAjgkpwzhjnXFhr1sebwLSXPdhAAQZIa00pZYwJIZz3e9JVLJWcjLmIB6Wapuk6yE6Nue/MBR+0vojhz18rsEIp5Zw73QamtOv6vncryblt27OztTbeiw230Zsz3Pks/mbM+7Cb+lr8A2K3EEUZTHVDAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Bazooka memory wasters&quot;
        title=&quot;Bazooka memory wasters&quot;
        src=&quot;/blog/static/5938801e9e8897edc802c48ebde57744/8ff1e/prometheus_bazooka_memory_wasters.png&quot;
        srcset=&quot;/blog/static/5938801e9e8897edc802c48ebde57744/9ec3c/prometheus_bazooka_memory_wasters.png 200w,
/blog/static/5938801e9e8897edc802c48ebde57744/c7805/prometheus_bazooka_memory_wasters.png 400w,
/blog/static/5938801e9e8897edc802c48ebde57744/8ff1e/prometheus_bazooka_memory_wasters.png 800w,
/blog/static/5938801e9e8897edc802c48ebde57744/c2b5a/prometheus_bazooka_memory_wasters.png 982w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Similar queries allow us to identify potentially misconfigured applications and
reach out to their developers.&lt;/p&gt;
&lt;p&gt;As another related example, we might want to graph the top three CPU users on a
Bazooka cluster as measured over the last five minutes, grouped by application
and process type:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d16372095651eb4c86d6556e9a4ebf14/1c938/prometheus_bazooka_cpu_top3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.20112254443406%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABmElEQVQoz21SO04DMRDd4yDxERGJICeAA0CZGyBRQAlFDoCg4xQpkeigpqamhWxCyH5sj+fjMN4PWT5P1uzs2DNv3tjJ/sH+cDjsR+wN+v3BQE2/1+vt/oT+b27tbGxud4PJx3z+/j59m05ns9k8Ypam0zRNF4tF2YF35v759e7hpTTGOVsjIUQH6FFCi1WFrlP7ariJNUiYGRCItISzzhCRiGjQV0BEADDWajCESCAt1I/JBSyBDbIjARSoDq2Zv9FEwrqdhJgyMMCk7BSCI7BUsGA8GVYkLLGWaAILo5BnJEFmqpNZXd1Xi8KetQHOfZlDnkG2jLZdLiuwMGhKrwM0TdsqsW6ltc3kKk4VsJLGl66EmFxL/5b0V+qvrdBUrwamSiH79Hls0OvKlgF9JCJkgEDUzmjNuGbWtrts6pM1VBbknKAnU7IpdALsLGq8yFGX7tpKswd/fXtzeXU5Ho/PLy6eHh+1BMVJxAvTL+sLAtB7R2OqX31VNjYVkz2enZ2enByPRqPDw6PJZBJvpR7h6h/93R6/AFcgvQqBjszAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Bazooka top CPU users&quot;
        title=&quot;Bazooka top CPU users&quot;
        src=&quot;/blog/static/d16372095651eb4c86d6556e9a4ebf14/8ff1e/prometheus_bazooka_cpu_top3.png&quot;
        srcset=&quot;/blog/static/d16372095651eb4c86d6556e9a4ebf14/9ec3c/prometheus_bazooka_cpu_top3.png 200w,
/blog/static/d16372095651eb4c86d6556e9a4ebf14/c7805/prometheus_bazooka_cpu_top3.png 400w,
/blog/static/d16372095651eb4c86d6556e9a4ebf14/8ff1e/prometheus_bazooka_cpu_top3.png 800w,
/blog/static/d16372095651eb4c86d6556e9a4ebf14/1c938/prometheus_bazooka_cpu_top3.png 1069w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For intense queries that take a long time to evaluate, you can configure
Prometheus to precompute them in the background. Precomputed expressions are
called &lt;em&gt;rules&lt;/em&gt;. They have a name, which is used like the name of a metric in
other expressions.&lt;/p&gt;
&lt;p&gt;For more details about the query language, see the
&lt;a href=&quot;http://prometheus.io/docs/querying/basics/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Dashboards&lt;/h2&gt;
&lt;p&gt;The one-stop solution for the fanciest dashboards is
&lt;a href=&quot;http://prometheus.io/docs/visualization/promdash/&quot;&gt;PromDash&lt;/a&gt;, a GUI-based
dashboard builder with a SQL backend. It talks to any number of Prometheus
servers via an HTTP API and graphs their data in highly configurable
dashboards. Even Graphite graphs can be included.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a2ecbeb647062fed5ab2e252546b74b3/40872/promdash_event_processor.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.19545071609099%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC5klEQVQ4y5WT3YsbZRTGh14Iu6mbZPIxM5nvmcxMZiZpks1Hk2VTdd20a4uWpbZS2e4WC1akRaRW96IIIlK9EJReiWBFUC9sBcH/7+eZrC4I3njxcHgP7/s85zzvOYrv+7iuS7WqCqpYlkWr1VpF27b/NxRDHmdZRhxHhGFIkiSkaYd6vc7a2hqlUon19fVV/Bckt/4fUFRVxZZqdF0/hWG0UNUqpbNn2djYoFqpotZUKpWyoEK5XKapNVfnQrTAiagQ6lqDSKoqqiug65pYYNE712fQ75N3u9S1OrbpkKdd8iwl7SRYpkuzZqBrutjTwtANKhs1lLnt8Vo3xbBMTNtiO/S5NZ1y9fIldnaXvH5xl6XfZnsRMdnqYRkegRcwnHhcfWuA57sYTY+w7dMbWULohNyP5wxE0XVccvmgtqhWX1hDOXOGvKZzMxzz6NMePz8/z6tXfN79IOPZn1v8/secz74aMtp2ePztiC++7KOM8w7vXVlyezjjjdGQvdmQ5XTA5VmXHT/m+oVN9l+e8+Fum1/u9nn6aMxPH2/yw1GX727k/HhvwNPPJ/z2ySYPd0PxsBaStRdM8gnTdE7uL+gGFxhnC14ZzRmn2/jWlJek1UM34h2x5NALuWZG3HASjvyIO1HIgZuwI3eU4hN6vZ6MTQc/CGUG9b9hUm8amKZFo1HHEiuiNCdox3hhRJLlkvNQG5rc06lphkRN5lDGJEszEjE1bgcE8tC2bKIwwPcckriNJp76jkMUBCJgrvJZEtL2Pblrohs6oe/g2iZKXdTj2KKsapRebOIGLlk3WP16papRkXyrZQiphllskBC6jn06s7Z04MqG+K4tnTRQ9i+ZPH9yjjtvd7h1PeLXb/o8ezLg+8c99pYhH91N+Po4lkqEVGatIGzJ4BuGcYpABIrYbDZRdsYBxwcL3n9zzsODGff3z3N7b4sHN+c8OJxyfDTj3rXNVZXFjp9s0glRcS7iP7mC8C/TvYE2Kq4UZQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;PromDash screenshot&quot;
        title=&quot;PromDash screenshot&quot;
        src=&quot;/blog/static/a2ecbeb647062fed5ab2e252546b74b3/8ff1e/promdash_event_processor.png&quot;
        srcset=&quot;/blog/static/a2ecbeb647062fed5ab2e252546b74b3/9ec3c/promdash_event_processor.png 200w,
/blog/static/a2ecbeb647062fed5ab2e252546b74b3/c7805/promdash_event_processor.png 400w,
/blog/static/a2ecbeb647062fed5ab2e252546b74b3/8ff1e/promdash_event_processor.png 800w,
/blog/static/a2ecbeb647062fed5ab2e252546b74b3/40872/promdash_event_processor.png 1187w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If you want to serve simple dashboards from the Prometheus server
directly (without the need of a separate application with SQL
backend), have a look at
&lt;a href=&quot;http://prometheus.io/docs/visualization/consoles/&quot;&gt;console templates&lt;/a&gt;.
SoundCloud uses PromDash as its main way for visualizing Prometheus data.&lt;/p&gt;
&lt;h2&gt;Alerting&lt;/h2&gt;
&lt;p&gt;Alerts are defined using the same powerful query language described
above. A separate binary, the
&lt;a href=&quot;https://github.com/prometheus/alertmanager&quot;&gt;Alertmanager&lt;/a&gt;, handles alert
notifications and aggregations and enables silencing by any label set. If an
alert fires, Alertmanager can send an email or page you through an external
alerting service like &lt;a href=&quot;http://www.pagerduty.com/&quot;&gt;PagerDuty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also offer a &lt;a href=&quot;https://github.com/prometheus/nagios_plugins&quot;&gt;Nagios plugin&lt;/a&gt;
as a bridge from the parts that are already monitored by Prometheus to the
existing Nagios alerting set-up that many companies have in place. In fact,
SoundCloud uses exactly that configuration for production alerting, which is
why the Alertmanager is still less mature than the rest of the Prometheus
ecosystem. We plan to move to fully native Prometheus alerting soon.&lt;/p&gt;
&lt;p&gt;For more information, see the documentation about
&lt;a href=&quot;http://prometheus.io/docs/querying/rules/#alerting-rules&quot;&gt;configuring alerting rules&lt;/a&gt;
and
&lt;a href=&quot;http://prometheus.io/docs/practices/alerting/&quot;&gt;alerting best practices&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Storage&lt;/h2&gt;
&lt;p&gt;One of our goals was to run a Prometheus server with millions of time
series and tens of thousands of samples ingested per second on a
single server using its local disk. You might think everything should
be distributed-something nowadays, but distributed systems demand
payment in complexity and operational burdens. How many of you would
love to run OpenTSDB but shy away from maintaining a whole Hadoop
cluster just for that? With Prometheus, you shard servers by function
and run multiple replicas of the same shard for
redundancy. Cross-server queries are not possible, but we are working
on a hierarchical federation scheme where a higher-level Prometheus server
collects selected metrics from subordinated Prometheus servers.&lt;/p&gt;
&lt;p&gt;Designing the local time series database for Prometheus was an adventure of its
own. We explored and compared many existing local storage implementations.
Based on our research, we decided to use
&lt;a href=&quot;https://github.com/syndtr/goleveldb&quot;&gt;LevelDB&lt;/a&gt; for indices and our own design
for storing the actual sample values and timestamps.&lt;/p&gt;
&lt;p&gt;The disk storage of a server is naturally limited (depending on the
rate of incoming samples, we usually keep several weeks or months
around, without any downsampling), and the disk can fail permanently
(resulting in data loss, at least on that one replica). Whoever
requires true durability of samples will need some kind of
distributed storage. Prometheus already has experimental support for
streaming collected samples to OpenTSDB as a permanent storage, turning
the Prometheus ecosystem into a collection machinery for
OpenTSDB. One day, we would like to support the complete round-trip,
i.e. Prometheus queries can read data back from OpenTSDB. Other time
series databases might get support too, but the differences in the
data model are a challenge.&lt;/p&gt;
&lt;h2&gt;Getting your service ready for Prometheus&lt;/h2&gt;
&lt;p&gt;Prometheus-style monitoring relies on instrumenting your own code directly.
This is very different from traditional strategies to monitor applications,
where the monitored application is often completely ignorant of the monitoring
system, which uses plugins or agents to talk to the application in its own
language. The high-quality service-based monitoring you get from your
handwritten instrumentation is usually worth the price.&lt;/p&gt;
&lt;p&gt;To instrument code, we offer client libraries for &lt;a href=&quot;https://github.com/prometheus/client_golang&quot;&gt;Go&lt;/a&gt;,
&lt;a href=&quot;https://github.com/prometheus/client_java&quot;&gt;Java&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/prometheus/client_ruby&quot;&gt;Ruby&lt;/a&gt;. As the Java client library
is usable from any JVM language, this completely covers the needs of
SoundCloud. A Python client is also under development. These client libraries
allow you to track and expose metrics over HTTP. The default exposition format
is &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;protocol buffers&lt;/a&gt;, but we
also support a very simple text-based format, which can be used for easy
“library-less” exposition of metrics even from shell scripts.&lt;/p&gt;
&lt;p&gt;To mimic the ways of traditional monitoring approaches, there are a number of
&lt;a href=&quot;http://prometheus.io/docs/instrumenting/exporters/&quot;&gt;exporters&lt;/a&gt;
for certain types of servers, like the &lt;a href=&quot;https://github.com/prometheus/haproxy_exporter/&quot;&gt;HAProxy Exporter&lt;/a&gt;
to &lt;a href=&quot;http://www.boxever.com/haproxy-monitoring-with-prometheus&quot;&gt;export metrics of an HAProxy&lt;/a&gt;
or the &lt;a href=&quot;https://github.com/prometheus/node_exporter&quot;&gt;Node Exporter&lt;/a&gt;
to export varius node attributes.&lt;/p&gt;
&lt;p&gt;Finally, if you have short-lived jobs like cronjobs that cannot wait
for Prometheus to scrape them, there is the
&lt;a href=&quot;https://github.com/prometheus/pushgateway&quot;&gt;Pushgateway&lt;/a&gt;, to which you can
push metrics. The Pushgateway will then present these metrics permanently to
Prometheus for scraping.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Prometheus has proven to be very useful at SoundCloud. It drives
our internal monitoring, facilitates firefighting during outages
and provides further insight into them for postmortems. We run
Prometheus as an open-source project because we believe it can benefit the
infrastructure community as a whole. If you are interested in getting involved,
check out the &lt;a href=&quot;http://prometheus.io/community/&quot;&gt;Prometheus community&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special thanks to &lt;a href=&quot;http://matttproud.com&quot;&gt;Matt T. Proud&lt;/a&gt;, who initially
started the Prometheus project.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud in Scalding case study by Concurrent Inc.]]></title><description><![CDATA[Recently we teamed up with Concurrent Inc., the backers of the data-processing framework Cascading, to do a case study of how we use…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-concurrent-scalding-case-study</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-concurrent-scalding-case-study</guid><pubDate>Tue, 02 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently we teamed up with Concurrent Inc., the backers of the data-processing framework &lt;a href=&quot;http://www.cascading.org/&quot;&gt;Cascading&lt;/a&gt;, to do a &lt;a href=&quot;http://www.concurrentinc.com/customer/soundcloud/&quot;&gt;case study&lt;/a&gt; of how we use &lt;a href=&quot;https://github.com/twitter/scalding&quot;&gt;Scalding&lt;/a&gt; for some of our data-driven products such as Search. Scalding enables us to iterate quickly, test easily, and it allows for loose coupling of some of our data-processing pipelines.&lt;/p&gt;
&lt;p&gt;Check back for future posts about our use of other data-processing tools, and frameworks such as &lt;a href=&quot;https://spark.apache.org/&quot;&gt;Spark&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[XML responses deprecated]]></title><description><![CDATA[The SoundCloud API will be dropping support for Extensible Markup Language
(XML) responses. XML will be phased out on the following schedule…]]></description><link>https://developers.soundcloud.com/blog/xml-responses-deprecated</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/xml-responses-deprecated</guid><pubDate>Mon, 17 Nov 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The SoundCloud API will be dropping support for Extensible Markup Language
(XML) responses. XML will be phased out on the following schedule:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;XML is currently the default response format for requests without an
explicit format specified in the path (e.g. &lt;code class=&quot;language-text&quot;&gt;/tracks&lt;/code&gt;) or &lt;code class=&quot;language-text&quot;&gt;Accept&lt;/code&gt; header.
This default will be changed to JSON on December 1, 2014.&lt;/li&gt;
&lt;li&gt;Explicit requests for XML — specified either in the path (e.g.
&lt;code class=&quot;language-text&quot;&gt;/tracks.xml&lt;/code&gt;) or an &lt;code class=&quot;language-text&quot;&gt;Accept: application/xml&lt;/code&gt; header — will continue
to be supported until December 15, 2014. After that point, only JSON
responses will be supported.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SoundCloud has been using JSON exclusively for internal APIs for several years.
Dropping support for XML in our public API will allow us to focus on providing
consistent and reliable service.&lt;/p&gt;
&lt;p&gt;If your app still uses XML responses, please start working to upgrade it to
JSON immediately. If your app does not currently use XML responses, it should
be unaffected by this change.&lt;/p&gt;
&lt;p&gt;If you are unable to migrate your app from XML to JSON for some reason, we
recommend accessing the SoundCloud API through a proxy server that converts
JSON to XML.&lt;/p&gt;
&lt;p&gt;Please let us know if you have any questions about this update &lt;a href=&quot;mailto:api@soundcloud.com&quot;&gt;via
email&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building the new SoundCloud iOS application — Part II: Waveform rendering]]></title><description><![CDATA[When we rebuilt our iOS app, the player was the core focus. The interactive
waveform was at the center of the design. It was important both…]]></description><link>https://developers.soundcloud.com/blog/ios-waveform-rendering</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/ios-waveform-rendering</guid><pubDate>Mon, 15 Sep 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we rebuilt our iOS app, the player was the core focus. The interactive
waveform was at the center of the design. It was important both that the player
be fast and look good.&lt;/p&gt;
&lt;h2&gt;Initial implementation&lt;/h2&gt;
&lt;p&gt;We iterated on the waveform view until it was as responsive as possible. The
initial implementation focused on replicating the design, which heavily used
CoreGraphics. A single custom view calculates the current bar offset based on
its time property. It then draws each of the waveform samples that are in the
current visible section of the waveform. Each sample is either rendered as a
filled rectangle for unplayed samples, or by adding clip paths to the context
then drawing a linear &lt;code class=&quot;language-text&quot;&gt;CGGradient&lt;/code&gt; for the played samples.&lt;/p&gt;
&lt;p&gt;To handle progress updates, we used the callback block on &lt;code class=&quot;language-text&quot;&gt;AVPlayer&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;player addPeriodicTimeObserverForInterval&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CMTimeMake&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                     queue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                usingBlock&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CMTime time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;waveformView setPlaybackTime&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CMTimeGetSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This generates 60 callbacks per second to try and achieve the highest
possible frame rate. This solution worked perfectly during testing on the
simulator. Unfortunately, testing on an iPhone 4 did not produce similar
results:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 620px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5d0dcb8ac21a9ae439c45efd433b2870/a5ed1/waveform_rendering_cpu.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.2258064516129%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAADbUlEQVQ4y6WR3U9bdRjHT1yiJmKMUS96tSV6A5JtaqZX3voPzBMTb7wx3ojJhllCsEYWDMs02XAvvLQLY1KqIhMwQGl7zmlPWwotfW/pG+VAC6WW0a4tG3075/f1R7lZjFf6JJ88z/N9nnx/v+RhPnj9/VMrS9Mfef0rnyQTqY83NlJsKpVi4/E4G6MkkknW5XKzAi+wwUCIDYcjrN/vZ6PRKBuLxdl4InG8ezEWi326uLj4JtN57t03FudmMk6rgFAoiNWVFay53bQOwePxwE1ru1vEjPEhDOICLMsCjEvG1iyZTIIaIZFIoFAoQKvV9jKnz5xRTU8/3OTMPDiOl3leUAyGJcVudygczytUU3gLrwg2QTFbzIqZ5xSj0UR1TuEF4WTO83WPx4vBwUE181bnh6qB73XSyJAJ45NeZexnH5madRGXf514IxLxUQLRbRJO7JJIMkvWN/ZIdDNHc5aEEjtU2yXBeKaR+auCyV+mvmFeefW06rPPx6Suy3+i6+t55Yuv5vHdwO+4dKUHX17qQdflHlzpvYrevmv4tv9H9A3cQP/1W+AcQbjDaSyJXmKwepsWZwgPdL+pmQsX3lP19WukH27+inGdUbk/Yca8wQrRsQJBdMJic0KwLoOzLIO3OsAJdpgFGyJxCVImj42tLElt7zX3i4eY0OnVTPvbHarxBxPSzOwcrDabYuIFuNyr8Pkd8PmsCAREeH0iPF6RHspGddp7BWxKMRwUynh0UCT7B8WmTIDZ2Tk18/ypF1WiuCxJW2lE1uPKzm4O29sZpNMSstkMcrksveA+ipST/KiVHz8uolwuo1Qqk1Kp1ASNhYUFNdPR0akycXbJtRZGMJJS1uNbyOULKJUPKU9QKJbx9KiGaq2Bo+qz1Cm1YwidtwyNJrOaOXvurMrmcEv+UALRZFrZyRVJqVI9XiKNpkLKlSfkqNYgdVrXG/IzKCcapdaQW4a8YFEz5985r7KIztTqGr2aL9LYTOfl0mFVrhw+lamRTH8qU3O5Wm/K9Jf/Ct2rHRuaOb6XaW9vVwXCyXyx0sBevoxq663/Fja74yrT1tb22rXrN27eHtJM3bo7OvnTnRH93aFR/fDwsF6j1eqHR0b0oxqNXqu9R/t7rfxPNBqtbuz++B/d3d0XGRrPUV6ivPw/aaO88DclODbMti3V8wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CPU rendering instruments profile&quot;
        title=&quot;CPU rendering instruments profile&quot;
        src=&quot;/blog/static/5d0dcb8ac21a9ae439c45efd433b2870/a5ed1/waveform_rendering_cpu.png&quot;
        srcset=&quot;/blog/static/5d0dcb8ac21a9ae439c45efd433b2870/9ec3c/waveform_rendering_cpu.png 200w,
/blog/static/5d0dcb8ac21a9ae439c45efd433b2870/c7805/waveform_rendering_cpu.png 400w,
/blog/static/5d0dcb8ac21a9ae439c45efd433b2870/a5ed1/waveform_rendering_cpu.png 620w&quot;
        sizes=&quot;(max-width: 620px) 100vw, 620px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The preceding trace displays an initial frame rate of 17 FPS that drops to 10 FPS
over time. As time progresses, there are more played waveform samples, which are
expensive gradient fills and take more CPU time than unplayed samples. The CPU profiler
displays that 90% of CPU time is spent in the &lt;code class=&quot;language-text&quot;&gt;drawRect:&lt;/code&gt; method of our waveform view.&lt;/p&gt;
&lt;h2&gt;Reducing waveform renders&lt;/h2&gt;
&lt;p&gt;We needed a new approach that did not require redrawing the entire
waveform 60 times a second. The waveform samples never need to change in size, but only
in tint color. This makes it possible to render the waveform once, and apply it as an alpha mask
to a layer filled with a gradient on the left half, and white on the right half.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 620px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2d22fc1fcea6920ee8508b5de7306304/a5ed1/waveform_rendering_mask.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.677419354838705%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACeElEQVQoz12Tz0tUURTH38gNKkLrD7BMKw1KM6uVumkRFVlNb3xjkwYamdnM2GiiUYtaZFAxv5pRQgi0cSG1U6ISsnIxGQwWmAX2axYSBtWgOMbk+3Tf00S98OF7zr2cL4d7z1VoFOhLoEkweX4emlaheyxwOQP9xzjGSvz+RTKZJJVKMT09zcqlzF0QzDmlmeSvjHEJXqmCN5ogXm3kFhLOdXweHmA49pZw6C7j4+OMjY0xOTlpmkxNTTExMUF/fz8KdbLI4NyC1guixwXd+wUvjwpiVgsD6noe93Zxtq6e1pYWenp6iEQiDA4OEovF8Pv9BAIBXC4XysgxwTvJ6yPS4JDsrEzQVSpQNwsu5QuuFijcLNmA+8wprDaNmupqKisraW5upqGhgba2NlpbW6mtrTX3FWeeoCpHUClxZAtO58zn5VmCE5sE9qw0HFvXoJUdxH7SgVZejqZpi9hsNhwOBxUVFaYqmiyyZsriFdg2zqNmpqFmrUY9fAC1XENV1WUYhoZarVbsdjtK4xaBW3bUsAQjdy2oOzsNd+4a3DVVuC565D05zbv6j9vtNnE6nXg8HhT/dsGdvOV4JUG5b5z589Lw56/Ff/0KwfYOAvIBgsGg+QgGPp9vMQ6FQijPCwXPdgmeFAj68uf10Q7Bbdl5OFdwb5uF+4XptN+4xi2vj2DAj9frJRwOmwadnZ10dHQsmiuUyFExKF5Axh+KBC92CkalxossfCzOYORpH5Heh0QedDM0NEQ0GmV0dJR4PG7mxggZo6Toe+VQL4F9gu+y45+7BSkj32NBL03nz6f3JHX49vWL+VNmZmaYnZ1d/CG6rpNIJPgHgFEOs9xpvk4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Waveform mask with background colors&quot;
        title=&quot;Waveform mask with background colors&quot;
        src=&quot;/blog/static/2d22fc1fcea6920ee8508b5de7306304/a5ed1/waveform_rendering_mask.png&quot;
        srcset=&quot;/blog/static/2d22fc1fcea6920ee8508b5de7306304/9ec3c/waveform_rendering_mask.png 200w,
/blog/static/2d22fc1fcea6920ee8508b5de7306304/c7805/waveform_rendering_mask.png 400w,
/blog/static/2d22fc1fcea6920ee8508b5de7306304/a5ed1/waveform_rendering_mask.png 620w&quot;
        sizes=&quot;(max-width: 620px) 100vw, 620px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This increases the FPS to the maximum of 60 with approximately 50% GPU usage. The CPU usage
dropped to approximately 10%.&lt;/p&gt;
&lt;h2&gt;Using system animations&lt;/h2&gt;
&lt;p&gt;We can now stop using the &lt;code class=&quot;language-text&quot;&gt;AVPlayer&lt;/code&gt; timer
callback for waveform progress and use a standard &lt;code class=&quot;language-text&quot;&gt;UIView&lt;/code&gt; animation instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;UIView animateWithDuration&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CMTimeGetSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                      delay&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                    options&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;UIViewAnimationOptionCurveLinear
                 animations&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;waveformView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;progress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                completion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This drops CPU usage down to approximately 0%, without noticeably increasing the GPU usage.
This would seem to be an obvious win, unfortunately it introduces some visual glitches.
Watching the animation on longer tracks shows an amplitude phasing effect on the waveform
samples.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pixel-aligned waveform rendering&lt;/th&gt;
&lt;th&gt;Interpolated waveform rendering&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 300px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c2c3d7ad79a9249aa3e68e90dfb478dc/135ae/waveform_rendering_sharp.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 113.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAAD/UlEQVQ4y32U62/bVBjG/Y8hhLpJgBiIAYKOaUhcduED4hsSIAErSEggLqPQ9bKubdrl3jZJL861aW5tEsdJmqxZm5sd23GcNOklbdJui1/e462M7sOko6PX9vmd57H9noey5RsW/pGVO7Hld8wVeG6YTgvL2csngwqsOR0PJFu+5U2EDVUw873TZyoOq/B0nZ6DRQl8Cuh5cMowL4KRB2pt1RiMLFm4h+GQzb7dMApg4R+bedUsgKEC/jrM4h0BbCIE68AfAW6HWzgk4gVhE8LoJ+abZDxjBgmMYs8sqK50xlzqrijgqYFbhuwe3N8D7gjmBHDJEGoAPiLKgTV6vtBKuIaS9J8Ie9moY0v2MWFPPBQUD3x1YFvAHUJuH0qHQFch0oBEixih1lcN/qgnHJzdnP8u7hm28CdZ+0A4aI35dMHAXETaR+epXVXsQLUDhTYRLLYhuQvhBlChsGNz7hth8j1x4p2s/WbMO5F1DKwFTPft36+7Rth6R+hA/kDFWenCdpu4qHWJBQJHgrP8VL889qp4921u+oo0/lbB+Hlu7uvc/LdbM1cTzV7rIS5VhSPCFA8BC1mDt/aBKuqvFfWfVcdeFycuFg3XayN9wsS7RcMN6e5Fbqq/KEsKwm2i/ASWOgRG59UuUNU7bxQM1+VRolzUX62NnK9MXSrf+1gZfqWsu8xLJeURwr3/YPEsfOEsfK4y9UGJwH2c7kNOKj8Hn1Uef7NguCGPvUZs66/VRs9XdP2le58oo+c43WVO4l4Eo2ZR/2ltuE8av0AEb7+M71yeuVIfeomffB9t1x9Dud1Dt8ox+U7I1LRCPgYKRfBXlac/wq+FHxmt5k1fPLB+xesubZm/3BYFvovtoebbT/sENcuHsKk1DMU6B6MrM9hb2GFR33Rq+XfGPYqtmlr6Le65Exd3UvsQ21GZFrYKRHfIf05qBc7UxsLPjHsk4/gxvfgL4xrO2m6mlv9IOIeyth/Y5Vus2MgeQKKpIontzTQhvQsZrdjY0+C4ZxxnTWos4/gpSd8iu9gHEvQgKyiZA1yqJjUm3iT6G1pB4FDIztJ/EeWlXxOu26jMOv+OeSfRDkMPrstHm21gWs/gtAYzmnnKnUqEg/OMe5h1/hMJWP1RPx5Sf9QXDtlXIs4V+STegvUdIohwrAmBOoGx8NaA8jEReiODYRIML+HZxDDwJqILOc6dStLpjFs4WpbJ6UMYjyFuhHkSqpMt3Ah7mYibZTA3VuIhD8sYRRJApkrPsa1YysdOWV2QCLBaJwGAVjFYliSgZcD7lCuVXsgJs+Uund0yCfAswAQSd5g+Fi33cDi0DMNisUq2IBmGuUmW8j0Ld/I/+DQ9+Rdd/gtroWNMv8niMgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Waveform on pixel boundary&quot;
        title=&quot;Waveform on pixel boundary&quot;
        src=&quot;/blog/static/c2c3d7ad79a9249aa3e68e90dfb478dc/135ae/waveform_rendering_sharp.png&quot;
        srcset=&quot;/blog/static/c2c3d7ad79a9249aa3e68e90dfb478dc/9ec3c/waveform_rendering_sharp.png 200w,
/blog/static/c2c3d7ad79a9249aa3e68e90dfb478dc/135ae/waveform_rendering_sharp.png 300w&quot;
        sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 300px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/98f12e069c62b0789db0caa8b12b97ff/135ae/waveform_rendering_blurred.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 113.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAAsSAAALEgHS3X78AAADzklEQVQ4y4WU2W8bVRTG/bchEFUogrJUvLQCRFsBz5VaXioEpVAkaKkEpNnbtN5t4n2364mdTOzxmtWN15nx2EntxLHjFjfx4buTRk3gAelq9N2Z8zvfmTtzjmbuactUeWmqDGyFZ8Ya/WsZjoXp9PZoaSJ82LlSsha7oSSnk8lYHR4/gxiaxVdxuio5ZAo3SVslT51sEumrpIlz1hj3F1LGOYut0NSLZKoeGKuHRpF0NYo0yYo7Is1JFN2i2j5ZRAo0WCLmHFNhvUSJ4GTSPwZzg3hokMiXy5uKPVj5FRad26GVXRL3WS44x7ZZFcx5PmazlPfTnnsZ9x1tnQIp3rbRCAnxIB/hpG6gSek2lbq0ukvVHrnrFN9md7gtwFFzdNHDLbg3zFeTgVFj7WDZ9l2cM/IRbSyij8l74S3YDoHV+1Tusio2u6yQecDcgmfN+k1l9kJ94uzK3Ld8eHZl7sbCE33e+WPCdTfZfF7rU7E7rPSo+QKCEi0mkIvBqLk2c16aOqeMj1RmL4rTH5Uef7FuubbsuLn58EJi+6A1QCiDG8+Z85HAdb1DmqLuy/KjT8XpD+sT7xR1V+BfnxjZ1H9Ve/AJbj5Vms0BQo/hHtVUgSOQ9kmjjJ+B1ZFzUXtZnjgrT50raS817r8lzpwv16XGSeceK/gIFlV45CQMZ3nyPcDK2Blx5uPSf+DaKXgC8CVp+gMFZWuv1CfflafeL2kvoyI4/w/cuP8m3hnVos7S48+Vsbfx8uVHnzVG30A5KLv5EgD7VDhkpAADgZOT+qQBuWa5hqMCsGa9Dh7Fr1mul2cvbuq/Xq83Kn0qdIYbe4zc6FBh79VR42tr0p7f+NADwfcH/jA+/DDlvZfy/Q6R9txN+kd5eSezi287XGpRpk24JlTBPyOhRZq8/QfB92fW+bMqRnOOWznnbYi87fuM6xdBbi93KN0aptq0vEO4plUBMttW4YR/DHE5x0+JwHjWeRuJ0CF5+0386oLcynco1RoKbcqDaTMeIqn64w9zpt13AMATxefst7BdCs0kglMp969xpb+6B+A1nD6RRePNZmKcBaFg0CThRHQxoossBrm468m8I6QM8J5YwrEhOpyJNgUbpAmklsDbCluxeVt0wY3GDgq8Y63iz2S86axP7LsU1gPwEdQUdpk1I5ZfhfmgsIjuh2dASADG9DGIQ0wVc6nvVYaIdsrMEEB2h00SbL0KGyYoe9m1WsIA9OTXDSK9HmAiG3eYPiZ17hmqZJfY9IAGhnnCZpi5MmCh1UNz5e/Tw5FNQjAnx6X+9PYfln9i8+rEZaoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Waveform inbetween pixels&quot;
        title=&quot;Waveform inbetween pixels&quot;
        src=&quot;/blog/static/98f12e069c62b0789db0caa8b12b97ff/135ae/waveform_rendering_blurred.png&quot;
        srcset=&quot;/blog/static/98f12e069c62b0789db0caa8b12b97ff/9ec3c/waveform_rendering_blurred.png 200w,
/blog/static/98f12e069c62b0789db0caa8b12b97ff/135ae/waveform_rendering_blurred.png 300w&quot;
        sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;While the mask is animated across the waveform view, the animation will not only be drawn
at integer pixel positions. When the mask is at a fractional pixel position, it causes the
edges of the mask to be interpolated across each of the neighboring pixels. This reduces the
alpha of the mask at the edge of each sample. This lower alpha reduces the amplitude of the
visible waveform samples. The overall effect is that as the mask is translated across the view,
the waveform samples animate between a sharp dark state and a lighter blurry state.&lt;/p&gt;
&lt;p&gt;We need a way to tell CoreAnimation to only move the mask to pixel-aligned
positions. This is possible by using a keyframe animation that is set to discrete calculation
mode, which does not interpolate between each keyframe.&lt;/p&gt;
&lt;p&gt;A high-level API was added for keyframe animations in iOS7, but the CoreAnimation interface
in this case is a bit clearer:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CAAnimation &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;keyframeAnimationFrom&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CGFloat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;start to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CGFloat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;end
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    CAKeyframeAnimation &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;animation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    	&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;CAKeyframeAnimation animationWithKeyPath&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;position.x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    CGFloat scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;UIScreen mainScreen&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    CGFloat increment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;copysign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    NSUInteger numberOfSteps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ABS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; increment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    NSMutableArray &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;positions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    	&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSMutableArray arrayWithCapacity&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;numberOfSteps&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSUInteger i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; numberOfSteps&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;positions addObject&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; increment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    animation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;values &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; positions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    animation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculationMode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; kCAAnimationDiscrete&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    animation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;removedOnCompletion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; YES&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; animation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Adding this animation to the mask layer produces the same animation, but only at
pixel-aligned positions. This has a performance benefit; animating longer tracks
produces animations with the FPS reduced to the maximum possible FPS for the number of
animation keyframes, which reduces the device utilization accordingly. In this case, the
distance of waveform animation is 680 pixels. If the played track is 10 minutes
long, the FPS is 680 animation pixels over 600 seconds ≈ 1 pixel position per second.
CoreAnimation schedules the animation frames intelligently, so that we only get a
1 FPS animation with correspondingly reduced 1% device utilization.&lt;/p&gt;
&lt;h2&gt;Increasing up-front cost for cheaper animations&lt;/h2&gt;
&lt;p&gt;The CPU performance is at an acceptable level, but 50% GPU usage is still high. Most
of this is due to the high cost of masking, which requires a texture blend on the GPU.
As we fetch a waveforms samples asynchronously for each track, initial render time for
the view is less important than performance during playback. Instead of masking the
waveform samples, they can be drawn twice: once in the unplayed
state, once in the played state with gradient applied. That way, each waveform is
contained in separate views that clip their subviews. To adjust the waveform position
within these windows, the bounds origin can be adjusted for each subview to slide over
its content, similar to how a &lt;code class=&quot;language-text&quot;&gt;UIScrollView&lt;/code&gt; works.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5f083ab7c0db99633e5ab91acc120e20/54e54/waveform_rendering_clip.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.41379310344827%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsSAAALEgHS3X78AAACqElEQVQoz4WSy08TURTG+W+MiUZ0SWLihriUBawUFkCicYELCQbxEUkgJBAQH00Ij9J5z/QNrZSSSB/TlloHC5RKEen0DjQqlhY6fU474+1QjHGh3z2Lu/h+ueee8zXIsuzz+VAUpWma+qdIktTr9ZlMWlYUSapANfwPJtUDRRMEwTDMcea0Wi5I5ZL0F0zWRalFUgRRv+AYg+tIHNMbTcdJvrqzUsqLNVhRFJZlEQRRG6u5GRwxo1M0gZEUbcDmVjX9zulhu3bcTKEeekpcHFH0faWDnYIk/4Z1DOwM0ozBhE673j6yzU3YZl+y4w/ifU3hF23c89b1wdsxbCS3pClZR3OJz2KxXId1CEoaTGefXJydjPXf3H5ya+tZ69eHN4SeJvC0BYzdFTS9wqrlZC8iRtayP79nxVyDIstenx/XTrteDQSHOrcGWmK9zQf3rh52NwqdjaDjCsAmBM87Yc0FPgYEzn+yty3yu6epo6wo1mBPIGjQjApdl4WOC0L7ReHOpUT7Nb7revx+8/7DloTHASLriXAQbHwQtkKnwpfc4a6YPsoXihCuugPBhckh0NO8/7gtPtzNv+6NawcT1FjC8oZf1oJPnoNNPwizPOfmOU82vlkAkXwmpcKK7GZ9FnQWLM3sryB7TiTqQHnn/O6SNryg3bTO8G7jhssWcC447VbP+5WTH8ly+luxWCiWSrWBeVkWRxGHgVikMT2JWyjMa5y30hhDEgaKsDGYkcbhHuAaTSbzcTpTqVbL5bIkSefT1ukIGAq1GLpWpLp0HCd0KAZ3oUOQsyCl02mYK0nNZz1hMCTQT+A4hqGI6oMstJrNJueyw+v1chwXi8WSySR8s3KuGuz3+3EcNxqNdrvd7XaHQqFoNAoASKVS+XwePgI9yrkqf+gXtqpwYEt+CTcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Clip view hierarchy&quot;
        title=&quot;Clip view hierarchy&quot;
        src=&quot;/blog/static/5f083ab7c0db99633e5ab91acc120e20/54e54/waveform_rendering_clip.png&quot;
        srcset=&quot;/blog/static/5f083ab7c0db99633e5ab91acc120e20/9ec3c/waveform_rendering_clip.png 200w,
/blog/static/5f083ab7c0db99633e5ab91acc120e20/c7805/waveform_rendering_clip.png 400w,
/blog/static/5f083ab7c0db99633e5ab91acc120e20/54e54/waveform_rendering_clip.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;You can offset the bounds origin of the left clip view by its width. The result is a
seamless view that looks exactly the same as the masking effect. Because we are only
moving views around in the hierarchy, redrawing does not need to occur and the same
keyframe animations can be applied to each of the clip views. After applying these
changes, the GPU utilization drops to less than 20% for 60 FPS animation. This was
considered fast enough, and is our final iteration of the waveform renderer.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Understanding the performance cost of drawing techniques on iOS can be
tricky. The best way to achieve acceptable performance is to start simple, profile
often, and iterate until you hit your target. We significantly improved performance
by profiling and identifying the bottleneck at each step, and most of the drawing
code from our initial naïve implementation survived to the final performant iteration.&lt;/p&gt;
&lt;p&gt;We obtained the biggest win by shifting work from the CPU to
the GPU. Any CoreGraphics drawing done using &lt;code class=&quot;language-text&quot;&gt;drawRect:&lt;/code&gt; uses the CPU to fill
the layers contents. This is often unavoidable, but if the content
seldom changes, the layer contents can be cached and manipulated by
CoreAnimation on the GPU. After the drawing reduces to manipulating &lt;code class=&quot;language-text&quot;&gt;UIView&lt;/code&gt; properties,
nearly all of the work can be performed using animations, thereby reducing the amount
of CPU view state updates.&lt;/p&gt;
&lt;p&gt;It is important to consider GPU usage, but this is harder to understand intuitively.
Profiling with Instruments provides helpful insight, especially the OpenGL ES
Driver template, which shows animation FPS and the percentage utilization of the GPU. The main
tricks here are to reduce blending and masking, ideally using opaque layers wherever
possible. The simulator option “Color Blended Layers” can be useful to identify
where you have unnecessary overdraw. For more details see the &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/developertools/conceptual/instrumentsuserguide/MeasuringGraphicsPerformanceinYouriOSDevice/MeasuringGraphicsPerformanceinYouriOSDevice.html&quot;&gt;Apple documentation&lt;/a&gt; and WWDC videos.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building the new SoundCloud iOS application — Part I: The reactive paradigm]]></title><description><![CDATA[Recently, SoundCloud launched the new iOS application which was a complete
rewrite of the existing iOS application. The Mobile engineering…]]></description><link>https://developers.soundcloud.com/blog/building-the-new-ios-app-a-new-paradigm</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-the-new-ios-app-a-new-paradigm</guid><pubDate>Mon, 07 Jul 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, SoundCloud launched the new iOS application which was a complete
rewrite of the existing iOS application. The Mobile engineering team saw this as an
opportunity to build a solid foundation for the future of SoundCloud on iOS
and to experiment with new technologies and processes at the same time.&lt;/p&gt;
&lt;p&gt;In the world of mobile, you deal with data, errors, threads and concurrency a
lot. The common scenario starts with a user tapping on the screen. The
application jumps off of the main UI thread, does some I/O related work, some
database-related operations along with some transformations to the data. The
application then jumps back to the UI thread to render some new information
onto the screen.&lt;/p&gt;
&lt;p&gt;Both the Android and iOS platforms provide some tools to deal with the
different aspects of this scenario yet they are far from ideal. Some of
them do not provide much in terms of error handling, which forces you to write
boiler-plate code while some of them force you to deal with low-level
concurrency primitives. You might have to add some additional libraries to
your project so that you do not have to write filtering and sorting predicate
code.&lt;/p&gt;
&lt;p&gt;We knew early on we wanted to avoid these issues and thus came into the
picture the functional reactive paradigm and &lt;a href=&quot;https://github.com/ReactiveCocoa/ReactiveCocoa&quot;&gt;Reactive Cocoa&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In short, &lt;a href=&quot;https://github.com/ReactiveCocoa/ReactiveCocoa&quot;&gt;Reactive Cocoa&lt;/a&gt; allows you to create composable, event-driven
(finite or infinite) streams of data while making use of functional composition
to perform transformations on those streams. &lt;a href=&quot;http://en.wikipedia.org/wiki/Erik_Meijer_(computer_scientist)&quot;&gt;Erik Meijer&lt;/a&gt; is
best known in this space with his Reactive Extensions on the .NET
platform which also spawned the JVM based implementation &lt;a href=&quot;https://github.com/Netflix/RxJava&quot;&gt;RxJava&lt;/a&gt;. By
adopting this paradigm, we now have a uniform way of dealing with data
models and operators that can apply transformations on those data models while
taking care of low level concurrency primitives so that one does not have to be
concerned about threads or the difficult task of concurrent programming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s take an example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Like most mobile applications, the SoundCloud iOS application is a typical case of an
API client with local storage. It fetches JSON data from the API via HTTP and
parses it into API model objects. The persistent store technology we use is Core
Data. We decided early on that we wanted to isolate the API from our storage
representation so there is a final mapping step involved where we convert API
models to Core Data models.&lt;/p&gt;
&lt;p&gt;We break this down into smaller units of work: we have&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Execute a network request. Parse the JSON response.&lt;/li&gt;
&lt;li&gt;Transform JSON objects into API model objects.&lt;/li&gt;
&lt;li&gt;Transform API model objects into Core Data models.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;1. Executing the network request&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For simplicity, assume that the network-access layer
implements the following method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;executeRequest&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSURL &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We do not pass in any delegates or callback blocks, we just give it an
&lt;code class=&quot;language-text&quot;&gt;NSURL&lt;/code&gt; and get back a &lt;code class=&quot;language-text&quot;&gt;RACSignal&lt;/code&gt; representing a possible asynchronous operation,
or &lt;a href=&quot;http://en.wikipedia.org/wiki/Futures_and_promises&quot;&gt;future&lt;/a&gt;. To obtain the data from that operation, we can &lt;em&gt;subscribe&lt;/em&gt; to the signal
using &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACSignal.html#//api/name/subscribeNext:error:completed:&quot;&gt;subscribeNext:error:completed:&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACDisposable &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;subscribeNext&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;nextBlock
                           error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSError &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;errorBlock
                       completed&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;completedBlock&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You might recognize the familiar-looking error and success-callback blocks from
other asynchronous APIs. This is where some of Reactive Cocoa’s and FRP’s
strengths lie as we shall see later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Parsing the JSON response&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the network request has been made, the JSON response needs to be parsed into an API model
representation. For this we use a thin layer around Github’s &lt;a href=&quot;https://github.com/Mantle/Mantle&quot;&gt;Mantle&lt;/a&gt; library, which wraps parsing
in a &lt;code class=&quot;language-text&quot;&gt;RACSignal&lt;/code&gt; and pushes the result (or error) to the subscriber:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;parseResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;data
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;RACSignal createSignal&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;RACSubscriber&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; subscriber&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    NSError &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nil&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    id apiModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MTLJSONAdapter modelOfClass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;ApiTrack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;class
                            fromJSONDictionary&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;data
                                         error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subscriber sendError&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subscriber sendNext&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;apiModel&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subscriber sendCompleted&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To achieve the composition of operations that we have mentioned earlier, we
wrapped the functionality of existing libraries with signals, where appropriate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Persisting the API model with Core Data&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In our architecture, the database represents the single source of truth. Therefore
to show tracks to the user we first need to store them as Core Data objects. We have a
collection of adapter classes that are responsible for mapping API model objects to Core Data
model objects. An &lt;code class=&quot;language-text&quot;&gt;ApiTrackAdapter&lt;/code&gt; might look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;adaptObject&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ApiTrack &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;apiTrack
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt; findOrBuildTrackWithUrn&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;apiTrack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                           map&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CoreDataTrack &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;coreDataTrack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      coreDataTrack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; apiTrack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// set other properties&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; coreDataTrack&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Putting it all together&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We now have the building blocks to issue a network request, parse the JSON, and
store it as a Core Data object. RAC makes it very easy to compose the
individual methods functionally by feeding the output of each operation as an
input to the next one. The following example uses &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACStream.html#//api/name/flattenMap:&quot;&gt;flattenMap:&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;loadAndStoreTrack&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSURL &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;url
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;requestHandler executeRequest&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; flattenMap&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;parser parseResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; flattenMap&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ApiTrack &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;adapter adaptObject&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;flattenMap:&lt;/code&gt; method maps or transforms values emitted by a signal and
produces a new signal as a result. In this example, the newly created signal
returned by &lt;code class=&quot;language-text&quot;&gt;loadAndStoreTrack:&lt;/code&gt; would either return the adapted Core Data
track object &lt;em&gt;or&lt;/em&gt; error if any of the operations failed. In addition to
&lt;code class=&quot;language-text&quot;&gt;flattenMap:&lt;/code&gt;, there is a whole range of predefined functional operators like
&lt;code class=&quot;language-text&quot;&gt;filter:&lt;/code&gt;or &lt;code class=&quot;language-text&quot;&gt;reduce:&lt;/code&gt; that can be applied to signals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RAC Schedulers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We left out one powerful feature of RAC which is the ability to parametrize
concurrency. To ensure that the application stays responsive, we want to perform
the network I/O and model parsing in a background queue.&lt;/p&gt;
&lt;p&gt;Core Data operations are different, we do not have a choice there. They have to
be executed on a predefined private queue, otherwise we risk creating deadlocks
in our application.&lt;/p&gt;
&lt;p&gt;With the help of &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACScheduler.html&quot;&gt;RACScheduler&lt;/a&gt; we can easily control where the side-effects
of a signal are performed by simply calling &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACSignal.html#//api/name/subscribeOn:&quot;&gt;subscribeOn:&lt;/a&gt; on it with
a custom scheduler implementation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;loadAndStoreTrack&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSURL &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;url
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;requestHandler executeRequest&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; flattenMap&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;parser parseResponse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; flattenMap&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ApiTrack &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;adapter adaptObject&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                       subscribeOn&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;CoreDataScheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, we use a scheduler that is aware of the current Core Data context to ensure
that &lt;code class=&quot;language-text&quot;&gt;adaptObject:&lt;/code&gt; is executed on the right queue by wrapping everything internally with
&lt;a href=&quot;https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html#//apple_ref/occ/instm/NSManagedObjectContext/performBlock:&quot;&gt;performBlock:&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If we want to update our UI with the title of the track we just fetched, we could do
something like the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;trackService loadAndStoreTrack&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;trackUrl&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; subscribeNext&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;trackView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSError &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// handle errors&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To ensure that this final update happens on the UI thread we can tell RAC to
deliver us the information back on the main thread by using the &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACSignal.html#//api/name/deliverOn:&quot;&gt;deliverOn:&lt;/a&gt;
method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;trackService loadAndStoreTrack&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;trackUrl&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      deliverOn&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;RACScheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mainThreadScheduler&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  subscribeNext&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Track &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;trackView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSError &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// handle errors&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By breaking down each operation within this common scenario into isolated units
of work, it becomes easier to perform the operations we need on the desired
threads by taking advantage of Reactive Cocoa’s scheduling abilities. The
functional reactive paradigm has also helped us to compose these independent
operations one after another by using operators such as &lt;code class=&quot;language-text&quot;&gt;flattenMap&lt;/code&gt;. Although
adopting FRP and ReactiveCocoa has had its difficulties, we have learned many lessons along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steep learning curve&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Adopting FRP requires a change of perspective, especially
for developers who are not used to a functional programming style. Methods do not return values
directly, they return intermediate objects (signals) that take callbacks. This
can lead to more verbose code, especially when the code is heavily nested which
is common when doing more complex things with RAC.&lt;/p&gt;
&lt;p&gt;Therefore, it is important to have short and well-named methods, for example a method
signature like &lt;code class=&quot;language-text&quot;&gt;-(RACSignal *)signal&lt;/code&gt; does not communicate anything about the type of values
the caller is going to receive.&lt;/p&gt;
&lt;p&gt;Another problem is the sheer number of methods or operators defined on a base classes like
&lt;code class=&quot;language-text&quot;&gt;RACStream&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;RACSignal&lt;/code&gt;. In practice only a few (like &lt;code class=&quot;language-text&quot;&gt;flattenMap:&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;filter:&lt;/code&gt;) are
used on a regular basis, but the remaining 80% tend to confuse developers who are
new to the framework.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Memory management can be problematic because of RAC’s heavy use of blocks which
can easily lead to retain cycles. They can be avoided by breaking the cycle with
weak references to &lt;code class=&quot;language-text&quot;&gt;self&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;@weakify&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;@strongify&lt;/code&gt; macros).&lt;/p&gt;
&lt;p&gt;One of RAC’s promises is to reduce the amount of state you need to keep around
in your code. This is true but you still need to manage the state introduced by
the framework itself, which comes in the form of &lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACDisposable.html&quot;&gt;RACDisposable&lt;/a&gt;, an object
returned as a result of signal subscription. A common pattern we introduced is
to bind the lifetime of the subscription to the lifetime of the object with
&lt;a href=&quot;http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACDisposable.html#//api/name/asScopedDisposable&quot;&gt;asScopedDisposable&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disposable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;signal subscribeNext&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* stuff */&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; asScopedDisposable&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Overdoing it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is easy to fall into the trap of trying to apply FRP to every single problem
one encounters (also known as the golden hammer syndrome), thereby unnecessarily
complicating the code. Defining clear boundaries and rules between the reactive
and non-reactive parts of the code base is important to minimize verbosity and
to use the power of FRP And Reactive Cocoa where appropriate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are inherent performance problems within RAC. For example, a simple
imperative &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; loop is guaranteed to execute much faster than &lt;code class=&quot;language-text&quot;&gt;flattenMap:&lt;/code&gt;
which introduces a lot of internal method dispatching, object allocation, and
state handling.&lt;/p&gt;
&lt;p&gt;In most cases this overhead is not noticeable, especially when I/O latency is involved,
as in the preceding examples.&lt;/p&gt;
&lt;p&gt;However in situations where performance really matters, such as fast UI rendering, it
makes sense to avoid RAC completely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We found this to be a non-issue if your application components are well
designed and individually tested. Backtraces tend to get longer but this can
be alleviated with some extra tooling like custom LLDB filters. A healthy
amount of debug logging across critical components also does not hurt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Testing a method that returns a &lt;code class=&quot;language-text&quot;&gt;RACSignal&lt;/code&gt; is more complicated than testing
code that returns plain value objects, but it can be made less painful with a
testing library that supports custom matchers. We have created a collection of
matchers for &lt;code class=&quot;language-text&quot;&gt;expecta&lt;/code&gt; that lets us write concise tests. For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;RACSignal &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;signal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subject executeRequest&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;signal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendSingle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;foo&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We found that adopting FRP tends to produce easily testable components because
they are generally designed to perform one single task, which is to produce an output
given a specific input.&lt;/p&gt;
&lt;p&gt;It took a while for the team to get up to speed with FRP and Reactive Cocoa and to learn
for which parts of the application it can be used most effectively. Right now it has become
an indispensable part of our mobile development efforts, both on Android and
iOS. The functional reactive approach has made it easier to build complex
functionality out of smaller pieces whilst simplifying concurrency and error handling.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Real-Time Counts with Stitch]]></title><description><![CDATA[Here at SoundCloud, in order to provide counts and a time series of counts in real time, we created something called Stitch. Stitch was…]]></description><link>https://developers.soundcloud.com/blog/real-time-counts-with-stitch</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/real-time-counts-with-stitch</guid><pubDate>Thu, 03 Jul 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here at SoundCloud, in order to provide counts and a time series of counts in real time, we created something called Stitch.&lt;/p&gt;
&lt;p&gt;Stitch was initially developed to provide timelines and counts for our &lt;a href=&quot;https://blog.soundcloud.com/2014/03/04/new-soundcloud-stats/&quot;&gt;stats pages&lt;/a&gt;, which are where users can see which of their tracks are played and when.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9e68a73a9b66c964410db209cf4ee787/d6590/soundcloud_stats_screenshot.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.0289990645463%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAG7ZVHGTq//xAAbEAABBAMAAAAAAAAAAAAAAAADAAECBBMkM//aAAgBAQABBQIY2k2GKIQgyV+astsf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHRAAAgIBBQAAAAAAAAAAAAAAAQIAIRARMWGBkf/aAAgBAQAGPwLUk7ymb2MqmuZ2cPP/xAAeEAACAQMFAAAAAAAAAAAAAAAAAREhMUFRYYGx8P/aAAgBAQABPyFnn5ZeopKDkILwe92L/qo8iHIlddI//9oADAMBAAIAAwAAABB8H//EABcRAAMBAAAAAAAAAAAAAAAAAAABEVH/2gAIAQMBAT8QcKsP/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERUf/aAAgBAgEBPxBUj0//xAAdEAEAAwABBQAAAAAAAAAAAAABABExQVFhcdHw/9oACAEBAAE/ELtQdBFFDIWnaj3RdM1W0Dz5nyOqYhrU6JL/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud Stats Screenshot&quot;
        title=&quot;SoundCloud Stats Screenshot&quot;
        src=&quot;/blog/static/9e68a73a9b66c964410db209cf4ee787/a296c/soundcloud_stats_screenshot.jpg&quot;
        srcset=&quot;/blog/static/9e68a73a9b66c964410db209cf4ee787/f544b/soundcloud_stats_screenshot.jpg 200w,
/blog/static/9e68a73a9b66c964410db209cf4ee787/41689/soundcloud_stats_screenshot.jpg 400w,
/blog/static/9e68a73a9b66c964410db209cf4ee787/a296c/soundcloud_stats_screenshot.jpg 800w,
/blog/static/9e68a73a9b66c964410db209cf4ee787/c35de/soundcloud_stats_screenshot.jpg 1200w,
/blog/static/9e68a73a9b66c964410db209cf4ee787/8179c/soundcloud_stats_screenshot.jpg 1600w,
/blog/static/9e68a73a9b66c964410db209cf4ee787/d6590/soundcloud_stats_screenshot.jpg 2138w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Stitch is a wrapper around a Cassandra database. It has a web application that provides read access to the counts through an HTTP API. The counts are written to Cassandra in two distinct ways, and it’s possible to use either one or both of them:&lt;/p&gt;
&lt;dl&gt;
  &lt;dt&gt;Real Time&lt;/dt&gt;
  &lt;dd&gt;For real-time updates, Stitch has a processor application that handles a stream of events coming from a broker and increments the appropriate counts in Cassandra.&lt;/dd&gt;
  &lt;dt&gt;Batch&lt;/dt&gt;
  &lt;dd&gt;The batch part is a MapReduce job running on [Hadoop] that reads event logs and calculates the overall totals, and then bulk loads this into Cassandra.&lt;/dd&gt;
&lt;dl&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The difficulty with real-time counts is that incrementing is a non-idempotent operation, which means that if you apply the same increment twice, you get a different value than if you would only apply it once. That said, if an incident affects our data pipeline and the counts are wrong, we can’t fix it by simply re-feeding the day’s events through the processors; if we did, we would risk double counting.&lt;/p&gt;
&lt;h2&gt;Our First Solution&lt;/h2&gt;
&lt;p&gt;Initially, Stitch only supported real-time updates and addressed this problem with a MapReduce job, &lt;code class=&quot;language-text&quot;&gt;The Restorator&lt;/code&gt;, which performed the following actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculated the expected totals.&lt;/li&gt;
&lt;li&gt;Queried Cassandra to get the values it had for each counter.&lt;/li&gt;
&lt;li&gt;Calculated the increments needed to apply to fix the counters.&lt;/li&gt;
&lt;li&gt;Applied the increments.&lt;p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Meanwhile, to stop the sand shifting under its feet, &lt;code class=&quot;language-text&quot;&gt;The Restorator&lt;/code&gt; needed to coordinate a locking system between itself and the real-time processors. This was so that the processors didn’t try to simultaneously apply increments to the same counter, which would result in a race condition. To deal with this, &lt;code class=&quot;language-text&quot;&gt;The Restorator&lt;/code&gt; used &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;ZooKeeper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As you can probably tell, this setup was quite complex, and it often took a long time to run. But despite this, it worked.&lt;/p&gt;
&lt;h2&gt;Our Second Solution&lt;/h2&gt;
&lt;p&gt;Luckily, a new use case emerged: a team wanted to run Stitch purely in batch. This is when we added the batch layer, and we used this as an opportunity to revisit the way Stitch was dealing with the non-idempotent increments problem. We evolved to a &lt;a href=&quot;http://lambda-architecture.net/&quot;&gt;Lambda Architecture&lt;/a&gt;-style approach, where we combined a fast real-time layer for a possibly inaccurate but immediate count with a batch slow layer for an accurate but delayed count. The two sets of counts are kept separately and updated independently, possibly even living on different database clusters, and it is up to the reading web application to return the correct version when queried. At its most naive, it returns the batch counts instead of the real-time counts, whenever they exist.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To find out how Stitch has evolved over the years, you can read this updated post, &lt;a href=&quot;https://developers.soundcloud.com/blog/keeping-counts-in-sync&quot;&gt;Keeping Counts In Sync&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 649px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/49e8022a8f73567d4a3dc2659c4ff0f8/548e0/stitch_diagram.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 89.06009244992296%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMCBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAA//aAAwDAQACEAMQAAAB7NAtsEnczQ0v/8QAGxAAAgIDAQAAAAAAAAAAAAAAABEBEAIDITH/2gAIAQEAAQUCw4Q6cCEa/a//xAAVEQEBAAAAAAAAAAAAAAAAAAABIP/aAAgBAwEBPwFj/8QAFxEAAwEAAAAAAAAAAAAAAAAAAQMQMv/aAAgBAgEBPwEReZ//xAAaEAACAwEBAAAAAAAAAAAAAAAAARARMQJR/9oACAEBAAY/Amm7Mijw06n/xAAeEAACAQMFAAAAAAAAAAAAAAAAAREQIVFhcYGRof/aAAgBAQABPyGT9CGZuYYvSSxps2cBah6K/wD/2gAMAwEAAgADAAAAEOTQwP/EABcRAQEBAQAAAAAAAAAAAAAAAAEQETH/2gAIAQMBAT8QSusez//EABYRAQEBAAAAAAAAAAAAAAAAAAERIP/aAAgBAgEBPxCAhg//xAAcEAEBAAICAwAAAAAAAAAAAAABEQAxEFEhcYH/2gAIAQEAAT8Q8wi00mGEo9l4WsA1cEiAXe2KG/3gLQpPzn//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Stitch Diagram&quot;
        title=&quot;Stitch Diagram&quot;
        src=&quot;/blog/static/49e8022a8f73567d4a3dc2659c4ff0f8/548e0/stitch_diagram.jpg&quot;
        srcset=&quot;/blog/static/49e8022a8f73567d4a3dc2659c4ff0f8/f544b/stitch_diagram.jpg 200w,
/blog/static/49e8022a8f73567d4a3dc2659c4ff0f8/41689/stitch_diagram.jpg 400w,
/blog/static/49e8022a8f73567d4a3dc2659c4ff0f8/548e0/stitch_diagram.jpg 649w&quot;
        sizes=&quot;(max-width: 649px) 100vw, 649px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Thanks go to &lt;a href=&quot;https://github.com/kim&quot;&gt;Kim Altintop&lt;/a&gt; and &lt;a href=&quot;https://github.com/omidaladini&quot;&gt;Omid Aladini&lt;/a&gt;, who created Stitch, and &lt;a href=&quot;https://github.com/johnglover&quot;&gt;John Glover&lt;/a&gt;, who continues to work on it with me.&lt;/p&gt;
&lt;p&gt;If this sounds like the sort of thing you’d like to work on too, check out our &lt;a href=&quot;https://soundcloud.com/jobs&quot;&gt;jobs page&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building Products at SoundCloud—Part III: Microservices in Scala and Finagle]]></title><description><![CDATA[In the first two parts of this series, we talked about how SoundCloud started breaking away from a monolithic Ruby on Rails application into…]]></description><link>https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-3-microservices-in-scala-and-finagle</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-3-microservices-in-scala-and-finagle</guid><pubDate>Fri, 13 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the first two parts of this series, we talked about how SoundCloud started breaking away from a monolithic Ruby on Rails application into a microservices architecture. In this part we will talk a bit more about the platforms and languages in which we tend to write these microservices.&lt;/p&gt;
&lt;p&gt;At the same time that we started the process of building systems outside the &lt;em&gt;Mothership&lt;/em&gt; (our Rails monolith) we started breaking our large team of engineers into smaller teams that focused on one specific area of our platform.&lt;/p&gt;
&lt;p&gt;It was a phase of high experimentation, and instead of defining which languages or runtimes these teams should use, we had the rule of thumb &lt;em&gt;write it in whatever you feel confident enough putting in production and being on-call for&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This led to a &lt;a href=&quot;http://en.wikipedia.org/wiki/Cambrian_explosion&quot;&gt;Cambrian Explosion&lt;/a&gt; of languages, runtimes and skills. We had systems being developed in everything from Perl to Julia, including Haskell, Erlang, and node.js.&lt;/p&gt;
&lt;p&gt;While this process proved quite productive in creating new systems, we started having problems when maintaining them. The &lt;a href=&quot;http://en.wikipedia.org/wiki/Bus_factor&quot;&gt;bus factor&lt;/a&gt; for several of our systems was very low, and we eventually decided to consolidate our tools.&lt;/p&gt;
&lt;p&gt;Based on the expertise and preferences across teams, and an assessment of the industry and our peers, we decided to stick to the JVM and select JRuby, Clojure, and Scala as our company-wide supported languages for product development. For infrastructure and tooling, we also support Go and Ruby.&lt;/p&gt;
&lt;p&gt;Turns out that selecting the runtime and language is just one step in building products in a microservices architecture. Another important aspect an organization has to think about is what &lt;em&gt;stack&lt;/em&gt; to use for things like RPC, resilience, and concurrency.&lt;/p&gt;
&lt;p&gt;After some research and prototyping, we ended up with three alternatives: a pure &lt;em&gt;Netty&lt;/em&gt; implementation, the &lt;em&gt;Netflix stack&lt;/em&gt;, and the &lt;em&gt;Finagle stack&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Using pure &lt;a href=&quot;http://netty.io/&quot;&gt;Netty&lt;/a&gt; was tempting at first. The framework is well documented and maintained, and the support for HTTP, our main protocol for RPC, is good. After a while, though, we found ourselves implementing abstractions on top of it to do basic things for the concurrency and resilience requirements of our systems. If such abstractions were to be required, we would rather use something that exists than re-invent the wheel.&lt;/p&gt;
&lt;p&gt;We tried the Netflix stack, and a while back &lt;a href=&quot;http://blog.josephwilk.net/clojure/building-clojure-services-at-scale.html&quot;&gt;Joseph Wilk wrote about our experience with Hystrix and Clojure&lt;/a&gt;. &lt;a href=&quot;https://github.com/Netflix/Hystrix&quot;&gt;Hystrix&lt;/a&gt; does very well in the resilience and concurrency requirements, but its API based on the &lt;em&gt;Command&lt;/em&gt; pattern was a turnoff. In our experience, Hystrix commands do not compose very well unless you also use RxJava, and although we use this library for several back-end systems and our Android application, we decided that the reactive approach was not the best for all of our use cases.&lt;/p&gt;
&lt;p&gt;We then started trying out &lt;a href=&quot;http://twitter.github.io/finagle/&quot;&gt;Finagle&lt;/a&gt;, a protocol-agnostic RPC system developed by Twitter and used by many companies our size. Finagle does very well in our three requirements, and its design is based on a familiar and extensible &lt;em&gt;&lt;a href=&quot;http://www.eaipatterns.com/PipesAndFilters.html&quot;&gt;Pipes-and-Filters&lt;/a&gt; meets &lt;a href=&quot;http://docs.scala-lang.org/overviews/core/futures.html&quot;&gt;Futures&lt;/a&gt;&lt;/em&gt; model.&lt;/p&gt;
&lt;p&gt;The first issue we found with Finagle is that, as opposed to the other alternatives, it is written in Scala, therefore the language runtime jar file is required even for a Clojure or JRuby application. We decided that this wasn’t too important, though it adds about 5MB to the transitive dependencies’ footprint, the language runtime is very stable and does not change often.&lt;/p&gt;
&lt;p&gt;The other big issue was to adapt the framework to our conventions. Twitter uses mostly Thrift for RPC; we use HTTP. They use ZooKeeper for Service Discovery; we use DNS. They use a Java properties-based configuration system; we use environment variables. They have their own telemetry system; we have our own telemetry system (we’re not ready to show it just yet, but stay tuned for some exciting news there). Fortunately, Finagle has some very nice abstractions for these areas, and most of the issues were solved with very minimal changes and there was no need to patch the framework itself.&lt;/p&gt;
&lt;p&gt;We then had to deal with the very messy state of Futures in Scala. Heather Miller, from the Scala core team, &lt;a href=&quot;http://www.infoq.com/presentations/Asynchronous-Scala-Java&quot;&gt;explained the history and changes introduced by newer versions of the language in a great presentation&lt;/a&gt;. But in summary, what we have across the Scala ecosystem are several different implementations of Futures and Promises, with Finagle coupled to &lt;a href=&quot;https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala&quot;&gt;Twitter’s Futures&lt;/a&gt;. Although &lt;a href=&quot;https://groups.google.com/d/msg/finaglers/wjADYhoiJKM/Ejqd6A9rahMJ&quot;&gt;Scala allows for compatibility between these implementations&lt;/a&gt;, we decided to use Twitter’s everywhere, and invest time in &lt;a href=&quot;https://github.com/twitter/util/pull/97&quot;&gt;helping the Finagle community move closer to the most recent versions of Scala&lt;/a&gt; rather than debug weird issues that this interoperability might spawn.&lt;/p&gt;
&lt;p&gt;With these issues addressed, we focused on how best to develop applications using Finagle. Luckly, Finagle’s design philosophy is nicely described by Marius Eriksen, one of its core contributors, in his paper &lt;a href=&quot;http://monkey.org/~marius/funsrv.pdf&quot;&gt;&lt;em&gt;Your Server as a Function&lt;/em&gt;&lt;/a&gt;. You don’t need to follow these principles in your &lt;em&gt;userland&lt;/em&gt; code, but in our experience everything integrates much better if you do. Using a Functional programming language like Scala makes following these principles quite easy, as they map very well to pure functions and combinators.&lt;/p&gt;
&lt;p&gt;We have used Finagle for HTTP, Thrift, memcached, Redis, and MySQL. Every request to the SoundCloud platform is very likely hitting at least one of our Finagle-powered microservices, and the performance we have from these is quite amazing.&lt;/p&gt;
&lt;p&gt;In the last part of this series of blog posts, we will be talking about how Finagle and Scala are being used to move away from a &lt;em&gt;one-size-fits-all&lt;/em&gt; RESTful API to optmized back-ends for our applications.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building Products at SoundCloud—Part II: Breaking the Monolith]]></title><description><![CDATA[In the previous post, we talked about how we enabled our teams to build microservices in Scala, Clojure, and JRuby without coupling them…]]></description><link>https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-2-breaking-the-monolith</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-2-breaking-the-monolith</guid><pubDate>Thu, 12 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith&quot;&gt;In the previous post&lt;/a&gt;, we talked about how we enabled our teams to build microservices in &lt;a href=&quot;https://developers.soundcloud.com/blog/category/scala&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;https://developers.soundcloud.com/blog/category/clojure&quot;&gt;Clojure&lt;/a&gt;, and &lt;a href=&quot;https://developers.soundcloud.com/blog/category/ruby&quot;&gt;JRuby&lt;/a&gt; without coupling them with our legacy monolithic Rails system. After the architecture changes were made, our teams were free to build their new features and enhancements in a much more flexible environment. An important question remained, though: how do we extract the features from the monolithic Rails application called &lt;em&gt;Mothership&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Splitting a legacy application is never easy, but luckily there are plenty of industry and academic publications to help you out.&lt;/p&gt;
&lt;p&gt;The first step in any activity like this is to identify and apply the criteria used to define the &lt;em&gt;units&lt;/em&gt; to be extracted. At SoundCloud, we have decided to use &lt;a href=&quot;http://www.amazon.com/gp/product/0321125215/ref=as_li_tl?ie=UTF8&amp;#x26;camp=1789&amp;#x26;creative=390957&amp;#x26;creativeASIN=0321125215&amp;#x26;linkCode=as2&amp;#x26;tag=fragmental-20&amp;#x26;linkId=ZQCEXWYXNZ5AA2WS&quot;&gt;the work of Eric Evans and Martin Fowler in what is called a &lt;em&gt;Bounded Context&lt;/em&gt;&lt;/a&gt;. An obvious example of Bounded Context in our domain was user-to-user messages. This was a well-contained feature set, highly cohesive, and not too coupled with the rest of the domain, as it just needs to hold references to users.&lt;/p&gt;
&lt;p&gt;After we identified the Bounded Context, the next task was to find a way to extract it. Unfortunately, Rails’ ActiveRecord framework often leads to a very coupled design. The code dealing with such messages was as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;index&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;InboxItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    respond mailbox_items_in_collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paginate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:page&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    respond mailbox_items_in_collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paginate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;:joins&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;INNER JOIN messages ON &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter tag&quot;&gt;#{&lt;/span&gt;safe_collection&lt;span class=&quot;token delimiter tag&quot;&gt;}&lt;/span&gt;&lt;/span&gt;_items.message_id = messages.id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;:page&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;:order&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;messages.created_at DESC&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Because we wanted to extract the messages’ Bounded Context into a separate microservice, we needed the code above to be more flexible. The first step we took was to refactor this code into &lt;a href=&quot;http://www.amazon.com/gp/product/0131177052/ref=as_li_tl?ie=UTF8&amp;#x26;camp=1789&amp;#x26;creative=390957&amp;#x26;creativeASIN=0131177052&amp;#x26;linkCode=as2&amp;#x26;tag=fragmental-20&amp;#x26;linkId=5KPGXJ7FUWPGJR3K&quot;&gt;what Michael Feathers describes as a &lt;em&gt;seam&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
A seam is a place where you can alter behavior in your program without editing in that place.
&lt;/blockquote&gt;
&lt;p&gt;So we changed our code a little bit:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;index&lt;/span&gt;&lt;/span&gt;
  conversations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cursor_for &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;cursor&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    conversations_service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conversations_for&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    current_user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    cursor&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    cursor&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  respond collection_for&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conversations&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:conversations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first version of  the &lt;code class=&quot;language-text&quot;&gt;conversations_service#conversations_for&lt;/code&gt; method was not that different from the previous code; it performed the exact same ActiveRecord calls.&lt;/p&gt;
&lt;p&gt;We were ready to extract this logic into a microservice without having to refactor lots of controllers and other Presentation Layer code. We first replaced the implementation of  &lt;code class=&quot;language-text&quot;&gt;conversations_service#conversations_for&lt;/code&gt; with a call to the service:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;conversations_for&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; offset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@http_client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;do_get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pagination&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; limit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  parse_response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We avoided big-bang refactorings as much as we could, and this required us to have the microservices working together with the old Mothership code for as long as it took to completely extract the logic into the new microservice.&lt;/p&gt;
&lt;p&gt;As described before, we did not want to use the Mothership’s database as the integration point for microservices.  That database is an &lt;a href=&quot;http://martinfowler.com/bliki/ApplicationDatabase.html&quot;&gt;&lt;em&gt;Application Database&lt;/em&gt;&lt;/a&gt;, and making it an &lt;a href=&quot;http://martinfowler.com/bliki/IntegrationDatabase.html&quot;&gt;&lt;em&gt;Integration Database&lt;/em&gt;&lt;/a&gt; would cause problems because we would have to synchronize any change in the database across many different microservices that would now be coupled to it.&lt;/p&gt;
&lt;p&gt;Although using the database as the integration point between systems was not planned, we had the new microservices accessing the Mothership’s database during the transition period.&lt;/p&gt;
&lt;p&gt;This brought up two important issues. During the whole transition period, the new microservices could not change the relational model in MySQL—or, even worse, use a different storage engine. For extreme cases, like user-to-user messages where a threaded-based model was replaced by a chat-like one, we had &lt;code class=&quot;language-text&quot;&gt;cronjobs&lt;/code&gt; keep different databases synchronized.&lt;/p&gt;
&lt;p&gt;The other issue was related to the Semantic Events system described in Part I. The way our architecture and infrastructure was designed requires events to be emitted where the state change happened, and this ought to be a single system. Because we could not have both the Mothership and the new microservice emitting events, we had to implement only the read-path endpoints until we were ready to make the full switch from the Mothership to the new microservice. This was less problematic than what we first thought, but nevertheless it did impact product prioritization because features to be delivered were constrained by this strategy.&lt;/p&gt;
&lt;p&gt;By applying these principles we were able to extract most services from the Mothership. Currently we have only the most coupled part of our domain there, and products like &lt;a href=&quot;https://blog.soundcloud.com/2014/01/13/hear-whats-happening-new-messaging/&quot;&gt;the new user-to-user messaging system&lt;/a&gt; were built completely decoupled from the monolith.&lt;/p&gt;
&lt;p&gt;In the next part, we will look at how we use Scala and &lt;a href=&quot;http://twitter.github.io/finagle/&quot;&gt;Finagle&lt;/a&gt; to build our microservices.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building Products at SoundCloud —Part I: Dealing with the Monolith]]></title><description><![CDATA[Most of SoundCloud’s products are written in Scala, Clojure, or JRuby. This wasn’t always the case. Like other start-ups, SoundCloud was…]]></description><link>https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-1-dealing-with-the-monolith</guid><pubDate>Wed, 11 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most of SoundCloud’s products are written in &lt;a href=&quot;https://developers.soundcloud.com/blog/category/scala&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;https://developers.soundcloud.com/blog/category/clojure&quot;&gt;Clojure&lt;/a&gt;, or &lt;a href=&quot;https://developers.soundcloud.com/blog/category/ruby&quot;&gt;JRuby&lt;/a&gt;. This wasn’t always the case. Like other start-ups, SoundCloud was created as a single, monolithic Ruby on Rails application running on the MRI, Ruby’s official interpreter, and backed by memcached and MySQL.&lt;/p&gt;
&lt;p&gt;We affectionately call this system &lt;em&gt;Mothership&lt;/em&gt;. Its architecture was a good solution for a new product used by several hundreds of thousands of artists to share their work, collaborate on tracks, and be discovered by the industry.&lt;/p&gt;
&lt;p&gt;The Rails codebase contained both our Public API, &lt;a href=&quot;https://soundcloud.com/apps&quot;&gt;used by thousands of third-party applications&lt;/a&gt;, and the user-facing web application. With the launch of the &lt;a href=&quot;https://developers.soundcloud.com/blog/building-the-next-soundcloud&quot;&gt;Next SoundCloud&lt;/a&gt; in 2012, our interface to the world became mostly the Public API —we built all of our client applications on top of the same API partners and developers used.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 700px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ebb4b534084c91b368f7641a794fd7cb/67d4f/product-engineering-diagram-1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.28571428571429%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAHABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB3aFB/8QAFBABAAAAAAAAAAAAAAAAAAAAEP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAEP/aAAgBAQAGPwJ//8QAFhABAQEAAAAAAAAAAAAAAAAAAQAQ/9oACAEBAAE/ISDP/9oADAMBAAIAAwAAABDwD//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABkQAAMAAwAAAAAAAAAAAAAAAAABESFRcf/aAAgBAQABPxCnZgYrIuIj2f/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Diagram 1&quot;
        title=&quot;Diagram 1&quot;
        src=&quot;/blog/static/ebb4b534084c91b368f7641a794fd7cb/67d4f/product-engineering-diagram-1.jpg&quot;
        srcset=&quot;/blog/static/ebb4b534084c91b368f7641a794fd7cb/f544b/product-engineering-diagram-1.jpg 200w,
/blog/static/ebb4b534084c91b368f7641a794fd7cb/41689/product-engineering-diagram-1.jpg 400w,
/blog/static/ebb4b534084c91b368f7641a794fd7cb/67d4f/product-engineering-diagram-1.jpg 700w&quot;
        sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;These days, we have about 12 hours of music and sound uploaded every minute, and hundreds of millions of people use the platform every day. SoundCloud combines the challenges of scaling both a very large social network with a media distribution powerhouse.&lt;/p&gt;
&lt;p&gt;To scale our Rails application to this level, we developed, contributed to, and published several components and tools to help &lt;a href=&quot;https://github.com/soundcloud/lhm&quot;&gt;run database migrations at scale&lt;/a&gt;, &lt;a href=&quot;https://github.com/soundcloud/master_slave_adapter&quot;&gt;be smarter about how Rails accesses databases&lt;/a&gt;, &lt;a href=&quot;https://github.com/soundcloud/empipelines&quot;&gt;process a huge number of messages&lt;/a&gt;, and more. In the end we have decided to fundamentally change the way we build products, as we felt we were always patching the system and not resolving the fundamental scalability problem.&lt;/p&gt;
&lt;p&gt;The first change was in our architecture. We decided to move towards what is now known as a &lt;em&gt;&lt;a href=&quot;http://martinfowler.com/articles/microservices.html&quot;&gt;microservices architecture&lt;/a&gt;&lt;/em&gt;. In this style, engineers separate domain logic into very small components. These components expose a well-defined API, and implement a &lt;a href=&quot;http://martinfowler.com/bliki/BoundedContext.html&quot;&gt;Bounded Context&lt;/a&gt; —including its persistence layer and any other infrastructure needs.&lt;/p&gt;
&lt;p&gt;Big-bang refactoring has bitten us in the past, so the team decided that the best approach to deal with the architecture changes would not be to split the Mothership immediately, but rather to not add anything new to it. All of our new features were built as microservices, and whenever a larger refactoring of a feature in the Mothership was required, we extract the code as part of this effort.&lt;/p&gt;
&lt;p&gt;This started out very well, but soon enough we detected a problem. Because so much of our logic was still in the Rails monolith, pretty much all of our microservices had to talk to it somehow.&lt;/p&gt;
&lt;p&gt;One option around this problem was to have the microservices accessing directly the Mothership database. This is a very common approach in some corporate settings, but because this database is &lt;a href=&quot;http://martinfowler.com/ieeeSoftware/published.pdf&quot;&gt;a &lt;em&gt;Public&lt;/em&gt;, but not &lt;em&gt;Published Interface&lt;/em&gt;&lt;/a&gt;, it usually leads to many problems when we need to change the structure of shared tables.&lt;/p&gt;
&lt;p&gt;Instead, we went for the only &lt;em&gt;Published Interface&lt;/em&gt; we had, which was the Public API. Our internal microservices would behave exactly like the applications developed by third-party organizations integrate with the SoundCloud platform.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 700px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/35484a6b037a3e0c87ce0a91acfaf724/67d4f/product-engineering-diagram-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.857142857142854%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe7UK0P/xAAWEAADAAAAAAAAAAAAAAAAAAAQESD/2gAIAQEAAQUChj//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAYEAADAQEAAAAAAAAAAAAAAAAAASEQQf/aAAgBAQABPyHgijdFWf/aAAwDAQACAAMAAAAQEw//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhMRBRYXH/2gAIAQEAAT8QL0MXpgTVHwldo5Ty4iCjPH//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Diagram 2&quot;
        title=&quot;Diagram 2&quot;
        src=&quot;/blog/static/35484a6b037a3e0c87ce0a91acfaf724/67d4f/product-engineering-diagram-2.jpg&quot;
        srcset=&quot;/blog/static/35484a6b037a3e0c87ce0a91acfaf724/f544b/product-engineering-diagram-2.jpg 200w,
/blog/static/35484a6b037a3e0c87ce0a91acfaf724/41689/product-engineering-diagram-2.jpg 400w,
/blog/static/35484a6b037a3e0c87ce0a91acfaf724/67d4f/product-engineering-diagram-2.jpg 700w&quot;
        sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Soon enough, we realized that there was a big problem with this model; as our microservices needed to react to user activity. The push-notifications system, for example, needed to know whenever a track had received a new comment so that it could inform the artist about it. At our scale, polling was not an option.  We needed to create a better model.&lt;/p&gt;
&lt;p&gt;We were already using AMQP in general and RabbitMQ in specific — In a Rails application you often need a way to dispatch slow jobs to a &lt;em&gt;worker&lt;/em&gt; process to avoid hogging the concurrency-weak Ruby interpreter. &lt;a href=&quot;http://www.infoq.com/presentations/amqp-soundcloud&quot;&gt;Sebastian Ohm and Tomás Senart presented the details of how we use AMQP&lt;/a&gt;, but over several iterations we developed a model called &lt;em&gt;Semantic Events&lt;/em&gt;, where changes in the domain objects result in a message being dispatched to a broker and consumed by whichever microservice finds the message interesting.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 700px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/56bd8881dd8a35df5789994673747f9b/67d4f/product-engineering-diagram-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.857142857142854%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAQAF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB3WBmP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEAAQUCX//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABsQAAICAwEAAAAAAAAAAAAAAAABITEQEUFR/9oACAEBAAE/IeFeIkbkT3eP/9oADAMBAAIAAwAAABADz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABwQAQACAwADAAAAAAAAAAAAAAEAESExQRBRcf/aAAgBAQABPxAvQ72GHD5UD2I5QrVxEGQ+P//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Diagram 3&quot;
        title=&quot;Diagram 3&quot;
        src=&quot;/blog/static/56bd8881dd8a35df5789994673747f9b/67d4f/product-engineering-diagram-3.jpg&quot;
        srcset=&quot;/blog/static/56bd8881dd8a35df5789994673747f9b/f544b/product-engineering-diagram-3.jpg 200w,
/blog/static/56bd8881dd8a35df5789994673747f9b/41689/product-engineering-diagram-3.jpg 400w,
/blog/static/56bd8881dd8a35df5789994673747f9b/67d4f/product-engineering-diagram-3.jpg 700w&quot;
        sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This architecture enabled &lt;a href=&quot;http://martinfowler.com/eaaDev/EventSourcing.html&quot;&gt;&lt;em&gt;Event Sourcing&lt;/em&gt;&lt;/a&gt;, which is how many of our microservices deal with shared data, but it did not remove the need to query the Public API —for example, you might need all fans of an artist and their email addresses to notify them about a new track.&lt;/p&gt;
&lt;p&gt;While most of the data was available through the Public API, we were constrained by the same rules we enforced on third-party applications.  It was not possible, for example, for a microservice to notify users about activity on private tracks as users could only access public information.&lt;/p&gt;
&lt;p&gt;We explored several possible solutions to the problem. One of the most popular alternatives was to extract all of the ActiveRecord models from the Mothership into a Ruby gem, effectively making the Rails model classes a &lt;em&gt;Published Interface&lt;/em&gt; and a shared component. There were several important issues with this approach, including the overhead of versioning the component across so many microservices, and that it became clear that microservices would be implemented in languages other than Ruby. Therefore, we had to think about a different solution.&lt;/p&gt;
&lt;p&gt;In the end, the team decided to use Rails’ features of &lt;em&gt;engines&lt;/em&gt; (or &lt;em&gt;plugins&lt;/em&gt;, depending on the framework’s version) to create an Internal API that is available only within our private network. To control what could be accessed internally, we used Oauth 2.0 when an application is acting on behalf of a user, with different authorisation scopes depending on which microservice needs the data.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 700px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1a6422e826679f9dcc889cdb99f83952/67d4f/product-engineering-diagram-4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.71428571428571%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe6Q0o//xAAVEAEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAQABBQKrf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABoQAAMAAwEAAAAAAAAAAAAAAAABERAhMYH/2gAIAQEAAT8hXBeGyDgnYsf/2gAMAwEAAgADAAAAEPPP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAQACAgMAAAAAAAAAAAAAAREAMRBBIVFx/9oACAEBAAE/EK013THCkHyYHsYngdXK0KPH/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Diagram 4&quot;
        title=&quot;Diagram 4&quot;
        src=&quot;/blog/static/1a6422e826679f9dcc889cdb99f83952/67d4f/product-engineering-diagram-4.jpg&quot;
        srcset=&quot;/blog/static/1a6422e826679f9dcc889cdb99f83952/f544b/product-engineering-diagram-4.jpg 200w,
/blog/static/1a6422e826679f9dcc889cdb99f83952/41689/product-engineering-diagram-4.jpg 400w,
/blog/static/1a6422e826679f9dcc889cdb99f83952/67d4f/product-engineering-diagram-4.jpg 700w&quot;
        sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Although we are constantly removing features from the Mothership, having both a &lt;em&gt;push&lt;/em&gt; and &lt;em&gt;pull&lt;/em&gt; interface to the old system makes sure that we do not couple our new microservices to the old architecture. The microservice architecture has proven itself crucial to developing production-ready features with much shorter feedback cycles. External-facing examples are &lt;a href=&quot;http://techcrunch.com/2013/03/11/soundclouds-new-moving-sounds-feature-holds-promise-for-ads-viral-content-like-tumblrs-gifs/&quot;&gt;the visual sounds&lt;/a&gt;, and &lt;a href=&quot;https://blog.soundcloud.com/2014/03/04/new-soundcloud-stats/&quot;&gt;the new stats system&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Roshi: a CRDT system for timestamped events]]></title><description><![CDATA[Let’s talk about the stream. The SoundCloud stream represents stuff that’s relevant to you primarily via
your social graph, arranged in time…]]></description><link>https://developers.soundcloud.com/blog/roshi-a-crdt-system-for-timestamped-events</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/roshi-a-crdt-system-for-timestamped-events</guid><pubDate>Fri, 09 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s talk about the stream.&lt;/p&gt;
&lt;p&gt;The SoundCloud stream represents stuff that’s relevant to you primarily via
your social graph, arranged in time order, newest-first. The atom of that data
model, an event, is a simple enough thing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timestamp&lt;/li&gt;
&lt;li&gt;User who did the thing&lt;/li&gt;
&lt;li&gt;Identifier of the thing that was done&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At 2014-04-01T13:14:15.034Z,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/a-trak&quot;&gt;a-trak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Reposted track &lt;a href=&quot;https://soundcloud.com/skrillex/duck-sauce-nrg-skrillex-kill&quot;&gt;skrillex/duck-sauce-nrg-skrillex-kill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you followed A-Trak, you’d want to see that repost event in your stream.
Easy. The difficult thing about time-ordered events is scale, and there are
basically two strategies for building a large-scale time-ordered event system.&lt;/p&gt;
&lt;h2&gt;Data models&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Fan out on write&lt;/strong&gt; means everybody gets an inbox.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 404px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/94700c78a46380cc5a7e80e582757b89/6cdf8/fan-out-on-write.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.495049504950494%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABc0lEQVQoz2NggABOBiKAtbU1S09PD09NTQ03PnUgSWUgFgRiFiBmBGImKGYWFxdndXNzA7H5L1y4sOv79+8fvwHBmTNnskCatbS0hIGUKBCLADErSIwfaigfEMsBsRTUcBCfBwi4BAQE2IFssYMHD5588+bNn3fv3v3ftGlTNUizoqKiBCMjoyAQg8xhRnctM9QgmCUgg7hkZGTYQJKGhob6/v7+vl5eXt6qqqpCDCQAVqiBXNLS0qyysrKgoJC5fv362f////8F4v/nzp2rBCnU1NSUNTY2VjAyMpIDYjZCBrMDAa+YmBhY4e7du9ffunXrGRC/Xrt2bSZIzMbGhtfKyord1NSUDWgwM1HOlZKS4gCFa0BAgFNnZ2dIfX19aEZGhhiSEkai/b58+XIYk/P27dvX/0MBMJabQYIqKirSQEoBiAWIMrCyshLuCqA3FwINugHEtxcvXhwHEnRxcQG5ng1bDOMEOTk5yF5jhxqAFQAApHlumQtK2IcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Fan out on write&quot;
        title=&quot;Fan out on write&quot;
        src=&quot;/blog/static/94700c78a46380cc5a7e80e582757b89/6cdf8/fan-out-on-write.png&quot;
        srcset=&quot;/blog/static/94700c78a46380cc5a7e80e582757b89/9ec3c/fan-out-on-write.png 200w,
/blog/static/94700c78a46380cc5a7e80e582757b89/c7805/fan-out-on-write.png 400w,
/blog/static/94700c78a46380cc5a7e80e582757b89/6cdf8/fan-out-on-write.png 404w&quot;
        sizes=&quot;(max-width: 404px) 100vw, 404px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That’s how it works today: we use Cassandra, and give each user a row in a
column family. When A-Trak reposts Skrillex, we fan-out that event to all of
A-Trak’s followers, and make a bunch of inserts. Reads are fast, which is
great. But writes carry perverse incentives: the more followers you have, the
longer it takes to persist all of your updates. Storage requirements are also
quadratic against user growth and follower count (i.e. affiliation density).
And mutations, e.g. changes in the social graph, become costly or unfeasible
to implement at the data layer. It works, but it’s unwieldy in a lot of
dimensions.&lt;/p&gt;
&lt;p&gt;At some point, those caveats and restrictions started affecting our ability to
iterate on the stream. To keep up with product ideas, we needed to address the
infrastructure. And rather than tackling each problem in isolation, we thought
about changing the model.&lt;/p&gt;
&lt;p&gt;The alternative is &lt;strong&gt;fan in on read&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 404px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/38fd90dc777269b8e41fb7a44abd73f3/6cdf8/fan-in-on-read.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABJ0lEQVQoz2NggABOBjTg6urK2tfXx1NVVYUhRwhwA7EyEAuqqKiwggQCAwNlbty4cfnHjx+vP378+P7YsWNeIPHCwkKQPBMUMwMxCxBLA7EYEIsAMVg/PxDzADGvoKAgiGa1sbFRP3ny5Ks3b978f/ny5f/+/v5MkC+AFgpB1YM0ywCxPBDbMDIyCgMxH9QS7ICTk1NeUVFRR0JCQh1kCDMzM0gDLxCzA7EAEAvDXEQQODs7Y4ixsIB8B/YJHzSYRIFYjZWV1d7ExETByMhIDojZUTSZmpqCaR0dHYagoCDG6OhoptjYWEZVVVWwOBcXFwPUu9JQVzJYWVkxubu7sxobG7MBDWZiIBOAIkEJiDWhhrMzUAGwQ71vC8QG0OCgGmDGG8OUAAC8ADKG1qfkJgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Fan in on read&quot;
        title=&quot;Fan in on read&quot;
        src=&quot;/blog/static/38fd90dc777269b8e41fb7a44abd73f3/6cdf8/fan-in-on-read.png&quot;
        srcset=&quot;/blog/static/38fd90dc777269b8e41fb7a44abd73f3/9ec3c/fan-in-on-read.png 200w,
/blog/static/38fd90dc777269b8e41fb7a44abd73f3/c7805/fan-in-on-read.png 400w,
/blog/static/38fd90dc777269b8e41fb7a44abd73f3/6cdf8/fan-in-on-read.png 404w&quot;
        sizes=&quot;(max-width: 404px) 100vw, 404px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;When A-Trak reposts Skrillex, it’s a single append to A-Trak’s outbox. When
users view their streams, the system will read the most recent events from the
outboxes of everyone they follow, and perform a merge. Writes are fast,
storage is minimal, and since streams are generated at read time, they
naturally represent the present reality. (It also opens up a lot of
possibilities for elegant implementations of product features and
experiments.)&lt;/p&gt;
&lt;p&gt;Of course, reads are difficult. If you follow thousands of users, making
thousands of simultaneous reads, time-sorting, merging, and cutting within a
typical request-response deadline isn’t trivial. As far as we know, nobody
operating at our scale builds timelines via fan-in-on-read. And we presume
that’s due at least in part to the challenges of reads.&lt;/p&gt;
&lt;p&gt;Yet we saw potential here. Storage reduction was actually huge: we projected a
complete fan-in-on-read data size for all users on the order of a hundred
gigabytes. At that size, it’s feasible to keep the data set in memory,
distributed among commodity servers. The problem then becomes coördination:
how do you reliably and correctly populate that data system (writes), and
materialize views from up to thousands of sources by hard deadlines (reads)?&lt;/p&gt;
&lt;h2&gt;Enter the CRDT&lt;/h2&gt;
&lt;p&gt;If you’re into so-called &lt;a href=&quot;https://en.wikipedia.org/wiki/CAP_theorem&quot;&gt;AP data systems&lt;/a&gt;, you’ve probably run into the
term &lt;a href=&quot;http://research.microsoft.com/apps/video/default.aspx?id=153540&amp;#x26;r=1&quot;&gt;CRDT&lt;/a&gt; recently. CRDTs are conflict-free replicated data types:
data structures for distributed systems. The tl;dr on CRDTs is that by
constraining your operations to only those which are associative, commutative,
and idempotent, you sidestep a lot of the complexity in distributed
programming. (See: &lt;a href=&quot;http://lostechies.com/jimmybogard/2013/06/06/acid-2-0-in-action/&quot;&gt;ACID 2.0&lt;/a&gt; and/or &lt;a href=&quot;http://www.bloom-lang.net/calm&quot;&gt;CALM theorem&lt;/a&gt;.) That, in
turn, makes it straightforward to guarantee eventual consistency in the face
of failure.&lt;/p&gt;
&lt;p&gt;With a bit of thinking, we were able to map a fan-in-on-read stream product to
a data model that could be implemented with a specific type of CRDT. We were
then able to focus on performance, optimizing our reads without becoming
overwhelmed by incidental complexity imposed by the consistency model.&lt;/p&gt;
&lt;h2&gt;Roshi&lt;/h2&gt;
&lt;p&gt;The result of our work is &lt;a href=&quot;https://github.com/soundcloud/roshi&quot;&gt;Roshi&lt;/a&gt;, a distributed storage system for
time-series events. It implements what we believe is a novel CRDT set type,
closely resembling a &lt;a href=&quot;https://github.com/aphyr/meangirls#lww-element-set&quot;&gt;LWW-element-set&lt;/a&gt; with inline garbage collection. At
its core, it uses the &lt;a href=&quot;http://redis.io/commands#sorted_set&quot;&gt;Redis ZSET&lt;/a&gt; sorted set to store state, and
orchestrates self-repairing reads and writes on top, in a stateless
operational layer. We spent a long while optimizing the read path to support
our latency and QPS requirements, and we’re confident that Roshi will
accommodate our exponential growth for years. It took about six developer
months to build, and we’re in the process of rolling it out now.&lt;/p&gt;
&lt;p&gt;Roshi is fully open-source, and all the gory technical details are in the
repository, so please do check it out. I hope it’s easy to grok: at the time
of writing, it’s 5000 lines of Go, of which 2300 are tests. And we intend to
keep the codebase lean, explicitly not adding features that are outside of the
tightly defined problem domain.&lt;/p&gt;
&lt;p&gt;Open-sourcing our work naturally serves the immediate goal of providing usable
software to the community. We hope that Roshi may be a good fit for problems
in your organizations, and we look forward to collaborating with anyone who’s
interested in contributing. Open-sourcing also serves another, perhaps more
interesting goal, which is advancing a broader discussion about software
development. The obvious reaction to Roshi is to ask why we didn’t implement
it with an existing, proven data system like Cassandra. But we too often
underestimate the costs of doing that: costs like mapping your domain to the
generic language of the system, learning the subtleties of the implementation,
operating it at scale, and dealing with bugs that your likely novel use cases
may reveal. There are even second-degree costs: when software engineering is
reduced to plumbing together generic systems, software engineers lose their
sense of ownership, which is the foundation of craftsmanship and software
quality.&lt;/p&gt;
&lt;p&gt;Given a well-defined problem, a specific solution may be far less costly than
a generic version: there’s a smaller domain translation, a much smaller
surface area, and less operational friction. We hope that Roshi stands in
evidence for the case that the practice of software engineering can be a more
thoughtful and crafted process. Software that is “invented here” can, in the
right circumstances, deliver outstanding business value.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Roshi was a team effort. I’m deeply indebted to the amazing work of
&lt;a href=&quot;https://twitter.com/tsenart&quot;&gt;Tomás Senart&lt;/a&gt;, &lt;a href=&quot;https://github.com/u-c-l&quot;&gt;Björn Rabenstein&lt;/a&gt;, and &lt;a href=&quot;http://www.freenerd.de&quot;&gt;Johan Uhle&lt;/a&gt;,
without whom Roshi would have never been possible.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing JavaScript SDK version 2]]></title><description><![CDATA[SoundCloud is pleased to introduce a new major version of the SoundCloud
JavaScript SDK. In version 2, we’ve rewritten much of the internal…]]></description><link>https://developers.soundcloud.com/blog/introducing-javascript-sdk-version-2</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-javascript-sdk-version-2</guid><pubDate>Thu, 01 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud is pleased to introduce a new major version of the SoundCloud
JavaScript SDK. In version 2, we’ve rewritten much of the internal code,
resulting in better performance for your JavaScript applications and support
for more streaming standards, such as HTTP Live Streaming.&lt;/p&gt;
&lt;p&gt;You can test the new version by pointing your JavaScript applications to
&lt;a href=&quot;https://connect.soundcloud.com/sdk-2.0.0.js&quot;&gt;https://connect.soundcloud.com/sdk-2.0.0.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ve also created a
&lt;a href=&quot;https://developers.soundcloud.com/docs/api/javascript-sdk-2&quot;&gt;guide&lt;/a&gt; to help you
upgrade from version 1 to version 2.&lt;/p&gt;
&lt;p&gt;JavaScript SDK version 1 is now deprecated and will be permanently replaced by
version 2 on July 1, 2014.&lt;/p&gt;
&lt;p&gt;On June 17, 2014, we will temporarily replace version 1 with version 2 between
10:00 and 11:00 UTC. We will do this again on June 24, 2014, between 18:00 and
19:00 UTC. These two upgrade tests will give you an opportunity to understand
the impact of this change on your applications. To ensure a seamless transition
for your users, we strongly encourage you to upgrade and perform internal tests
in advance of these dates.&lt;/p&gt;
&lt;p&gt;To receive notices before, during, and after these tests, &lt;a href=&quot;https://twitter.com/SoundCloudDev&quot;&gt;follow @SoundCloudDev
on Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have any questions about this upgrade, please feel free to email
&lt;a href=&quot;mailto:api-support@soundcloud.com?subject=JavaScript SDK 2.0.0&quot;&gt;api-support@soundcloud.com&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Irrational Fun: Find Yourself at Berlin Buzzwords]]></title><description><![CDATA[We were counting down the days until Berlin
Buzzwords on May 25, when we realised that it
would be great if you came too! With that in mind…]]></description><link>https://developers.soundcloud.com/blog/buzzwords-contest</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/buzzwords-contest</guid><pubDate>Sun, 27 Apr 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We were counting down the days until &lt;a href=&quot;http://berlinbuzzwords.de/&quot;&gt;Berlin
Buzzwords&lt;/a&gt; on May 25, when we realised that it
would be great if you came too! With that in mind, we’ve created a contest. One
lucky winner will receive a free ticket to Berlin Buzzwords, including travel
expenses and accommodation. Here are the details about how to apply.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The ratio of a circle’s circumference to its diameter, represented by the Greek
letter π, is an irrational number—it never terminates or repeats. Your goal
is to find the SoundCloud logo in π.&lt;/p&gt;
&lt;p&gt;We have provided a 14 pixel by 6 pixel, greyscale reference image: &lt;img src=&quot;/blog/5e0db8179b7cc2b056f00227038fd312/ref.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is the same image at 60X magnification:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 420px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c205b7d9d328c0738c46f04b092375b8/635b1/mag.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.857142857142854%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABeUlEQVQoz52Sr6sCQRDH1/P8gYIWq8EgiPmKnl0NJhHkusWkxcNiUzAIYrou2sRg9x8wyKVLwuUzaBPxvO9jBvYQ3uM9eAvDzuzOfPju7IggCEBL7nK5rovdbofj8YhutwshBOLxOCKRCFKpFBqNBg6HA+f6vh/Wi0/Y9XrF+XyGbdtYLBZotVqwLAvNZpOBsVgMiqIgmUwin89jPB7DcRw8n89QlHi9Xng8HnxwOp2wXC6xWq1QrVYZMhwOUalU2FdVFdFolMEU67rO+bfbDe/3m00Q9X6/43K5YD6fM2gymaBWq3GRaZqhLxUSmOJCoYB2uw3P88JWifV6jX6/j81mA8MwkMvlGCJVjUYjViKBpJCMepnJZFAsFtHr9bieRIl6vc7Jg8EAmqaFEPnkn4CkUkLJ6I4+ab/fQ5RKJU4gVbKQIH8plFDZz3K5jE6nA0E9SCQS3575m8JPKI0S+QTbbrcQ0+kU2WyWR+A/QBqhdDqN2WzGn/IFV0dvxnjMvw4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;mag&quot;
        title=&quot;mag&quot;
        src=&quot;/blog/static/c205b7d9d328c0738c46f04b092375b8/635b1/mag.png&quot;
        srcset=&quot;/blog/static/c205b7d9d328c0738c46f04b092375b8/9ec3c/mag.png 200w,
/blog/static/c205b7d9d328c0738c46f04b092375b8/c7805/mag.png 400w,
/blog/static/c205b7d9d328c0738c46f04b092375b8/635b1/mag.png 420w&quot;
        sizes=&quot;(max-width: 420px) 100vw, 420px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Each of the 10 shades of grey in this image can be mapped to a number:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Color&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;RGB Hex&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Number&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #fffff&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;ffffff&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #f0f0f0&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;f0f0f0&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #ebebeb&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;ebebeb&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #d0d0d0&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;d0d0d0&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #c1c1c1&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;c1c1c1&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #a8a8a8&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;a8a8a8&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #878787&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;878787&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #535353&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;535353&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #333333&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;333333&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;background-color: #000000&quot;&gt;&amp;nbsp&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;000000&lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Applying this mapping to the reference image produces the following 84-digit bitmap:&lt;/p&gt;
&lt;pre&gt;
0 0 0 0 0 3 3 9 9 9 6 0 0 0
0 0 0 3 4 9 5 9 9 9 9 4 0 0
0 1 2 7 5 9 5 9 9 9 9 7 1 0
3 9 5 9 5 9 5 9 9 9 9 9 9 3
6 9 5 9 5 9 5 9 9 9 9 9 9 6
1 8 5 9 5 9 5 9 9 9 9 9 8 1
&lt;/pre&gt;
&lt;p&gt;Your challenge is to write an algorithm that finds the 10 sequences that most closely approximate the reference image. Each result should include the sequence and its position (after the decimal point) in π.&lt;/p&gt;
&lt;p&gt;Here is an example result set:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Rank&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Image&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sequence&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Offset&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/2ebccf21cf56cba42df5d0c35274453d/top-2.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        082201638940102393659252475011295776958920
        282336494898427768768699465405437965994582
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        297,640,119
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/7005b183f66e437e591eb82937666301/top-5.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        310015049916890341096198545241549627773525
        291969856827158758552799587406476458977970
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        792,987,187
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/9759dc7ed4f11651d3ee281112c50844/top-1.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        212021479960265330123798820231693768599316
        147634474729776147987653958935291919768971
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        972,165,010
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/ce8c549d406a1307b3f1ff9d041cba64/top-3.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        000053743536020032496985553181128909983810
        344939134894807349584729687746183109884672
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        981,165,566
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/9a00f5fc9731c187ee5477b79a60f7da/top-9.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        142204297650171312983445842322141909755200
        787408739757838589593329762648444919386594
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        789,652,974
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/1cf2891ffed889a6c92890b9fb0d87e4/top-6.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        300011495970664010077917573663456957498854
        662995598898697947677549686339433357728071
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        197,342,990
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/6b1b03d22f6affac3672abd8d8fcb3b7/top-10.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        313560984264300011495970664010077917573663
        456957498854662995598898697947677549686339
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        197,342,978
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/8545ccc3db8d8061df4a11f433587c2b/top-8.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        870402479996214234001557832923050859979903
        649788689695954439755933903629798966788984
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        75,975,342
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/6791562a522f2c4ee0d83a3b3e293fe5/top-7.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        208503759370245135490877462200175839969750
        453766432680245845143285985661373828688970
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        343,577,393
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/blog/f7e32a09adbca3768ba99a9b91e38d50/top-4.gif&quot;&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;tt&gt;
        300544295771128716836756973814286978997516
        282647269986574856578306678421894769876141
      &lt;/tt&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;
        950,462,734
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Entries will be judged against the follow criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code quality&lt;/li&gt;
&lt;li&gt;Runtime performance&lt;/li&gt;
&lt;li&gt;Visual closeness to the reference image (subjective)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should run your algorithm against the following &lt;a href=&quot;http://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt&quot;&gt;data
set&lt;/a&gt; (approximately 1
GB), which contains the first 1,000,000,000 (billion) digits of π.&lt;/p&gt;
&lt;p&gt;Please send your submission, including a link to the source-code repository, to
&lt;a href=&quot;mailto:e@soundcloud.com&quot;&gt;e@soundcloud.com&lt;/a&gt; on or before May 5, 2014
23:59 UTC. Your repository should contain a &lt;code class=&quot;language-text&quot;&gt;README&lt;/code&gt; that includes instructions
about how to set up and run your code. Entries are subject to the &lt;a href=&quot;/blog/docs/api/contest-terms&quot;&gt;terms and
conditions&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;a name=&quot;buzzwords-update&quot;&gt;UPDATE&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Congratulations to Tomasz Pewiński, who submitted the &lt;a href=&quot;https://github.com/pewniak747/soundcloud-logo&quot;&gt;winning entry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’d also like to acknowledge entries by &lt;a href=&quot;https://github.com/oveddan/soundcloud-irrationalfun&quot;&gt;Dan
Oved&lt;/a&gt; and &lt;a href=&quot;https://gist.github.com/mkhl/6e79aae644c7c0cb4d52&quot;&gt;Martin
Kühl&lt;/a&gt;, which were also
excellent. Thanks to everyone else who participated in the contest. We hope it
was fun!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Welcome to SoundCloud's redesigned developer site]]></title><description><![CDATA[We’ve taken some time to bring all our developer resources together into a
single site. In doing so, we’ve reorganized the layout to make…]]></description><link>https://developers.soundcloud.com/blog/new-developer-site</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new-developer-site</guid><pubDate>Fri, 11 Apr 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve taken some time to bring all our developer resources together into a
single site. In doing so, we’ve reorganized the layout to make things easier to
find and also given the site a fresh new look.&lt;/p&gt;
&lt;p&gt;We hope you like it!&lt;/p&gt;
&lt;p&gt;If you have any feedback about the new design, &lt;a href=&quot;https://twitter.com/SoundCloudDev&quot;&gt;follow @SoundCloudDev on
Twitter&lt;/a&gt; and let us know.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Security update: Heartbleed vulnerability in OpenSSL]]></title><description><![CDATA[On Monday, April 7th, 2014, a major security
vulnerability in OpenSSL was made public. The vulnerability was filed as
CVE-2014-0160
and…]]></description><link>https://developers.soundcloud.com/blog/heartbleed</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/heartbleed</guid><pubDate>Fri, 11 Apr 2014 00:00:00 GMT</pubDate><content:encoded>&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 341px;&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/blog/static/cbfbd52b0009558a2ef89e02d1aad58b/f570f/heartbleed.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 121.11436950146629%; position: relative; bottom: 0; left: 0; background-image: url(&amp;apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsSAAALEgHS3X78AAADT0lEQVQ4y42Vz08TQRTHp11KgBCDASQY5KqRk5ELGgqJFxIuelAT/wFPcpEDXMSjdxU9GIOhARVjwEDpFtpSyg8D6AUSow2JmBSCFZEAYven37cz024LJGzyZV9n3nzmvTdvFqYy5mV4QoydCvt8HdCwqijRcHHx83BR0TVW8GDMj7kn8InA/gD/+2BU0pxkEaxR9XjWphmzI0JTUBQKK8rwOGMVQcbKYL+Wc9KP1mDtBhh+CavEQIqcPjY1ZdYDAT0dDBrJnh4tUlFhzNACRVmBPpM9WV5ufuvu1n6OjRnrg4PGQmtrJsqh24jwLEPInQkMLPj9/2z+WEL237U1a76xUZ8kEDTT0KDvJ5NWod+ntrYMRQrWQwIOU+jrAwM6ORgHB5al67aZyTgLjL09O15fb0arqoxMOu2AaI58yJd+b46MGDGK0utVGf7ECJgeHzdokhwtiwdhappj/J6etn6Fw5Z7jHwsw3DM7bk5Y4qnnaAI31ENUn19ToTuBc6bL8qzsxvquvNzY2iIR6goQYYWuBenGra0aDwf03J2FxJjebCsDIPXsL1dIwZYXZTyaSgVxsCP3l6zMC03JC86XmM71d9vqvKUvd5ap3Vg3InxQZPqdahWBRHLuZ2lJWuipERP8HTvcpjH43Q3mvYthY3e03aXl+3CehbALLSPHaup0QiGG9MnAvPQdSkSDf5oXlFsCj9aXW3urqxIaPZQ5Ab7q6v2VF0dr5vPFwkw5hFXLwfE+zGgGnabjfBIzT+Li9n0JQwbWbHaWglLjDFWknePXcBeaBPQm0h/G3fXmigtNbYikezNQL/RRoa4FVEXTBHvQ8At6Dago5NInyKFbeImUGNTrfQoh428FxG5YYXAp9AOdB2KA5QE4EWcn76FU8yI05yVnzLXp4/DsoXk7/NQM3QFmoe+OuOK8iAhPmU4xe9Y/FL4+w7BCqHCJugslIRKRWt1QP1Y/AZjAZmqegxMfhdl6s2w50IcWBbK+Z2DVOiVu3bHPq7iXqUaQl8oLRfwAmw15IrwpMAbEP2fOIPFHjV3mlTjUejZSYEekfol2JeF7Q3lgI1QF0V9ZO2OekKuwwnlIpbAJqjzRNHJ45dQVyu52+oidEtmc1yE/wEmGTOQxTdLegAAAABJRU5ErkJggg==&amp;apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Heartbleed&quot; title=&quot;Heartbleed&quot; src=&quot;/blog/static/cbfbd52b0009558a2ef89e02d1aad58b/f570f/heartbleed.png&quot; srcset=&quot;/blog/static/cbfbd52b0009558a2ef89e02d1aad58b/9ec3c/heartbleed.png 200w,
/blog/static/cbfbd52b0009558a2ef89e02d1aad58b/f570f/heartbleed.png 341w&quot; sizes=&quot;(max-width: 341px) 100vw, 341px&quot; loading=&quot;lazy&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;On Monday, April 7th, 2014, a major security
vulnerability in OpenSSL was made public. The vulnerability was filed as
&lt;a href=&quot;https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0160&quot;&gt;CVE-2014-0160&lt;/a&gt;
and later dubbed “&lt;a href=&quot;http://heartbleed.com/&quot;&gt;Heartbleed&lt;/a&gt;”, because the bug lies
within OpenSSL’s heartbeat extension, which is used for keepalive monitoring.
As a result of the bug, process memory can be read out remotely by an
attacker—potentially including certificates, keys, credentials, tokens, or
other sensitive data processed by the server.&lt;/p&gt;
&lt;p&gt;OpenSSL works as a cryptographic library that allows for authenticity and
confidentiality across the entire Internet. Because the reported Heartbleed bug
affects a vast number of internet services using OpenSSL to secure their
services (such as HTTPS, SMTP, IMAPS, and POP3), a patched OpenSSL version was
&lt;a href=&quot;https://www.openssl.org/news/secadv_20140407.txt&quot;&gt;released&lt;/a&gt; by the maintainers
within hours. Linux and UNIX distributions, which depend on the OpenSSL
implementation, received patches by their respective upstream maintainers.&lt;/p&gt;
&lt;p&gt;SoundCloud too uses OpenSSL in many of our services to increase the security
and privacy of our users. We therefore moved quickly to patch the
vulnerability, and did so within hours of the patch being made available. We’ve
also been in close communications with our vendors and service providers, to
ensure that they have applied the appropriate fixes as well. We have confirmed
that our implementations of OpenSSL are no longer vulnerable to this bug.&lt;/p&gt;
&lt;p&gt;Because we consider our users’ security and privacy of the utmost importance,
we have further taken the precautionary measures to rotate SSL certificates and
keys, and expire authentication tokens, such as session cookies, remember
tokens, and OAuth access and refresh tokens. This means that users will be
signed out of their SoundCloud accounts. Along with top security researchers
and responsible companies, we have also recommended to our users that they
change their passwords on all accounts (not just SoundCloud) that they have
signed-in with in the past week. Developers of API clients that check our SSL
fingerprints will need to update them.&lt;/p&gt;
&lt;p&gt;While the Heartbleed bug marks a sad day for the Internet as a whole, with
SoundCloud’s rollout of Perfect Forward Secrecy (PFS) support last year, we
ensured that the impact of an attack with the purpose of stealing private keys
and reading previously encrypted traffic is minimized. In the same spirit we
will also strive to find more such opportunities in the future and preemptively
provide our users with the highest possible level of safety.&lt;/p&gt;
&lt;p&gt;For more details about this bug, go to &lt;a href=&quot;http://heartbleed.com&quot;&gt;heartbleed.com&lt;/a&gt;.
To use tools to check your services, go to
&lt;a href=&quot;http://filippo.io/Heartbleed/&quot;&gt;Filippo.io&lt;/a&gt; or
&lt;a href=&quot;https://github.com/FiloSottile/Heartbleed&quot;&gt;FiloSottile/Heartbleed&lt;/a&gt; on GitHub.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Sponsoring CocoaPods]]></title><description><![CDATA[I’m excited to announce that SoundCloud is sponsoring the development of CocoaPods through a Travis Foundation grant. CocoaPods is an open…]]></description><link>https://developers.soundcloud.com/blog/sponsoring-cocoapods</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/sponsoring-cocoapods</guid><pubDate>Sat, 08 Mar 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 278px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/c2a2b9555b3a32361e0c47d898c26c87/e14eb/logos-small.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.76258992805756%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVQoz5WS30uTURjHd50R0nU3XXaRVwUR9RfURXZTeV8XSaRBc0ZdGGRh0FuWlFZsOeYPdI0G1SoklYWir6vm2PANQxnkNqbbTJ1z7/bpeU+oCGp04Jzvc748z/c8X55jK5fL/M8yTROrplQqbcF1HZt1JOJx3ricfPJ6edfl4WNfH/7OV3Lv563Ho3jvi+f8mp1RRTs1YfFKMNDbQ83RIzhqLnDl9Cns58+puK76DPVnq6kXvCb49HYTzvst+N2ddD1upb+jne62Jwz4fCykUpsdDvhe03TpIlqDnQf26zxqdKiC5trLtN5o5JkIBT8E6Gi+g/uhhlvTaKm7Stutm2iOBl7eu8ucuNwQfN/TzQGbjROV+6gSPFaxh0OCx/dWcFjw5P5KDgr6XS5VlE4kWM3nWV5aYnVlRcVbLGfTab59CaIPDfE1GGRieFjh+PgIobFRQnLXBweZTyZ3HdiG4FqxyKK8pqYoZNEs/c0YMWAmxe9iAWsMpkw0m82SyWRISxPWzuVyiisUCpuW89Lyz+lpjKkpdF1n8nuYeHKORWeAhc8hfsRnCQlvGAaTkQjRaFTlRSSOxWKEw2ElrAR3/Ifl7S39y/IfL9ZlarEYFjIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cocoapods&quot;
        title=&quot;Cocoapods&quot;
        src=&quot;/blog/static/c2a2b9555b3a32361e0c47d898c26c87/e14eb/logos-small.png&quot;
        srcset=&quot;/blog/static/c2a2b9555b3a32361e0c47d898c26c87/9ec3c/logos-small.png 200w,
/blog/static/c2a2b9555b3a32361e0c47d898c26c87/e14eb/logos-small.png 278w&quot;
        sizes=&quot;(max-width: 278px) 100vw, 278px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;I’m excited to announce that SoundCloud is sponsoring the development of &lt;a href=&quot;http://cocoapods.org/&quot; title=&quot;CocoaPods&quot;&gt;CocoaPods&lt;/a&gt; through a &lt;a href=&quot;http://foundation.travis-ci.org/&quot; title=&quot;Travis Foundation&quot;&gt;Travis Foundation&lt;/a&gt; grant. CocoaPods is an open-source dependency manager for Objective-C projects. Travis Foundation is a non-profit that pairs corporate sponsors with open-source projects to make open source even better.&lt;/p&gt;
&lt;p&gt;At SoundCloud, our iOS team uses CocoaPods every day to manage the dependencies of our mobile apps. We hope that this sponsorship will lead to improvements that benefit the entire Mac and iOS developer ecosystem.&lt;/p&gt;
&lt;p&gt;Watch for updates in the coming weeks on the &lt;a href=&quot;http://foundation.travis-ci.org/blog/&quot; title=&quot;Travis Foundation blog&quot;&gt;Travis Foundation blog&lt;/a&gt; and
&lt;a href=&quot;http://blog.cocoapods.org/&quot; title=&quot;CocoaPods Blog&quot;&gt;CocoaPods blog&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Smooth image loading by upscaling]]></title><description><![CDATA[The site  is a single-page application that displays a multitude of users’ images. At SoundCloud, we use a technique to make the loading of…]]></description><link>https://developers.soundcloud.com/blog/smooth-image-loading-by-upscaling</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/smooth-image-loading-by-upscaling</guid><pubDate>Thu, 20 Feb 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The site &lt;code class=&quot;language-text&quot;&gt;soundcloud.com&lt;/code&gt; is a single-page application that displays a multitude of users’ images. At SoundCloud, we use a technique to make the loading of an image appear smooth and fast. When displaying an image on screen, we want it to display to the user as fast as possible. The images display in multiple locations from a tiny avatar on a waveform to a large profile image. For this reason, we create each image in several sizes. If you are using &lt;a href=&quot;http://en.gravatar.com/site/implement/images/&quot; title=&quot;Gravatar&quot;&gt;Gravatar&lt;/a&gt;, this technique also applies because you can fetch arbitrarily-sized images by passing the desired size in a query parameter (&lt;code class=&quot;language-text&quot;&gt;?s=&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 20px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6e65ac104e029f46e9691f8b4b7c7ac7/b1892/avatar-tiny.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQCBQP/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAABssh9Iy6BuLoG0J//xAAbEAACAgMBAAAAAAAAAAAAAAABAgADERIhE//aAAgBAQABBQKtcXL0W1uWfspYhZsdwTt6mf/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EAB4QAAEEAQUAAAAAAAAAAAAAAAEAAhAhEQMiQUJh/9oACAEBAAY/AtQ1ZqNjsBHr6gHRiOF//8QAHBAAAgEFAQAAAAAAAAAAAAAAAREAECExQWFR/9oACAEBAAE/IbhP2KJ6xlAjCiGQLG4HMLiydnNA7agTNpsTjH//2gAMAwEAAgADAAAAEF8Qgf/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABYRAAMAAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPxAj/8QAHhABAAMBAAEFAAAAAAAAAAAAAQARITFBUWHB0eH/2gAIAQEAAT8QRZgKaoDROjB7QpaHrAWBFF238luQkiwmVR2OJyIVZ8so8zBcA1vWKpwUFRt+B+5//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;avatar-tiny&quot;
        title=&quot;avatar-tiny&quot;
        src=&quot;/blog/static/6e65ac104e029f46e9691f8b4b7c7ac7/b1892/avatar-tiny.jpg&quot;
        srcset=&quot;/blog/static/6e65ac104e029f46e9691f8b4b7c7ac7/b1892/avatar-tiny.jpg 20w&quot;
        sizes=&quot;(max-width: 20px) 100vw, 20px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 50px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2b607d0e006e60a736e1a84e2f1f9b57/a2eb4/avatar-small.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQFA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAGu7OoSdpDF7C9hX//EABwQAAICAgMAAAAAAAAAAAAAAAECAAMRIRITIv/aAAgBAQABBQKgYK7W6uwu/qVMQIrnmDvuaf/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EAB0QAAIBBAMAAAAAAAAAAAAAAAABEQIQIUEiQlH/2gAIAQEABj8CrysubcKoQ+r00JO0eEmj/8QAGxAAAwEBAAMAAAAAAAAAAAAAAAERMSFBYcH/2gAIAQEAAT8hjLP2uEI+lfyMEiG9NA8e0Y4zyBiSvNg6YP/aAAwDAQACAAMAAAAQj/e8/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAgEBPxBGX//EAB0QAAICAgMBAAAAAAAAAAAAAAERACExQWFxsdH/2gAIAQEAAT8QC6RYLo4OYwJ0mFw8FWsBmCKXFFideRgZRnIPJ3ADuB+QIBV1HcFKLAjrwP2f/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;avatar-small&quot;
        title=&quot;avatar-small&quot;
        src=&quot;/blog/static/2b607d0e006e60a736e1a84e2f1f9b57/a2eb4/avatar-small.jpg&quot;
        srcset=&quot;/blog/static/2b607d0e006e60a736e1a84e2f1f9b57/a2eb4/avatar-small.jpg 50w&quot;
        sizes=&quot;(max-width: 50px) 100vw, 50px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 120px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6c7d2d6623893cae2ab30aa1fb0e6554/fb5e5/avatar-medium.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQFAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAABrtz6I8GgbG7BtCf/xAAaEAACAwEBAAAAAAAAAAAAAAABAgADERIh/9oACAEBAAEFAqVxlwrclnbeypzhisYGPRsM/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAHRAAAQMFAQAAAAAAAAAAAAAAAQAQEQIDISIxgf/aAAgBAQAGPwK5zJbQ4RI1PZQFTQvW/8QAGxABAAMAAwEAAAAAAAAAAAAAAQARITFBgVH/2gAIAQEAAT8hRTdtO8gB35EYinyUKbCkE3F7nKWjGE82LHBP/9oADAMBAAIAAwAAABDv34H/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAXEQADAQAAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EEZf/8QAHxABAQACAgEFAAAAAAAAAAAAAREAMSFBYVFxwdHh/9oACAEBAAE/EFdRqwHp8O+MQIbFQXnBjDqJea/mWDmMqr1DbrG70FEb5wmx1jmESHvjGEpAhPbG/BfvP//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;avatar-medium&quot;
        title=&quot;avatar-medium&quot;
        src=&quot;/blog/static/6c7d2d6623893cae2ab30aa1fb0e6554/fb5e5/avatar-medium.jpg&quot;
        srcset=&quot;/blog/static/6c7d2d6623893cae2ab30aa1fb0e6554/fb5e5/avatar-medium.jpg 120w&quot;
        sizes=&quot;(max-width: 120px) 100vw, 120px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 200px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/eecdf5d561f284977ce04215a04a9373/f544b/avatar-large.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQFA//EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAGu3Pojg0DY3QGsJ//EABoQAAIDAQEAAAAAAAAAAAAAAAECAAMREiH/2gAIAQEAAQUCpXGX1bks7b2VOcMVjoY9Gwz/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAdEAABAwUBAAAAAAAAAAAAAAABAAIQAxEhIjGB/9oACAEBAAY/AqnMujQ4RI1PboB0WXsf/8QAGxAAAgMBAQEAAAAAAAAAAAAAAAERIUExgVH/2gAIAQEAAT8hujdyLaII3wbpKHwrZqggpeN6dGIpHkiTxH//2gAMAwEAAgADAAAAEM/Yvf/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABcRAAMBAAAAAAAAAAAAAAAAAAABEBH/2gAIAQIBAT8QRl//xAAdEAEAAgIDAQEAAAAAAAAAAAABABEhMUFRYdHh/9oACAEBAAE/EFUgmxt147xAxRwtFssVPWJc2/kAprRLVeKNuo2ehbDfsJt1HBlKh5xKzJQIV1GeXp+z/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;avatar-large&quot;
        title=&quot;avatar-large&quot;
        src=&quot;/blog/static/eecdf5d561f284977ce04215a04a9373/f544b/avatar-large.jpg&quot;
        srcset=&quot;/blog/static/eecdf5d561f284977ce04215a04a9373/f544b/avatar-large.jpg 200w&quot;
        sizes=&quot;(max-width: 200px) 100vw, 200px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The technique uses the browser’s cache of previously loaded images. When displaying a large avatar image, first display a smaller version that is stretched out to full size. When the larger image has loaded, it fades in over the top of the smaller version.&lt;/p&gt;
&lt;p&gt;The HTML looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;![](small.jpg)
![](large.jpg)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The CSS looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.fullImage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; opacity 0.2s linear&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the sake of brevity, the positioning code is not included in the preceding snippet. However, the images should lie atop one another.&lt;/p&gt;
&lt;p&gt;Finally, the JavaScript code looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fullImage   &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.fullImage&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    placeholder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.placeholder&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

fullImage
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;opacity&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;load&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opacity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;placeholder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;placeholder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;iframe src=&quot;https://codepen.io/janmonschke/full/BaLvaVY&quot; width=&quot;100%&quot; height=&quot;280&quot; frameborder=&quot;no&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Thus far, it’s not too complicated, and it gives a nice effect to the loading of images.&lt;/p&gt;
&lt;p&gt;But there’s a problem: we don’t want to make a request to get the small image just to display it for a few milliseconds. The overhead of making HTTP requests means that loading the larger image will usually not take significantly longer than the small one. Therefore, it only makes sense to use this technique if a smaller image has already been loaded during a particular session and thus served from the browser’s cache. How do we know what images are in cache? Each time an avatar is loaded, we need to keep track of that. However over time, there could be many thousands of avatars loaded within one session, so it needs to be memory efficient. Instead of tracking the full URLs of loaded images, we extract the minimum amount of information to identify a image, and use a bitmask to store what sizes have been loaded:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// a simple map object, { identifier =&gt; loaded sizes }&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; loadedImages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Let&apos;s assume a basic url structure like this:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// &quot;http://somesite.com/{identifier}-{size}.jpg&quot;&lt;/span&gt;
  imageRegex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\/(\w+)-(\w+)\.jpg$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// a list of the available sizes.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// format is [pixel size, filename representation]&lt;/span&gt;
  sizes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tiny&quot;&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;small&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;medium&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;large&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// extract the identifier and size.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;storeInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageRegex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      id    &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      size  &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// find the index which contains this size&lt;/span&gt;
  sizes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;info&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      loadedImages&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// once the image has loaded, then store it into the map&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.fullImage&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;storeInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When the image loads, we extract the important parts from the URL: namely, the identifier and the size modifier. Each size is mapped to a number—its index in the &lt;code class=&quot;language-text&quot;&gt;sizes&lt;/code&gt; array—and the appropriate bit is turned on in the &lt;code class=&quot;language-text&quot;&gt;loadedImages&lt;/code&gt; map. The code on line 27 does this conversion and bit manipulation; &lt;code class=&quot;language-text&quot;&gt;1 &amp;lt;&amp;lt; index&lt;/code&gt; is essentially the same as &lt;code class=&quot;language-text&quot;&gt;Math.pow(2, index)&lt;/code&gt;. By storing only a single number in the object, we save quite a bit of memory. A single-number object could contain many different flags. For example, assume there are four different sizes and 10,000 images in the following map:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;asBools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// etc...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

asInts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 2^0 + 2^1 + 2^3 = 1 + 2 + 8&lt;/span&gt;
  b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// 2^1&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// etc...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The memory footprints of these objects differ by 30%: 1,372,432 bytes for the booleans, and 1,052,384 for the integers. The key names consume the largest portion of these objects’ memory. For this reason, it is important to compress the key names as much as possible. Numeric keys are stored particularly efficiently by the V8 JavaScript engine found in Chrome and Safari.&lt;/p&gt;
&lt;p&gt;We now have a map that shows us what images have been loaded during this session, and you can use that information to choose a placeholder:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// find the largest image smaller than the requested one&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPlaceholder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fullUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageRegex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      targetSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      targetIndex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  sizes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;info&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; targetSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      targetIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetIndex &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadedImages&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; targetIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fullUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token regex&quot;&gt;/\w+\.jpg$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        sizes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;targetIndex&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;.jpg&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;targetIndex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// and in usage:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; placeholderUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPlaceholder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullSizeUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;placeholderUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// there has been a smaller image loaded previously, so...&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;addTheFadeInBehaviour&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// no smaller image has been loaded so...&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;loadFullSizeAsNormal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Although, this technique is a bit involved and I’ve deliberately glossed over some of the details, it creates a nice visual effect, greatly reduces the perceived load time of the image, and it is especially effective for long-lived, single-page applications.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hassle-free concurrency in Android using RxJava]]></title><description><![CDATA[Both our Android and iOS teams use the reactive programming paradigm to simplify asynchronous, concurrent code in our native mobile apps…]]></description><link>https://developers.soundcloud.com/blog/hassle-free-concurrency-in-android-using-rxjava</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/hassle-free-concurrency-in-android-using-rxjava</guid><pubDate>Wed, 23 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Both our Android and iOS teams use the reactive programming paradigm to simplify asynchronous, concurrent code in our native mobile apps. For Android, we use Netflix’s RxJava. Matthias Käppler—a SoundCloud engineer and a contributor to the &lt;a href=&quot;https://github.com/Netflix/RxJava/tree/master/rxjava-contrib/rxjava-android&quot;&gt;RxJava Android library&lt;/a&gt;—&lt;a href=&quot;http://mttkay.github.io/blog/2013/08/25/functional-reactive-programming-on-android-with-rxjava/&quot;&gt;blogs about the HOWs and WHYs of RxJava on Android&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tomorrow in London, Matthias will be talking about &lt;a href=&quot;http://uk.droidcon.com/2013/sessions/conquering-concurrency-bringing-the-reactive-extensions-to-the-android-platform/&quot;&gt;RxJava at Droidcon&lt;/a&gt;.
You can grab a drink with him and other members of our Android Team at the
&lt;a href=&quot;http://scdroidcon.eventbrite.co.uk/&quot;&gt;SoundCloud Droidcon Drinkup&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Say hello to Sketchy the spam fighter]]></title><description><![CDATA[Sketchy is a spam-fighting, open-source software framework developed by SoundCloud engineers Matt Weiden, Rany Keddo, and Michael Brückner…]]></description><link>https://developers.soundcloud.com/blog/say-hello-to-sketchy-the-spam-fighter</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/say-hello-to-sketchy-the-spam-fighter</guid><pubDate>Mon, 07 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 300px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d6dcc23daced4d6498c1619edd6e8036/135ae/no-spam.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEyklEQVQ4y3VVC0yTVxS+1S2AATU8I1IZtBRseVWKsIEt0wwX1jFfc1mGxphlIdtUHDMssgFjOLcxYck06sZ8oGPiNiZm6gCTZYszPgalhYqgIlJfUUsBQUb//vfbvZc2Mcu8ycl//ptzv/+cc7/v/MSuNxC+rpjNpD0ofBr3ATxli01YZQmd22AJm9tvnad2W6PVHkt41KAlJPIn2zOaAue+ugAee574Tes1LVZw3568QGCR3ueXkMYpl9hTDGZ2sLs/LRPO97bAdeh7DLe0Yri1Da6GI3B+sBUDmYtgCVdes6nmv+49Ri4ZMgQoufqymRzxbtrU2oq+mAQ4qz6F+8F9iWXKTWZGHzNJGhmRnDtqMaDPQHei/msfaG/OYkLaZ0aIMm1x2vJBbSqGT5+WvUCUejyUStLU0+dLHg7Kl+fWtu1S/6wIdOv0uzjGOTJtKkt7anpeT3QcHpw6xcFkeXIS7slJ6na7GY6HIVMwHxIzKssi07sH69EZE09vVFRK/akLwXq+lngvYHpHWJRtYsdX8GYGb2lPXJNNTRiIT6QjrKc8Uw/rsyVC6bhVWjqTdETHLXNkZKP16FHPu0VF9OSJE9TlcqGurg61tbW4cOEC+PuunTtRf/gwHjofYO/y5agpKMD1O3fo3m++RePhQ5LDuATWKNVbpCdKdbD3jbUIjAiXopXzKEuacjCFQgE/Pz+oVSrs3r0bvBhufVeuQJueLvzyj8pEfGxysjRSWQVGqePkslJ92Vr5CYiCyEUbN9I9e/agubkZgYGBKCkpQVJSEtasWYOwsDAEBQWhpaUFxuxsAZiXl4fpCgU15Jjk8RMnGZWi+khnZMzDcRZUVFoqvlZZWQm73S6yCwgIgFqthtlshsFgEOA1NTUwmUwIDg5GaGgo5kRE0IUmEx07dx7WGM0ksSlVY/eafsGxtja6ft06Dorq6mqRzWurV8PhcGDlypXQaDRQKpXYvHkzjEYj9Ho9IiMjkZqcLADHzwtAN+9h36XPvgCjkFy0YQPlQEWbNomSDtTXi1tNSUmBv7+/2MvPz0diYiJyc3Oh0+mQYzTS+LQF8qNTv/GSr5KOkMhDrvdLWMlbpcAZMyjrD/397FnotFo0egGzsrJQVVWF4uJirFixAi+x3pWVlaGwsJBuKS6mrxQUSKPbtvNL+ZV0RmtWDaRnAUxOd+/dYxQGHfm5CRcTdBhqaxWAj8bHBbG5TUxMCPO+M+F48OjhqOTIeYHRJvZtRmzpaUbsS/ertguSOht+QHvIHOo8OJUdU8aT+E2ZqsWZ4e/2c2LfHix8Z7ZQS5dGt+xasgGDZRUeq2q+fP+AAKNcZlx2/zG2LVMuTx4z3t0lDej06IpPelOA/R0QLIZDd9KCz68xod8sr/Dwr4roxwfDYwPCNxw42O2MRbBGx+3nGH8Qxubepbm+6cNBq68zoTu/rIFndPT/xpeYRPI/E9IQk9wNlhkD2+c7fznLODVtep59TuHb7IrTvsqG59UbmUa4tn6IMcbRsTNnmP2FsebjcJV/jEGmW9azm6zM9b5zPZnZ3tGVliaefS8uJRf9Z4vyh+oPBFjnxa21hMz9kf0CujujYoc6lbHDzO9hv4VjfAg4irfM4rF/sjJ9mdkT9eRfv5KqM1u0PZcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Sketchy the spam fighter&quot;
        title=&quot;Sketchy the spam fighter&quot;
        src=&quot;/blog/static/d6dcc23daced4d6498c1619edd6e8036/135ae/no-spam.png&quot;
        srcset=&quot;/blog/static/d6dcc23daced4d6498c1619edd6e8036/9ec3c/no-spam.png 200w,
/blog/static/d6dcc23daced4d6498c1619edd6e8036/135ae/no-spam.png 300w&quot;
        sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;Sketchy is a spam-fighting, open-source software framework developed by SoundCloud engineers &lt;a href=&quot;https://github.com/mweiden&quot;&gt;Matt Weiden&lt;/a&gt;, &lt;a href=&quot;https://github.com/purzelrakete&quot;&gt;Rany Keddo&lt;/a&gt;, and Michael Brückner. Sketchy reduces malicious user activity on web applications. You can use it to address several common issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detect when a user submits text that contains spam content.&lt;/li&gt;
&lt;li&gt;Detect when a user submits multiple texts that are nearly identical.&lt;/li&gt;
&lt;li&gt;Rate-limit malicious actions that users perform repeatedly.&lt;/li&gt;
&lt;li&gt;Check user signatures such as IP addresses against external blacklist APIs.&lt;/li&gt;
&lt;li&gt;Collect and consolidate reports of spam from users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We created Sketchy so that we could fight spam around the clock. Any startup that reaches the scale of SoundCloud has to deal with spammers on a daily basis. We decided to release Sketchy as open-source software so other companies don’t have to reinvent the wheel. Since we’ve released it, &lt;a href=&quot;http://gigaom.com/2013/10/07/soundcloud-open-sources-sketchy-to-help-other-big-web-firms-fight-spammy-behavior/&quot;&gt;Sketchy has been featured on GigaOM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Sketchy has been deprecated and is no longer in use. It worked well for many years but has since been replaced by other services.&lt;/i&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building Clojure Services at Scale]]></title><description><![CDATA[SoundCloud has a service-oriented architecture, which allows us to use different languages for different services. With concurrency and…]]></description><link>https://developers.soundcloud.com/blog/building-clojure-services-at-scale</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-clojure-services-at-scale</guid><pubDate>Fri, 20 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud has a service-oriented architecture, which allows us to use different languages for different services. With concurrency and scaling in mind, we started to build some services in Clojure due to its interoperability with the JVM, the availability of good quality libraries, and we just plain like it as a language.&lt;/p&gt;
&lt;p&gt;How do you build distributed, robust, and scalable micro-services in Clojure? &lt;a href=&quot;http://blog.josephwilk.net/clojure/building-clojure-services-at-scale.html&quot;&gt;Read what Joseph Wilk, an engineer and Clojure enthusiast at SoundCloud, has to say&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Writing your own Karma adapter]]></title><description><![CDATA[Background When we started to work on the new version of our mobile web app, we knew we wanted to run unit tests on a wide variety of…]]></description><link>https://developers.soundcloud.com/blog/writing-your-own-karma-adapter</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/writing-your-own-karma-adapter</guid><pubDate>Mon, 09 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Background&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When we started to work on the new version of our mobile web app, we knew we wanted to run unit tests on a wide variety of clients, mobile devices, PhantomJS, and on Chrome when running locally. Because we practice continuous integration, we knew we also wanted Git hooks and proper results formatting.&lt;/p&gt;
&lt;p&gt;We chose Karma runner, which is a project from the &lt;a href=&quot;http://angularjs.org&quot; title=&quot;Angular JS&quot;&gt;Angular JS&lt;/a&gt; team that provides developers with a “productive testing environment”. One of the advantages that Karma runner offers over other similar projects is its ability to use any testing framework. At SoundCloud, we aim to have the same toolset across various JavaScript projects, and our unit test framework of choice is &lt;a href=&quot;https://github.com/spadgos/tyrtle&quot;&gt;Tyrtle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using Tyrtle&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can write your own Karma adapter by using the Tyrtle example that follows. The idea is to tie your tests to the Karma API. The pieces of information that you need are the number of tests, test suites or modules, the results of each test (with possible assertion or execution errors, or both), and a hook to let Karma know that the runner ran all of the tests.&lt;/p&gt;
&lt;p&gt;You also need to provide a start function that configures the unit test framework, loads the test files, and starts the tests.&lt;/p&gt;
&lt;p&gt;The basic template for an adapter is as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/**
 * Returned function is used to kick off tests
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStartFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;karma&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * Returned function is used for logging by Karma
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDumpFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;karma&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; serialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// inside you could use a custom `serialize` function&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// to modify or attach messages or hook into logging&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    karma&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; dump&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

win&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__karma__&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStartFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__karma__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
win&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dump &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDumpFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;win&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__karma__&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, create a renderer/reporter for the unit test framework that will pass
the data to Karma. Tyrtle has a renderer that can render HTML, XML for CI,
or print to any other type of output.&lt;/p&gt;
&lt;p&gt;To pass the data to Karma, implement the methods that follow:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Tyrtle renderer
 * @interface
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Renderer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Renderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;beforeRun&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tyrtle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Renderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;afterRun&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tyrtle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Renderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;afterTest&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;createStartFn&lt;/code&gt; function creates a renderer object, with a Karma runner
instance available within the start-function’s scope.&lt;/p&gt;
&lt;p&gt;Create a parameter named &lt;code class=&quot;language-text&quot;&gt;karma&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TyrtleKarmaRenderer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;karma&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;karma &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; karma&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Tell &lt;code class=&quot;language-text&quot;&gt;karma&lt;/code&gt; what the total number of tests is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Invoked before all tests are run; reports complete number of tests
 * @param  {Object} tyrtle Instance of Tyrtle unit tests runner
 */&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;TyrtleKarmaRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;beforeRun&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tyrtle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;karma&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// count number of tests in each of the modules&lt;/span&gt;
    total&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; tyrtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;memo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentModule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; memo &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; currentModule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After each test, pass the resulting data to Karma:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Invoked after each test, used to provide Karma with feedback
 * for each of the tests
 * @param  {Object} test current test object
 * @param  {Object} module instance of Tyrtle module
 *                  to which this test belongs
 */&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;TyrtleKarmaRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;afterTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;karma&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    description&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    success&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; Tyrtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PASS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    log&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statusMessage&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    time&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runTime
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, inform Karma that the tests have all finished running:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Invoked after all the tests are finished running
 * with unit tests runner as a first parameter.
 * `window.__coverage__` is provided by Karma.
 * This function notifies Karma that the unit tests runner is done.
 */&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;TyrtleKarmaRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;afterRun&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* tyrtle */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;karma&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    coverage&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__coverage__
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You now have a renderer constructor. Next, turn your attention to the &lt;code class=&quot;language-text&quot;&gt;createStartFn&lt;/code&gt; function. You need to configure and initialize the unit test framework that returns a function, which requires a list of test files that are served from the Karma server and starts the actual runner.&lt;/p&gt;
&lt;p&gt;Karma serves the files that are required for testing from a path that Karma creates and timestamps the files to avoid caching issues in browsers. Karma makes each path available as a key in a hash named &lt;code class=&quot;language-text&quot;&gt;__karma__.files&lt;/code&gt;. This makes Karma a bit tricky to configure when using an AMD-loader such as &lt;code class=&quot;language-text&quot;&gt;require.js&lt;/code&gt;. To understand how to use AMD with Karma, go to:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://karma-runner.github.io/0.8/plus/RequireJS.html&quot;&gt;http://karma-runner.github.io/0.8/plus/RequireJS.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the final &lt;code class=&quot;language-text&quot;&gt;createStartFn&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Creates instance of Tyrtle to run the tests.
 *
 * Returned start function is invoked by Karma runner when Karma is
 * ready (connected with a browser and loaded all the required files)
 *
 * When invoked, the start function will AMD require the list of test
 * files (saved by Karma in window.__karma__.files) and set them
 * as test modules for Tyrtle and then invoke Tyrtle runner to kick
 * off tests.
 *
 * @param  {Object} karma Karma runner instance
 * @return {Function}     start function
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStartFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;karma&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; runner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Tyrtle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  Tyrtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TyrtleKarmaRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;karma&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; testFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__karma__&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/-test\.js$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;testFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; testFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/base/public/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testFiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;testModules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// test files can return a single module, or an array of them.&lt;/span&gt;
      testFiles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;testFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; testModule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testModule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          testModule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;testModule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        testModule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;aModule&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          aModule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAMDName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testFile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          runner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aModule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      runner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To find more examples of how this all fits together, see the scripts &lt;a href=&quot;https://gist.github.com/gryzzly/6497759&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;test-main.js&lt;/code&gt;&lt;/a&gt; (the RequireJS configuration to work with Karma) and &lt;a href=&quot;https://gist.github.com/gryzzly/6497671&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;karma.conf.js&lt;/code&gt;&lt;/a&gt;. Also, there are many adapter implementations such as &lt;a href=&quot;https://github.com/karma-runner/karma-mocha&quot;&gt;Mocha&lt;/a&gt;, &lt;a href=&quot;https://github.com/karma-runner/karma-nodeunit&quot;&gt;NodeUnit&lt;/a&gt;, and &lt;a href=&quot;https://github.com/karma-runner/karma-qunit&quot;&gt;QUnit&lt;/a&gt; on the &lt;a href=&quot;https://github.com/karma-runner&quot;&gt;Karma GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ursula Kallio contributed to the writing of this post.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Responsive Android applications with sane code]]></title><description><![CDATA[A common problem in Android development is that you need to jump off the main UI thread to retrieve data from an IO-based source. At…]]></description><link>https://developers.soundcloud.com/blog/responsive-android-applications-with-sane-code</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/responsive-android-applications-with-sane-code</guid><pubDate>Mon, 12 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common problem in Android development is that you need to jump off the main UI thread to retrieve data from an IO-based source. At SoundCloud, we use Netflix’s &lt;a href=&quot;https://github.com/Netflix/RxJava&quot;&gt;RxJava&lt;/a&gt; to simplify asynchronous flows in Android. In an interview given for the Google Developers series “root access Berlin”,  &lt;a href=&quot;https://github.com/mustafasezgin&quot;&gt;Mustafa Sezgin&lt;/a&gt;—who heads up our Mobile Team—explains why we chose RxJava, what it gives us, and he walks through an example-usage pattern:&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 56.2%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;http://www.youtube.com/embed/DA0ZAnxUKDk?feature=oembed&quot; frameborder=&quot;0&quot; allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;If you are interested in more details, be sure to catch &lt;a href=&quot;http://mttkay.github.io/&quot;&gt;Matthias Käppler&lt;/a&gt;—from our Android Team—give an in-depth talk at &lt;a href=&quot;http://uk.droidcon.com/2013/&quot;&gt;Droidcon London&lt;/a&gt;, October 24-27, 2013.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Win a trip to the Barcelona Ruby Conference]]></title><description><![CDATA[The lineup for BaRuCo 2013 looks amazing, with speakers such as Aaron Patterson, Katrina Owen, Sandi Metz, and Ruby’s inventor Yukihiro…]]></description><link>https://developers.soundcloud.com/blog/win-a-trip-to-the-barcelona-ruby-conference</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/win-a-trip-to-the-barcelona-ruby-conference</guid><pubDate>Fri, 09 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 300px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5b1831d72dc2c2b0299a8d3b3445a0f3/135ae/barcelona-ruby-conference-sept-MMXIII.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAFUElEQVQ4y1VVaVBTVxh9gILSUgVHtFTHaa1QNzYFDNCAYEISlrAlARIIhFUgKDvagBSJxBAgrIKirYiiLFOXVooLVkXRsYJUa+vU6kydLmOr1LYuxOSevgRh9Pvz3Xfv+84733LPoyjarn07YnQUACp1c7kF9cro5xnXR28sPdJ7grn/YJ/f0OWrTvSe1dS5f1iyhTHGaGfPXaTGvrv5Jhht5q/WDkVlGmW0NO/Whog03TqOFP78VNAAep4w827uFlX9+OM/naaAJ579Y2b0Fy5doV4HM1l7R7eMHZk27uQjhKOvEGxBJvw4UiKKzyULV7LpPQE8uElgBidNqOp2l0zF6Z5Pgr7BTNO4b5s3/bJveBrCaGZiP4E+nyMm2SwRyQwUEHmAgGQFSw38sGSdb0QafHgy5JdWt79WIopKylZYTDLrSXZmxsCDl6hPCJXpC4LiwPYJJ4vXCYmNpxiz3WOxwD2SfOwZTHLZsZDxpAb/qHSdF1uKCnXLdiPGElee+RTye5zojL9dWBLEchL0+VwxbJ1DCMUrI1abezCr+gqo+lFQZYOgRFpi7RxG0jaIkBgkMfhHZxA6fYzeGFszXTe6ATvc6S/xI9N1RRyJCcwsfhesG0dh+dUfmN81AlGyAmlxeXDachiUvBtWznyyiRWLeGGmbl1oCmTZikPTo8EX59w21i2Nm6APZIQRiltKLFtv452BB4irasMXbr64b+uAJht7uHHSMSPrAKhIFdzWhpDckERDIN24DRGpf9FY9hRN9cMAfoqOyU9BXlAcWegeQczyj8G+dQilvBjcW+iIe/M/gGhlABbtOAHZradYTHuz1AOw8U4gOXSj2PwUg29oMj7r7GVS3X1fMj3Z8QgXZZMcdgyxdo8BpR6GIiAKL+Ytw8+LlsN/FRtuB0dR/AtB6MmfMG/jLljm9cHMLwupzAgSGZWh9wiSoqq2TUwd7jnu50UPbkB4mmk8Zq+Jg7nyPDpXM/Cj3SIw6dnzPTIG5e+Ab9NpLPeOxZycRlg3XAfFSEWqfyQJ4Cbq3VjxUNftllA379xx9GaJXy6wWYX8QBGx9xQS84wOrPaIghcrBQFHf8DnjwFG81mk269Aea4Ctpf/g5ViAHMYEnqEYshSW2e9m080Oo+eXG9simVMnPxOjJ0LChh8A9NPQKj1uYSS7UHg4G9of/AcK2sHIHBl4fu35yKotA0zOu6CEtbClRFOCv0FRDx3FQShsnEa611Tp3OVDZpQx/VgzXPRbeZKYOMaTixitJib0wH74i68penHiu4R+BS1wKHqFCzops1yjyJyngTsJd4v2Q5eyCis7J2ew+fQvc8JlT2Vycugzq8wZAUKMduFT6iQcszM7aPrNYKZWrpmlUOg4ppg4RxOkgKEqJEriELdYuDQ17B/8IK3CSzplVxpd3fKVfX7oG7Z/1K1qcywJSQBa924xM5TRCw8E2HuIaWZR5HVzmySRzMzgjXsPTRRt6sT6oa9tUaMFUyh+ZQ4mJSibEdDbcmn9ajf1wXtzmZdo1xhUMZuJEUhElLIE5PSyGTSkLXVsLO0Wre9vp0olM3YWlHXM5Xpk8cPJxf/Pnk0nb6ypi2vQKGe2K7dizJNG0or6oha02pQqpoMZ06fM3Qd7UfWJ9WgNRHlquadU3EXhoYn5ev8xWGTf/HsyTTTk6cGl5WU1zRnF1Tezy6pQuGnWhRX1KNC04qC0upfi7fVdPQe658Wg4uXrpjivj7zDUXdGLtJDV+9Nq1n4ZJNr/8CbA71HHep1LRyaYkKVmn3rBl/9NBu6vwjRoQ59M9M6wEajL7G1P89PeL40SDKOAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Barcelona Ruby Conference September MMX111&quot;
        title=&quot;Barcelona Ruby Conference September MMX111&quot;
        src=&quot;/blog/static/5b1831d72dc2c2b0299a8d3b3445a0f3/135ae/barcelona-ruby-conference-sept-MMXIII.png&quot;
        srcset=&quot;/blog/static/5b1831d72dc2c2b0299a8d3b3445a0f3/9ec3c/barcelona-ruby-conference-sept-MMXIII.png 200w,
/blog/static/5b1831d72dc2c2b0299a8d3b3445a0f3/135ae/barcelona-ruby-conference-sept-MMXIII.png 300w&quot;
        sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The lineup for &lt;a href=&quot;http://www.baruco.org/&quot;&gt;BaRuCo 2013&lt;/a&gt; looks amazing, with speakers such as Aaron Patterson, Katrina Owen, Sandi Metz, and Ruby’s inventor Yukihiro Matsumoto. The conference is currently &lt;strong&gt;SOLD OUT&lt;/strong&gt;, but we have one extra ticket… and it could be yours!&lt;/p&gt;
&lt;p&gt;If you win the ticket, SoundCloud will fly you from anywhere in the world to Barcelona, Spain and put you up in a nice Catalonian hotel.&lt;/p&gt;
&lt;p&gt;How do you enter to win?&lt;/p&gt;
&lt;p&gt;It’s simple. Just create a command-line interface in Ruby that uses the SoundCloud API. You can use the &lt;a href=&quot;https://github.com/soundcloud/soundcloud-ruby&quot; title=&quot;SoundCloud Ruby SDK&quot;&gt;SoundCloud Ruby SDK&lt;/a&gt;, but this is not a requirement. The only constraints are that it is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a command-line interface,&lt;/li&gt;
&lt;li&gt;written in Ruby,&lt;/li&gt;
&lt;li&gt;integrated with SoundCloud,&lt;/li&gt;
&lt;li&gt;and totally awesome!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The author of the most creative CLI that uses SoundCloud will win the BaRuCo
ticket.&lt;/p&gt;
&lt;p&gt;To submit an entry, push your code to GitHub and send an email that includes a link to the repo to &lt;a href=&quot;mailto:e@soundcloud.com&quot;&gt;e@soundcloud.com&lt;/a&gt; by 12:59:59 UTC on August 26th, 2013. We will announce the winner on August 30th.&lt;/p&gt;
&lt;p&gt;You must be at least 18 years old to enter and allowed to travel to Spain (sorry Edward Snowden).&lt;/p&gt;
&lt;p&gt;Entries will be judged by a team of SoundCloud staff. If you have any questions, &lt;a href=&quot;mailto:e@soundcloud.com&quot;&gt;email me&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Music Hack Day Coming to Toronto!]]></title><description><![CDATA[The first Music Hack Day — organized by our very own Dave Haynes
— was held in July 2009 in London, UK. Since then there have
been over 3…]]></description><link>https://developers.soundcloud.com/blog/music-hack-day-toronto</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/music-hack-day-toronto</guid><pubDate>Tue, 09 Jul 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8ba927893bc96a3cddf08e9bbe54c8f2/48a11/toronto.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABxWV5hgoP/8QAGBAAAwEBAAAAAAAAAAAAAAAAAAEQERL/2gAIAQEAAQUCw5Gsjn//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAADAAAAAAAAAAAAAAAAAAAAIDH/2gAIAQEABj8CKv8A/8QAGBAAAgMAAAAAAAAAAAAAAAAAAREAECH/2gAIAQEAAT8hEKgg2MJiiwV//9oADAMBAAIAAwAAABDUL//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/EGf/xAAaEAEBAQADAQAAAAAAAAAAAAABEQAhMWGR/9oACAEBAAE/ELpUL7nkU9rSYqmfG47ydj91u//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Toronto&quot;
        title=&quot;Toronto&quot;
        src=&quot;/blog/static/8ba927893bc96a3cddf08e9bbe54c8f2/48a11/toronto.jpg&quot;
        srcset=&quot;/blog/static/8ba927893bc96a3cddf08e9bbe54c8f2/f544b/toronto.jpg 200w,
/blog/static/8ba927893bc96a3cddf08e9bbe54c8f2/41689/toronto.jpg 400w,
/blog/static/8ba927893bc96a3cddf08e9bbe54c8f2/48a11/toronto.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The first Music Hack Day — organized by our very own Dave Haynes
— was held in July 2009 in London, UK. Since then there have
been over 30 Music Hack Day events all over the world in cities like
San Francisco, New York, Paris, Barcelona and Reykjavik.&lt;/p&gt;
&lt;p&gt;Music Hack Day events gather programmers, designers and artists to
conceptualize, build and demo the future of music. Software, hardware,
mobile, web, instruments, art — anything goes as long as it’s music
related.&lt;/p&gt;
&lt;p&gt;I’m happy to announce that SoundCloud is teaming up with
&lt;a href=&quot;http://rdio.com&quot;&gt;Rdio&lt;/a&gt;, &lt;a href=&quot;http://unspace.ca&quot;&gt;Unspace&lt;/a&gt; and &lt;a href=&quot;http://echonest.com/&quot;&gt;The Echo
Nest&lt;/a&gt; to organize the first ever Music Hack Day
in Toronto. &lt;a href=&quot;http://toronto.musichackday.org&quot;&gt;MHD Toronto&lt;/a&gt; happens on
August 10th and 11th 2013.&lt;/p&gt;
&lt;p&gt;If you are a Toronto developer, designer, or artist, come out and join us
for what is sure to be an amazing event.&lt;/p&gt;
&lt;p&gt;For more details and to register for the event, go to
&lt;a href=&quot;http://toronto.musichackday.org&quot;&gt;http://toronto.musichackday.org&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Berlin Geekettes All-Women Hackathon Roundup]]></title><description><![CDATA[Late last year, six women crowded in to a Mitte cafe booth and
listened to Berlin Geekettes founder Jess Erickson share her idea:
Berlin’s first all-women hackathon. With SoundCloud’s Amelie Anglade
the then newly-appointed the Berlin Geekettes Tech Ambassador, we
agreed that it was a great idea to produce the hackathon as a
partnership between the Geekettes and the women developers of
SoundCloud.  Fast forward to the first weekend of March, when the vision became
reality: after 24 hours of hacking, 80 women demoed 29 projects across
a range of different platforms, from a belt transformed into a game
controller to an app aimed to help toddlers learn to do everyday
tasks.]]></description><link>https://developers.soundcloud.com/blog/berlin-geekettes-hackathon-roundup</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/berlin-geekettes-hackathon-roundup</guid><pubDate>Thu, 25 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Late last year, six women crowded in to a Mitte cafe booth and
listened to Berlin Geekettes founder Jess Erickson share her idea:
Berlin’s first all-women hackathon. With SoundCloud’s Amelie Anglade
the then newly-appointed the Berlin Geekettes Tech Ambassador, we
agreed that it was a great idea to produce the hackathon as a
partnership between the Geekettes and the women developers of
SoundCloud.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ed0e09eb7428b54f9bc16d49eda40ad8/48a11/bghack.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQGAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAXt8ZbKqMlBP/8QAGxAAAgIDAQAAAAAAAAAAAAAAAQMAAhITMUL/2gAIAQEAAQUCo1ZAdTbmuUIE9nv/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwFH/8QAHBAAAQMFAAAAAAAAAAAAAAAAAAERMRASMkGh/9oACAEBAAY/ApYsMkI6O+qf/8QAGhABAAMBAQEAAAAAAAAAAAAAAQARITFBYf/aAAgBAQABPyG0aHRim3wRizmP2XKK426T1G8NT//aAAwDAQACAAMAAAAQHD//xAAVEQEBAAAAAAAAAAAAAAAAAAARAP/aAAgBAwEBPxAZv//EABYRAQEBAAAAAAAAAAAAAAAAAAAhAf/aAAgBAgEBPxDIt//EABsQAQACAwEBAAAAAAAAAAAAAAEAESExYXFB/9oACAEBAAE/EGBCkFmj75CSCFdN3Zyis9iQdo9Q65Crtn00wAigjOjNHhEpXgZ//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Duana discussing APIs&quot;
        title=&quot;Duana discussing APIs&quot;
        src=&quot;/blog/static/ed0e09eb7428b54f9bc16d49eda40ad8/48a11/bghack.jpg&quot;
        srcset=&quot;/blog/static/ed0e09eb7428b54f9bc16d49eda40ad8/f544b/bghack.jpg 200w,
/blog/static/ed0e09eb7428b54f9bc16d49eda40ad8/41689/bghack.jpg 400w,
/blog/static/ed0e09eb7428b54f9bc16d49eda40ad8/48a11/bghack.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Fast forward to the first weekend of March, when the vision became
reality: after 24 hours of hacking, 80 women demoed 29 projects across
a range of different platforms, from a belt transformed into a game
controller to an app aimed to help toddlers learn to do everyday
tasks.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Hackathon with a twist&lt;/h2&gt;
&lt;p&gt;This hackathon invited all types of projects: web, iOS, Android,
hardware, and so on. And the hackers rose to the challenge. One women
even turned her belt into a game controller, and demo’d it by standing
on a chair and playing Tetris by tapping her fingers on what she
dubbed
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/wonderbelt&quot;&gt;The WonderBelt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Achievement of a strong collaborative energy was an important part of
what we set out to do. With over 120 applications, we accepted as many
women as we could possibly fit into the generous space provided free
of charge by Deutsche Telekom, complete with internet infrastructure
and an onsite technician. In addition to API representatives, we asked
attendees to identify themselves as ”domain experts” if they were
willing to offer help and answer questions. We wanted to make the
event also appealing to new programmers, as well as non-programmers
such as designers and people with product ideas. We partnered with
&lt;a href=&quot;http://www.opentechschool.org/&quot;&gt;OpenTech School&lt;/a&gt; to host a
prep-school training session beforehand, with a presentation by
SoundCloud’s very own Duana Stanley. The result? A wide range of women
with different backgrounds and experience levels working together.&lt;/p&gt;
&lt;p&gt;And the Berlin Geekettes wanted to create a fun, healthy atmosphere,
which is why we had a yoga teacher leading relaxation exercise classes
and fresh organic meals (shout out to the amazing chefs!). Etsy joined
the fun by providing a craft corner with instructions and materials
for making plush birds. Many hackers took a break for a mental
refresh, with stretching and stitching.&lt;/p&gt;
&lt;h2&gt;Lots of actually working demos!&lt;/h2&gt;
&lt;p&gt;We got the usual API mash-ups, such as
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/homethingy&quot;&gt;HomeThingy&lt;/a&gt;
which suggests apartments on ImmobilienScout24 based on your Etsy
purchase history,
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/eye-moo&quot;&gt;Eye-MOO&lt;/a&gt;
which sends your EyeEM photos to be printed as postcards, and
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/mood-swings&quot;&gt;Mood Swings&lt;/a&gt;
which serves music from SoundCloud and images from Tumblr to suit your
mood.&lt;/p&gt;
&lt;p&gt;There were not one but &lt;em&gt;three&lt;/em&gt; mash-ups using the SoundCloud and
Readmill APIs, combining books and sounds in various ways:
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/soundmill&quot;&gt;Soundmill&lt;/a&gt;
(share highlights from your SoundCloud audiobooks!),
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/bookbeat&quot;&gt;BOOKBEAT&lt;/a&gt;
(which also one the Best Student Project prize), and our staff pick
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/getup-readup&quot;&gt;GetUp-ReadUp&lt;/a&gt;
(a noisy alarm clock that uses book trivia to make you feel alert).&lt;/p&gt;
&lt;p&gt;More than one team built apps aimed at small children, including the
overall winner:
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks/monkey-see-monkey-do&quot;&gt;Monkey See, Monkey Do&lt;/a&gt;
helps your toddler understand daily routines using simple
pictures. The team included an illustrator who made adorable drawings
of a monkey brushing its teeth, having a snack, and other toddler
activities.&lt;/p&gt;
&lt;h2&gt;More, please&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e84b60c281b7908661ad607382572430/48a11/bghack2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 125%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAZABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAQBAgUD/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQAC/9oADAMBAAIQAxAAAAG9efPOmzNIWmdBlzQB/8QAHBAAAwACAwEAAAAAAAAAAAAAAQIDABETIjFC/9oACAEBAAEFAmtnO6lbWYSuoF6EBbLoNrOzYkdj6Huf/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAh/9oACAEDAQE/ASP/xAAXEQEAAwAAAAAAAAAAAAAAAAAAEBEh/9oACAECAQE/AWxb/8QAIBAAAgEDBAMAAAAAAAAAAAAAAAERAhAhEjJBcSKBsf/aAAgBAQAGPwLf6N04JUEVUuecGn4eVLb7HPKtkpF1b//EABwQAQACAgMBAAAAAAAAAAAAAAEAESExEEFhof/aAAgBAQABPyG0EFrt1HTc1EpBXqUGFtWvJfXeRFQp9sUjndilYm1qEjfcfmch/9oADAMBAAIAAwAAABCI/YD/xAAZEQACAwEAAAAAAAAAAAAAAAABIQAQEUH/2gAIAQMBAT8QAW8oGpgn/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAEREEH/2gAIAQIBAT8Q7MNwo//EAB8QAQACAgICAwAAAAAAAAAAAAERIQAxQVEQcWGhsf/aAAgBAQABPxCRtuEN+yqxDI2CUlCVZ3WOmGooHVb9zkaP0Ih+Wj3jSEADLDYm4hwK3NgA4A4DOfnuzHWOgIJNsFEZDwZBxXjf0H7hn//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Celebrating Hackers&quot;
        title=&quot;Celebrating Hackers&quot;
        src=&quot;/blog/static/e84b60c281b7908661ad607382572430/48a11/bghack2.jpg&quot;
        srcset=&quot;/blog/static/e84b60c281b7908661ad607382572430/f544b/bghack2.jpg 200w,
/blog/static/e84b60c281b7908661ad607382572430/41689/bghack2.jpg 400w,
/blog/static/e84b60c281b7908661ad607382572430/48a11/bghack2.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Dive in to the whole list of projects
&lt;a href=&quot;https://www.hackerleague.org/hackathons/berlin-geekettes-hackathon/hacks&quot;&gt;list of submitted projects&lt;/a&gt;
on the hackathon’s official wiki. We were blown away by the quality of
the presentations from so many first-time hackathon-attendees!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Removing 'hotness' parameterhotness]]></title><description><![CDATA[The  endpoint has traditionally accepted an
 parameter for ordering results by either creation
date or ‘hotness’. The method for calculating…]]></description><link>https://developers.soundcloud.com/blog/removing-hotness-param</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/removing-hotness-param</guid><pubDate>Tue, 16 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;/tracks&lt;/code&gt; endpoint has traditionally accepted an
&lt;code class=&quot;language-text&quot;&gt;order&lt;/code&gt; parameter for ordering results by either creation
date or ‘hotness’. The method for calculating a tracks ‘hotness’ has
never been clearly explained, but generally speaking is based on
the number of likes and listens a track receives.&lt;/p&gt;
&lt;p&gt;Recently we started to experience problems with the query that returns
tracks ordered by hotness. In the past weeks, these problems started
to effect and even cause outages for API users.&lt;/p&gt;
&lt;p&gt;We have decided that the best way forward is to remove this parameter.
Starting soon, &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; requests to the &lt;code class=&quot;language-text&quot;&gt;/tracks&lt;/code&gt;
endpoint will ignore the &lt;code class=&quot;language-text&quot;&gt;order&lt;/code&gt; parameter and default to
ordering by creation date.&lt;/p&gt;
&lt;p&gt;In the future, we look forward to releasing support for more stable
and idiomatic search and order parameters. In the meantime, it is
still possible to approximate the result sets previously returned by
specifying &lt;code class=&quot;language-text&quot;&gt;order=hotness&lt;/code&gt; by manually sorting the returned
tracks by a combination of &lt;code class=&quot;language-text&quot;&gt;favoritings\_count&lt;/code&gt; and
&lt;code class=&quot;language-text&quot;&gt;playback\_count&lt;/code&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud at PennApps]]></title><description><![CDATA[Last weekend, we sponsored and attended our first
PennApps, the world’s largest student run
hackathon held at the
University of Pennsylvania in
Philadelphia. Nearly 500 students participated from a variety of
universities across the US and elsewhere. Students were given 36
hours to get into teams, hack on projects, then show them off
to the judges. The results were astounding. In total, over
100 hacks were submitted.
Here are some of my personal favourites.]]></description><link>https://developers.soundcloud.com/blog/pennapps-recap</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/pennapps-recap</guid><pubDate>Wed, 23 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last weekend, we sponsored and attended our first
&lt;a href=&quot;http://pennapps.com/&quot;&gt;PennApps&lt;/a&gt;, the world’s largest student run
hackathon held at the
&lt;a href=&quot;http://www.upenn.edu/&quot;&gt;University of Pennsylvania&lt;/a&gt; in
Philadelphia. Nearly 500 students participated from a variety of
universities across the US and elsewhere. Students were given 36
hours to get into teams, hack on projects, then show them off
to the judges. The results were astounding. In total, over
&lt;a href=&quot;https://www.hackerleague.org/hackathons/pennapps-spring-2013/hacks&quot;&gt;100 hacks were submitted&lt;/a&gt;.
Here are some of my personal favourites.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/917e9d55a63002b2f03ea66ef19b70d8/48a11/pennapps.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEBf/EABQBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAccWgqMYR//EABkQAAIDAQAAAAAAAAAAAAAAAAABAgMREv/aAAgBAQABBQKsejEzt2k1kv/EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/ASf/xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAgEBPwFn/8QAGRAAAgMBAAAAAAAAAAAAAAAAACEBEBEi/9oACAEBAAY/AmNVB0Yf/8QAGhAAAwEBAQEAAAAAAAAAAAAAAAERMVFBgf/aAAgBAQABPyGPrCeGNEkvHTc6c4i23h//2gAMAwEAAgADAAAAECz/AP/EABYRAQEBAAAAAAAAAAAAAAAAAAAhAf/aAAgBAwEBPxCo1//EABcRAAMBAAAAAAAAAAAAAAAAAAABMRH/2gAIAQIBAT8QrRQ//8QAHhABAQABAwUAAAAAAAAAAAAAAREAITFBEFGhscH/2gAIAQEAAT8QEyArXdXjzlgcrES9Du4+g2fu2J6hQgcLi7yj1n//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Giving my API Demo&quot;
        title=&quot;Giving my API Demo&quot;
        src=&quot;/blog/static/917e9d55a63002b2f03ea66ef19b70d8/48a11/pennapps.jpg&quot;
        srcset=&quot;/blog/static/917e9d55a63002b2f03ea66ef19b70d8/f544b/pennapps.jpg 200w,
/blog/static/917e9d55a63002b2f03ea66ef19b70d8/41689/pennapps.jpg 400w,
/blog/static/917e9d55a63002b2f03ea66ef19b70d8/48a11/pennapps.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h3&gt;Cloud Sequencer&lt;/h3&gt;
&lt;p&gt;The winners of the Best Audio Hack Prize, Cloud Sequencer is an app that
allows you to hook up a midi keyboard to your browser, and map portions
of SoundCloud tracks to the keys. The level of polish on this app was
amazing and I’m happy to note that the team is working on making a public
version available that will work with a midi keyboard or your computer’s
keyboard. Here’s the team, &lt;a href=&quot;https://twitter.com/naklugman&quot;&gt;Noah Klugman&lt;/a&gt;,
&lt;a href=&quot;http://maxseiden.com/&quot;&gt;Max Seiden&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/gargury&quot;&gt;Gaurav Kulkarni&lt;/a&gt;
and &lt;a href=&quot;https://twitter.com/daiyunxing&quot;&gt;Yunxing Dai&lt;/a&gt; demoing their project.&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 75%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;http://www.youtube.com/embed/B3fGSrFxzbA&quot; frameborder=&quot;0&quot; allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;h3&gt;SoundScultpr&lt;/h3&gt;
&lt;p&gt;This was fairly mind blowing. Four &lt;strong&gt;High&lt;/strong&gt; School students, &lt;a href=&quot;https://twitter.com/jzone333&quot;&gt;Jared Zoneraich&lt;/a&gt;, Kenny Song, &lt;a href=&quot;http://www.linkedin.com/pub/russell-kaplan/61/76a/b37&quot;&gt;Russell Kaplan&lt;/a&gt;,
David Maginley built &lt;a href=&quot;http://soundsculptor.herokuapp.com/&quot;&gt;SoundSculptr&lt;/a&gt;.
SoundSculptr uses a &lt;a href=&quot;https://www.leapmotion.com/&quot;&gt;Leap Motion&lt;/a&gt; to enable users
to create music by moving their hands. It’s a fantastic idea and they pulled it
off brilliantly. You can even upload the result to SoundCloud!&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 56.25%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;http://www.youtube.com/embed/J5c0htimgWc&quot; frameborder=&quot;0&quot; allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;h3&gt;Astoria&lt;/h3&gt;
&lt;p&gt;Ever checked out a neighbourhood using Google maps Street View? Astoria takes
this to a new level by allowing you to use your phone to “steer” your way through
a city Grand Theft Auto style, pulling in venues from Yelp as you pass them,
showing property values, and letting you listen to local sounds on SoundCloud.
Get the full neighbourhood experience with this app. Astoria was built by
Joshua Ma, Anisha Pai, Max Kolysh and Thomas Georgiou. Here’s team member Anvisha Pai
giving it a go.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e405663ada13f8054ac2646ede66561a/df2c7/astoria.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.33333333333331%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAwL/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAAcHJbp3YUDs9lXhmuKlf/8QAHBAAAgMAAwEAAAAAAAAAAAAAAQMAAhESIjEy/9oACAEBAAEFAkMwBh4xJrt/ATlTEvNYe0rDF/H/xAAWEQADAAAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8BK//EABYRAAMAAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwEj/8QAHRAAAQQCAwAAAAAAAAAAAAAAAQAREkECECAhQv/aAAgBAQAGPwKXp1CNu+gMgpAFXogno0nGceH/xAAbEAACAwEBAQAAAAAAAAAAAAABEQAhMUFRYf/aAAgBAQABPyEQUxf2XHRoOQwBLXVN1wXeQ8Dg5UKkEe+QtWHESyDzYNiacuE//9oADAMBAAIAAwAAABDbEcP/xAAYEQACAwAAAAAAAAAAAAAAAAAAARARYf/aAAgBAwEBPxDYItH/xAAYEQADAQEAAAAAAAAAAAAAAAAAAREQUf/aAAgBAgEBPxB8ReRn/8QAHxABAAICAgIDAAAAAAAAAAAAAQARITFRYUGBccHR/9oACAEBAAE/EF/c5CgzvsYcpCpKBVqbaitjmuwLydlxi/oLDs7quufMpacmTJ6mMgMDY599yoUIB5D9ELiybmHwwio3+R0xhGhOIlKt2+Z//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Checking out the hood with Astoria&quot;
        title=&quot;Checking out the hood with Astoria&quot;
        src=&quot;/blog/static/e405663ada13f8054ac2646ede66561a/df2c7/astoria.jpg&quot;
        srcset=&quot;/blog/static/e405663ada13f8054ac2646ede66561a/f544b/astoria.jpg 200w,
/blog/static/e405663ada13f8054ac2646ede66561a/41689/astoria.jpg 400w,
/blog/static/e405663ada13f8054ac2646ede66561a/df2c7/astoria.jpg 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;LiveTale.us&lt;/h3&gt;
&lt;p&gt;I love apps that explore new ways of telling stories. LiveTale, built by Nathan Schloss,
Yair Aviner, Ben Glickman and Tyler Cloutier lets users record snippets of a story
or listen to previously recorded story tales plotted on a google map. Bonus points for
deploying it live, so you can &lt;a href=&quot;http://livetale.us/&quot;&gt;check it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/f4dcd6a46d940501cc67ab04694d0b03/df2c7/livetale.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.33333333333331%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAQBAgMF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQAC/9oADAMBAAIQAxAAAAFxfl7ZbCQOrKYlSSv/xAAeEAACAQMFAAAAAAAAAAAAAAABAgMAESEQEhMiMv/aAAgBAQABBQK44mHffajK4YyPZjkFWaaTGaHiTT//xAAWEQADAAAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8BcP/EABcRAAMBAAAAAAAAAAAAAAAAAAAQEUH/2gAIAQIBAT8Bwj//xAAeEAABBAIDAQAAAAAAAAAAAAAAAREhMRASAgNRUv/aAAgBAQAGPwLsR5ZyfEG1OU2Ovz4WSau6JRWEx//EABoQAQADAQEBAAAAAAAAAAAAAAEAETEhcWH/2gAIAQEAAT8hsuKcIybD2i0i1Q9EYfJpuAhuntJvh9qDUEmieEfLNyLU/9oADAMBAAIAAwAAABDIL8P/xAAWEQADAAAAAAAAAAAAAAAAAAABIDH/2gAIAQMBAT8QFR//xAAYEQACAwAAAAAAAAAAAAAAAAAAARARQf/aAAgBAgEBPxB4lZ//xAAcEAEBAAMBAQEBAAAAAAAAAAABEQAhMUFxUdH/2gAIAQEAAT8QScHgPSYhGb0mo/zCI2lvc20p+WvM8W0QQOB9xJUAWXHLKaBQ/cSDUGM1POmO0bGuXGDMabzq9yZvP//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The LiveTale.us Team&quot;
        title=&quot;The LiveTale.us Team&quot;
        src=&quot;/blog/static/f4dcd6a46d940501cc67ab04694d0b03/df2c7/livetale.jpg&quot;
        srcset=&quot;/blog/static/f4dcd6a46d940501cc67ab04694d0b03/f544b/livetale.jpg 200w,
/blog/static/f4dcd6a46d940501cc67ab04694d0b03/41689/livetale.jpg 400w,
/blog/static/f4dcd6a46d940501cc67ab04694d0b03/df2c7/livetale.jpg 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Instagramaphone&lt;/h3&gt;
&lt;p&gt;This one made me laugh. Built by Dina Lamdany, Christopher Yan, Daria Jung and Nate Brennand,
&lt;a href=&quot;http://instagramophone.info/&quot;&gt;Instagramaphone&lt;/a&gt; allows you to record audio and then apply
“Filters” before sharing it with the world. Such a brilliant concept and really well
executed. &lt;a href=&quot;http://instagramophone.info/&quot;&gt;Check it out&lt;/a&gt; and start playing with filters now.
Oh, and they had a banana on their team. I applaud this.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/806cfc30f521eb9c01e9562ca8bcd587/df2c7/instagramaphone.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.33333333333331%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAwH/xAAXAQADAQAAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAGlKcm51RJA3t1LCpbFBn//xAAcEAACAgMBAQAAAAAAAAAAAAABAgARAwQiIxL/2gAIAQEAAQUCxmptN76r+Rb5Ow/ZJmd+1fFV1GJKwz//xAAVEQEBAAAAAAAAAAAAAAAAAAAQAf/aAAgBAwEBPwEp/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAIhEBL/2gAIAQIBAT8BWnIsz//EABwQAAEEAwEAAAAAAAAAAAAAAAAQESExAQJRgf/aAAgBAQAGPwIyel2lE4g2fV36k9X/xAAaEAEAAwEBAQAAAAAAAAAAAAABABEhMUFR/9oACAEBAAE/IYVK/hNEIl9LB8jIqjPIqkXPMhpEx55ClZ4XpE4uK1XUG9nU/9oADAMBAAIAAwAAABAfCzD/xAAYEQADAQEAAAAAAAAAAAAAAAAAAREQIf/aAAgBAwEBPxBvpcU//8QAFxEBAQEBAAAAAAAAAAAAAAAAAREAEP/aAAgBAgEBPxAxc4JDn//EAB8QAQACAgICAwAAAAAAAAAAAAEAESExUWFBcYGhsf/aAAgBAQABPxBFkrqICxZ9S6xzf8IiArIacTNEqqMrfmC5oFtS/RKrlVeTjvEMCnRYK4+Y6LU8y90pHpu4l0rDVxR//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Instagramaphone Team&quot;
        title=&quot;The Instagramaphone Team&quot;
        src=&quot;/blog/static/806cfc30f521eb9c01e9562ca8bcd587/df2c7/instagramaphone.jpg&quot;
        srcset=&quot;/blog/static/806cfc30f521eb9c01e9562ca8bcd587/f544b/instagramaphone.jpg 200w,
/blog/static/806cfc30f521eb9c01e9562ca8bcd587/41689/instagramaphone.jpg 400w,
/blog/static/806cfc30f521eb9c01e9562ca8bcd587/df2c7/instagramaphone.jpg 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I could go on, there were so many amazing hacks. Special mention to &lt;a href=&quot;http://blog.pennapps.com/post/40993980632/whos-hacking-what-the-musical-toilet&quot;&gt;The Musical Toilet&lt;/a&gt; which lets users
control SoundCloud with their, ahem, stream.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Architecture behind our new Search and Explore experience]]></title><description><![CDATA[Search is front-and-center in the new SoundCloud, key to the consumer experience. We’ve made the search box one of the first things you see…]]></description><link>https://developers.soundcloud.com/blog/architecture-behind-our-new-search-and-explore-experience</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/architecture-behind-our-new-search-and-explore-experience</guid><pubDate>Tue, 04 Dec 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Search is front-and-center in the new SoundCloud, key to the consumer experience. We’ve made the search box one of the first things you see, and beefed it up with suggestions that allow you to jump directly to people, sounds, groups, and sets of interest. We’ve also added a brand-new Explore section that guides you through the huge and dynamic landscape of sounds on SoundCloud. We’ve also completely overhauled our search infrastructure, which helps us provide more relevant results, scale with ease, and experiment quickly with new features and models.&lt;/p&gt;
&lt;h3&gt;In the beginning&lt;/h3&gt;
&lt;p&gt;In SoundCloud’s startup days, when we were growing our product at super speed, we spent just two days implementing a straightforward search feature, integrating it directly into our main app. We used Apache Solr, as was the fashion at the time, and built a master-slave cluster with the common semantics: writes to the master, reads from the slaves. Besides a separate component for processing deletes, our indexing logic was pull-based: the master Solr instance used a Solr &lt;a href=&quot;http://wiki.apache.org/solr/DataImportHandler&quot;&gt;DataImportHandler&lt;/a&gt; to poll our database for changes, and the slaves polled the master.&lt;/p&gt;
&lt;p&gt;At first, this worked well. But as time went on, our data grew, our entities became more complex, and our business rules multiplied. We began to see problems.&lt;/p&gt;
&lt;h3&gt;The problems&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The index was only getting updated on the read slaves about every fifteen minutes. Being real-time is crucial in the world of sound, as it’s important that our users’ sounds are searchable immediately after upload.&lt;/li&gt;
&lt;li&gt;A complete re-index of the master node from the database took up to 24 hours, which made making a simple schema change for a new feature or bugfix a Sisyphean task.&lt;/li&gt;
&lt;li&gt;Worse, during those complete-reindex periods, we couldn’t do incremental indexing because of limitations in the DataImportHandler. That meant the search index was essentially frozen in time: not a great user experience.&lt;/li&gt;
&lt;li&gt;Because the search infrastructure was tightly coupled in the main application, low-level Solr or Lucene knowledge leaked everywhere. If we wanted to make a slight change, such as to a filter, we had to touch 25 parts of the codebase in the main application.&lt;/li&gt;
&lt;li&gt;Because the application directly interacted with the master, if that single-point-of-failure failed, we lost information about updates, and our only reliable recovery was a complete reindex, which took 24 hours.&lt;/li&gt;
&lt;li&gt;Since Solr uses segment or bulk replication, our network was flooded when the slaves pulled updates from the master, which caused additional operational issues.&lt;/li&gt;
&lt;li&gt;Solr felt like it was built in a different age, for a different use-case. When we had performance problems, it was opaque and difficult to diagnose.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In early 2012, we knew we couldn’t go forward with many new features and enhancements on the existing infrastructure. We formed a new team and decided we should rebuild the infrastructure from scratch, learning from past lessons. In that sense, the Solr setup was an asset: it could continue to serve site traffic, while we were free to build and experiment with a parallel universe of search. Green-field development, unburdened by legacy constraints: every engineer’s dream.&lt;/p&gt;
&lt;h3&gt;The goals&lt;/h3&gt;
&lt;p&gt;We had a few explicit goals with the new infrastructure.&lt;/p&gt;
&lt;p&gt;*
&lt;strong&gt;Velocity&lt;/strong&gt;: we want high velocity when working on schema-affecting bugs and features, so a complete reindex should take on the order of an hour.
*
&lt;strong&gt;Reliability&lt;/strong&gt;: we want to grow to the next order of magnitude and beyond, so scaling the infrastructure—horizontally and vertically—should require minimum effort.
*
&lt;strong&gt;Maintainability&lt;/strong&gt;: we don’t want to leak implementation details beyond our borders, so we should strictly control access through APIs that we design and control.&lt;/p&gt;
&lt;p&gt;After a survey of the state of the art in search, we decided to abandon Solr in favor of ElasticSearch, which would theoretically address our reliability concerns in their entirety. We then just had to address our velocity and maintenance concerns with our integration with the rest of the SoundCloud universe. To the whiteboard!&lt;/p&gt;
&lt;h3&gt;The plan&lt;/h3&gt;
&lt;p&gt;On the read side, everything was pretty straightforward. We’d already built a simple API service between Solr and the main application, but had hit some roadblocks when integrating it. We pivoted and rewrote the backend of that API to make ElasticSearch queries, instead: an advantage of a lightweight service-oriented architecture is that refactoring services in this way is fast and satisfying.&lt;/p&gt;
&lt;p&gt;On the write side—conceptually, at least—we had a straightforward task. Search is relatively decoupled from other pieces of business and application logic. To maintain a consistent index, we only need an authoritative source of events, ie. entity creates, updates, and deletes, and some method of enriching those events with their searchable metadata. We had that in the form of our message broker. Every event that search cared about was being broadcast on a well-defined set of exchanges: all we had to do was listen in.&lt;/p&gt;
&lt;p&gt;But depending on broker events to materialize a search index is dangerous. We would be susceptible to schema changes: the producers of those events could change the message content, and thereby break search without knowing, potentially in very subtle ways. The simplest, most robust solution was to ignore the content of the messages, and perform our own hydration. That carries a runtime cost, but it would let us better exercise control over our domain of responsibility. We believed the benefits would outweigh the costs.&lt;/p&gt;
&lt;p&gt;Using the broker as our event source, and the database as our ground truth, we sketched an ingestion pipeline for the common case. Soon, we realized we had to deal with the problem of synchronization: what should we do if an event hadn’t yet been propagated to the database slave we were querying? It turned out that if we used the timestamp of the event as the “external” version of the document in ElasticSearch, we could lean on ElasticSearch’s consistency guarantees to detect, and resolve, problems of sync.&lt;/p&gt;
&lt;p&gt;Once it hit the whiteboard, it became clear we could re-use the common-case pipeline for the bulk-index workflow; we just had to synthesize creation events for every entity in the database. But could we make it fast enough? It seemed at least possible. The final box-diagram looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/assets/search-infra1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Implementation and optimization began. Over a couple of weeks, we iterated over several designs for the indexing service. We eked out a bit more performance in each round, and in the end, we got where we wanted: indexing our entire corpus against a live cluster took around 2 hours, we had no coupling in the application, and ElasticSearch itself gave us a path for scaling.&lt;/p&gt;
&lt;h3&gt;The benefits&lt;/h3&gt;
&lt;p&gt;We rolled out the new infrastructure in a dark launch on our beta site. Positive feedback on the time-to-searchability was immediate: newly-posted sounds were discoverable in about 3 seconds (post some sounds and try it out for yourself!). But the true tests came when we started implementing features that had been sitting in our backlog. We would start support for a new feature in the morning, make schema changes into a new index over lunch, and run A/B tests in the afternoon. If all lights were green, we could make an immediate live swap. And if we had any problems, we could rollback to the previous index just as fast.&lt;/p&gt;
&lt;p&gt;There’s no onerous, manual work involved in bootstrapping or maintaining nodes: everything is set up with ElasticSearch’s REST API. Our index definitions are specified in flat JSON files. And we have a single dashboard with all the metrics we need to know how search is performing, both in terms of load and search quality.&lt;/p&gt;
&lt;p&gt;In short, the new infrastructure has been an unqualified success. Our velocity is through the roof, we have a clear path for orders-of-magnitude growth, and we have a stable platform for a long time to come.&lt;/p&gt;
&lt;h3&gt;Enter DiscoRank&lt;/h3&gt;
&lt;p&gt;It’s not the number of degrees of separation between you and John Travolta: DiscoRank is our modification of the PageRank algorithm to provide more relevant search results: short for Discovery Rank. We match your search against our corpus and use DiscoRank to rank the results.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/assets/cool_disco.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;The DiscoRank algorithm involves creating a graph of searchable entities, in which each activity on SoundCloud is an edge connecting two nodes. It then iteratively computes the ranking of each node using the weighted graph of entities and activities. Our graph has millions and millions of nodes and a lot more edges. So, we did a lot of optimizations to adapt PageRank to our use case, to be able to keep the graph in memory, and to recalculate the DiscoRank quickly, using results from previous runs of the algorithm as priors. We keep versioned copies of the DiscoRank so that we can swap between them when testing things out.&lt;/p&gt;
&lt;h3&gt;How do we know we’re doing better?&lt;/h3&gt;
&lt;p&gt;Evaluating the relevance of search results is a challenge. You need to know what people are looking for (which is not always apparent from their query) and you need to get a sample that’s representative enough to judge improvement Before we started work on the new infrastructure, we did research and user
testing to better understand how and why people were using search on SoundCloud.&lt;/p&gt;
&lt;p&gt;Our evaluation baseline is a set of manually-tagged queries. To gather the first iteration of that data, we built an internal evaluation framework for SoundCloud employees: thumbs up, thumbs down on results. In addition, we have metrics like click positions, click engagement, precision, and recall, all constantly updating in our live dashboard. This allows us to compare ranking algorithms and other changes to the way we construct and execute queries. And we have mountains of click log data that we need to analyse.&lt;/p&gt;
&lt;p&gt;We have some good improvements so far, but there’s still a lot of tuning that can be done.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/assets/SearchTimings-505x378.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/assets/SuggesterTimings-505x378.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Search as navigation&lt;/h3&gt;
&lt;p&gt;The new Suggest feature lets you jump straight to the sound, profile, group, or set you’re looking for. We’ll trigger your memory if you don’t remember how something is spelled, or exactly what it’s called. Since we know the types of the results returned, we can send you straight to the source. This ability to make assumptions and customizations is a consequence of knowing the structure and semantics of our data, and gives a huge advantage in applications like this.&lt;/p&gt;
&lt;p&gt;Architecture-wise, we decided to separate the suggest infrastructure from the main search cluster, and build a custom engine based on Apache Lucene’s Finite State Transducers. As ever, delivering results fast is crucial. Suggestions need to show up right after the keystroke. It turned out we are competing with the speed of light for this particular use-case.&lt;/p&gt;
&lt;p&gt;The fact that ElasticSearch doesn’t come with a suggest engine turned out to be a non-issue and rather forced us to build this feature in isolation. This separation proved to be a wise decision, since update-rate, request patterns and customizations are totally different, and would have made a built-in solution hard to maintain.&lt;/p&gt;
&lt;h3&gt;Search as a crystal ball&lt;/h3&gt;
&lt;p&gt;Similar to suggest, &lt;a href=&quot;https://www.soundcloud.com/explore&quot;&gt;Explore&lt;/a&gt; is based on our new search infrastructure, but with a twist: our main goal for Explore is to showcase the sounds in our community at this very moment. This led to a time-sensitive ranking, putting more emphasis on the newness of a sound.&lt;/p&gt;
&lt;h3&gt;So far, so good&lt;/h3&gt;
&lt;p&gt;We hope you enjoy using the new search features of SoundCloud as much as we enjoyed building them. Staying focused on a re-architecture is painful when you see your existing infrastructure failing more and more each day, but we haven’t looked back—especially since our new infrastructure allows us to deliver a better user experience much faster. We now can roll out new functionality with ease, and the new Suggest and Explore along with an improved Search itself are just the first results.&lt;/p&gt;
&lt;p&gt;Keep an eye out for more improvements to come!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Apache Lucene and Solr are trademarks of the Apache Software Foundation&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Coding for GOOD - SoundCloud API]]></title><description><![CDATA[The web has come a long way. We have APIs that allow developers to
create amazing applications quickly, and browser technologies have…]]></description><link>https://developers.soundcloud.com/blog/coding-for-good</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/coding-for-good</guid><pubDate>Fri, 02 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The web has come a long way. We have APIs that allow developers to
create amazing applications quickly, and browser technologies have
advanced to the point where JavaScript / HTML and CSS are necessary
tools for creating apps. Despite this, there’s still a skills gap and
companies are having an increasingly hard time hiring for technical
positions.&lt;/p&gt;
&lt;p&gt;This is the main reason the folks at &lt;a href=&quot;http://www.good.is&quot;&gt;GOOD&lt;/a&gt; teamed
up with the &lt;a href=&quot;http://www.apollogrp.edu/&quot;&gt;Apollo Group&lt;/a&gt; to launch
&lt;a href=&quot;http://cfg.good.is/&quot;&gt;Coding for GOOD&lt;/a&gt;. A series of coding lessons and
final project. Participants with the top three submissions will be flown
to Los Angeles to participate in a hackathon. The best of the three hacks
may be offered a job at GOOD.&lt;/p&gt;
&lt;p&gt;We were excited when GOOD approached us about contributing a lesson on
the SoundCloud API, so I sat down with them in their New York office
and did a quick walkthrough on using the
&lt;a href=&quot;https://developers.soundcloud.com/docs/api/sdks#javascript&quot;&gt;JavaScript SDK&lt;/a&gt;
to create a simple SoundCloud app. Check it out, and if you know of anyone
new to using web APIs, have them take a look too.&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 56.25%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;p&gt; &lt;iframe src=&quot;http://www.youtube.com/embed/8TuqjGxosrc&quot; frameborder=&quot;0&quot; allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/p&gt; &lt;/div&gt;</content:encoded></item><item><title><![CDATA[Story Hack Boston Recap]]></title><description><![CDATA[On Saturday, we hosted
Story Hack Boston along with
P2PU and Mashery at the
MIT Media Lab. About 50 people from
content and technical backgrounds joined us to create new story
telling experiences. The crowd was pretty evenly split, which made for
a lot of awesome collaboration.]]></description><link>https://developers.soundcloud.com/blog/story-hack-recap</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/story-hack-recap</guid><pubDate>Tue, 02 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On Saturday, we hosted
&lt;a href=&quot;http://storyhack.splashthat.com&quot;&gt;Story Hack Boston&lt;/a&gt; along with
&lt;a href=&quot;http://p2pu.org&quot;&gt;P2PU&lt;/a&gt; and &lt;a href=&quot;http://www.mashery.org&quot;&gt;Mashery&lt;/a&gt; at the
&lt;a href=&quot;http://www.media.mit.edu&quot;&gt;MIT Media Lab&lt;/a&gt;. About 50 people from
content and technical backgrounds joined us to create new story
telling experiences. The crowd was pretty evenly split, which made for
a lot of awesome collaboration.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2f758e7aa3dba16714272b363425b8f9/df2c7/storyhack.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQDAQX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAd9vH0GxIiu0mJAv/8QAGhAAAwEBAQEAAAAAAAAAAAAAAAERAgMSE//aAAgBAQABBQLldjQ7c9dZPXWfRGCjSv8A/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAh/9oACAEDAQE/ASP/xAAXEQADAQAAAAAAAAAAAAAAAAAAAREQ/9oACAECAQE/AYR7/8QAHBAAAQMFAAAAAAAAAAAAAAAAAAERMQISICFB/9oACAEBAAY/ApJJG6XK7G6lw//EABoQAAIDAQEAAAAAAAAAAAAAAAABESFRQXH/2gAIAQEAAT8hV0NFQ3JgE/J0opM0E3q8H8HJWcw//9oADAMBAAIAAwAAABDUKH7/xAAWEQEBAQAAAAAAAAAAAAAAAAAQASH/2gAIAQMBAT8QwU//xAAWEQEBAQAAAAAAAAAAAAAAAAABEDH/2gAIAQIBAT8QYHZ//8QAHBABAQEBAAIDAAAAAAAAAAAAAREAITGBQVFh/9oACAEBAAE/EKFXVnPvFJD0YlA3oxAdYZObwtkRxYeefuPXvPidGMo+ZmnCYSmf/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;StoryHackers&quot;
        title=&quot;StoryHackers&quot;
        src=&quot;/blog/static/2f758e7aa3dba16714272b363425b8f9/df2c7/storyhack.jpg&quot;
        srcset=&quot;/blog/static/2f758e7aa3dba16714272b363425b8f9/f544b/storyhack.jpg 200w,
/blog/static/2f758e7aa3dba16714272b363425b8f9/41689/storyhack.jpg 400w,
/blog/static/2f758e7aa3dba16714272b363425b8f9/df2c7/storyhack.jpg 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;This event was the first of it’s kind for us, and therefore a bit of
an experiment. Hack days that bring together people from a variety of
disciplines around a specific topic can be really interesting and
present their own set of challenges. Judging from the tone of the day
and the outcome, I think it turned out really well.&lt;/p&gt;
&lt;p&gt;There were 10 hacks demo’d in total. A couple of my favorites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SoundStep - An iOS application that uses geolocation to tell you about stories that have been recorded in your hood! Amazing to see a working iOS app built and demo’d in a single day.&lt;/li&gt;
&lt;li&gt;Story Prompter - A web application that pulls headlines from &lt;a href=&quot;http://www.usatoday.com/&quot;&gt;USA Today&lt;/a&gt; using Mashery’s API and asks you to record a story about the headline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We had some really great content hacks as well. See the full list on
&lt;a href=&quot;https://www.hackerleague.org/hackathons/story-hack-boston/hacks&quot;&gt;Hacker League&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some reports on the day from happy hackers:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e410f309650f3a2fbf30fb6f1a5fc543/c7805/shbtweet1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.75000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABQUlEQVQoz31RSU7DMBTNPTgCB+Eq7Niw4wBwDS7AsEcs2LFASAVKZidxnKFtbJK4marmYUcCFVF40pP+t/9/fzJmz084Pz3G/c0lOOeQUkJjHMd/qdH2A16zEp5PkCQMfd/DuL2+wtHhAS7OTmA5DojvY72W2G63e4V30XQ97IzDVzlBEKDrOhhJkuDx4Q5vs9kUNAyD+uh/iO1C+5vNBn/BaNsWQgjkeYo4prBtE4R4yo5gvs/BWKx8Atd14XkeKKUqNodpmrAsa/IZY9/Fja4b4HgpPJKrXeSwnRQv8xiOmyEIl0okQBSFCMNwEtCj6QJxHEM3o6l39zWJ0bQD0rwCFxIhFYoci2U9vbG0wmolUJbVdDDNuq5RFAWapvk17tQh/2hAE46qlsgWNSKmEkWJZSFBaKHsCuum37uvfQf7BO00D7uT/XArAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Happy Hackers&quot;
        title=&quot;Happy Hackers&quot;
        src=&quot;/blog/static/e410f309650f3a2fbf30fb6f1a5fc543/c7805/shbtweet1.png&quot;
        srcset=&quot;/blog/static/e410f309650f3a2fbf30fb6f1a5fc543/9ec3c/shbtweet1.png 200w,
/blog/static/e410f309650f3a2fbf30fb6f1a5fc543/c7805/shbtweet1.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/fc5db5fe313e9740c6968941f213570a/c7805/shbtweet2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABHUlEQVQoz51ROU7EQBD0FwgRL96MAImAlGCfAOIFzti1wOysx9f4Xt92gGTZRfcILKQFAloqlbqnu/oY43qzwcPjE8ZxRNM0qOsaZVmiKArM8wy2ZVlW/gJ5yMd3yKxClibo+17nG5cXV7i9uUPoezBNc4VlWZim6UyIMX8K+tWAvVSwds+Ioki/Gdv7LV52e53wfZr/mBbsulavxx1UGCImDomzLNMoTicopdacJEng+z48z4NLEMcjAvKrqtKixoECthCQQaD5zXEgKUFRYZymCCjODVzXhaB3FuJ7dV2nuW1bDMOwnsdIKKnmDlIitW3kdLuaikryB5qKJ+QC/jBJMZ7wz5URx6C2gHQAcQDsV2IB0OTI818LfwLbB4MgDiJ3pSoUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;More Happy&quot;
        title=&quot;More Happy&quot;
        src=&quot;/blog/static/fc5db5fe313e9740c6968941f213570a/c7805/shbtweet2.png&quot;
        srcset=&quot;/blog/static/fc5db5fe313e9740c6968941f213570a/9ec3c/shbtweet2.png 200w,
/blog/static/fc5db5fe313e9740c6968941f213570a/c7805/shbtweet2.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/992fd35bf993a2574229d3d02c0fdc0d/c7805/shbtweet3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABiUlEQVQoz32SW27TUBCGvSv2glgEm+CJF1aA2AESQhXllTygINFEuHF8v8fX2rGJb22Sj+MDFVWQGGk0M0fz//OP5ihe5PPh6j3rzTe6vqfveqqqoq5ruq7jfD4z2xyf5rPdPxxJ20H2t23L8XhEufpyzfMXz3j15iXrWxX1h8pyuWSxWLBarWTTI9mjn/4QNv3IVy9ndfMdTdMYxxFFtyzevnvNp88fmdvGafoNOp0k2f9tRpz+VgKnTMPAoT1QFQVpHJHEIUkUEgY+URTJdTzPI0kTGcuyZLfbEYahHHppStMP6HHGNsxQg5QbJ8ZI7jDDHbZQPwM938eybRzHwRZxs9nI9yRJ2O/3ZFlGLgT14gbKYbrHyvc4dx1G3mCXPzGLlq0b4FimAAZSbSBIZ4VzbRiGzJ8qnl0SNmKC7zoCbGBuNQH08IOArW6gWw6W63Grm6R5QZSkuKKO4xhLqC+Eqn9WniU7rothmmhilbobMJsHHOFaNaGWA+tyZCOiWU/0F3e6/AG/AO3/VV3RWrZAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Even More Happy&quot;
        title=&quot;Even More Happy&quot;
        src=&quot;/blog/static/992fd35bf993a2574229d3d02c0fdc0d/c7805/shbtweet3.png&quot;
        srcset=&quot;/blog/static/992fd35bf993a2574229d3d02c0fdc0d/9ec3c/shbtweet3.png 200w,
/blog/static/992fd35bf993a2574229d3d02c0fdc0d/c7805/shbtweet3.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;A huge thanks to our sponsors, &lt;a href=&quot;http://p2pu.org&quot;&gt;P2PU&lt;/a&gt; and
&lt;a href=&quot;http://mashery.com&quot;&gt;Mashery&lt;/a&gt; for making this event possible.&lt;/p&gt;
&lt;p&gt;P2PU did a &lt;a href=&quot;http://info.p2pu.org/2012/10/01/the-hackathon-revamped-recs-for-mixing-hackers-storytellers/&quot;&gt;great write-up&lt;/a&gt; about lessons learned during Story Hack Day. We also got some nice coverage
in &lt;a href=&quot;http://bostinno.com&quot;&gt;BostInno&lt;/a&gt; &lt;a href=&quot;http://bostinno.com/2012/09/28/story-hack-boston-mit-media-lab/&quot;&gt;before&lt;/a&gt; and &lt;a href=&quot;http://bostinno.com/2012/09/30/storytellers-technologists-merge-at-the-mit-media-lab-to-hack-a-new-kind-of-narrative/&quot;&gt;after&lt;/a&gt; the event.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Next App Gallery Update]]></title><description><![CDATA[We’re making some changes to how we manage our
App Gallery and wanted to take some time
to explain them to you, our developer community. The…]]></description><link>https://developers.soundcloud.com/blog/the-next-app-gallery-update</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/the-next-app-gallery-update</guid><pubDate>Tue, 18 Sep 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re making some changes to how we manage our
&lt;a href=&quot;https://soundcloud.com/apps&quot;&gt;App Gallery&lt;/a&gt; and wanted to take some time
to explain them to you, our developer community.&lt;/p&gt;
&lt;p&gt;The App Gallery is where we highlight interesting and useful
SoundCloud powered apps and services for our users. As our developer
community continues to grow, it’s even more important that we keep a
high bar for apps found in App Gallery. Having a high standard
protects the value of being featured in App Gallery for all of our
developers while giving our users a sense of confidence in the caliber
of content we’re showcasing there.&lt;/p&gt;
&lt;p&gt;When evaluating a newly submitted app for App Gallery, we consider the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the app both high quality and adhering to our &lt;a href=&quot;https://developers.soundcloud.com/docs/api/terms-of-use&quot;&gt;API Terms of Use&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Does it enable new and valuable experiences for SoundCloud users?&lt;/li&gt;
&lt;li&gt;Has it shown notable traction in gaining connected users?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moving forward, we’ll be proactively reaching out to folks who meet
this criteria about adding their apps to App Gallery instead of
accepting mass submissions like we have in the past. Our long term
hope is to completely rebuild App Gallery to work in conjunction with
&lt;a href=&quot;https://next.soundcloud.com&quot;&gt;Next SoundCloud&lt;/a&gt; in a way that better serves our entire developer
community. That includes ideas like better attribution of content
within SoundCloud products and easier, relevant discovery within App
Gallery for SoundCloud users.&lt;/p&gt;
&lt;p&gt;If you’re confident your app meets our criteria and we haven’t gotten
in touch with you, feel free to drop us a line at &lt;a href=&quot;mailto:api@soundcloud.com&quot;&gt;api@soundcloud.com&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to Make a SoundCloud Powered Children's Toy]]></title><description><![CDATA[Just over a week ago we had our first internal hackathon at
SoundCloud. You can read (and listen!) about it on our community
blog or read…]]></description><link>https://developers.soundcloud.com/blog/how-to-make-a-soundcloud-powered-toy</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-to-make-a-soundcloud-powered-toy</guid><pubDate>Thu, 30 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just over a week ago we had our first internal hackathon at
SoundCloud. You can read (and listen!) about it on our &lt;a href=&quot;https://blog.soundcloud.com/2012/08/20/we-hack/&quot;&gt;community
blog&lt;/a&gt; or read
some of the &lt;a href=&quot;http://www.businessinsider.com/check-out-the-awesome-hovercraft-this-startups-engineers-built-in-a-48-hour-hackathon-2012-8&quot;&gt;awesome&lt;/a&gt;
&lt;a href=&quot;http://www.rollingstone.com/culture/blogs/gear-up/soundcloud-founder-alex-ljung-encourages-company-wide-sound-hacking-20120824&quot;&gt;press coverage&lt;/a&gt;
the event received.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/cc0584fca1e9478e0e3f9ed685442eb8/48a11/wehack.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQFAv/EABgBAQADAQAAAAAAAAAAAAAAAAABAgME/9oADAMBAAIQAxAAAAGPWxbl6GUOBLoZdX//xAAaEAEBAAMBAQAAAAAAAAAAAAABAgADESES/9oACAEBAAEFAtT1i0Hfc4U9m/kfWXLtwfP/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQMBAT8BYiP/xAAVEQEBAAAAAAAAAAAAAAAAAAABIP/aAAgBAgEBPwFj/8QAHBAAAwACAwEAAAAAAAAAAAAAAAERISISIFGB/9oACAEBAAY/Ao6cUiWfDVZ9Nnkb6f/EABwQAQEBAAEFAAAAAAAAAAAAAAERADEhQVFhkf/aAAgBAQABPyFawJ2w5SnFcrS9Eb5QyIt08TNULW5TjJJn8N//2gAMAwEAAgADAAAAEAfQP//EABgRAAMBAQAAAAAAAAAAAAAAAAABEUEx/9oACAEDAQE/EJVNEunQiiP/xAAYEQEBAAMAAAAAAAAAAAAAAAAAARExQf/aAAgBAgEBPxCOsKu3/8QAHhABAQEAAQQDAAAAAAAAAAAAAREAITFBUWFxgZH/2gAIAQEAAT8QJjqLwVvTjCitIKnmvfEngcLM/cpWK+D6yIoUog9rbzgJOR5Set1pMASQ9Zgop5N//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Hacking&quot;
        title=&quot;Hacking&quot;
        src=&quot;/blog/static/cc0584fca1e9478e0e3f9ed685442eb8/48a11/wehack.jpg&quot;
        srcset=&quot;/blog/static/cc0584fca1e9478e0e3f9ed685442eb8/f544b/wehack.jpg 200w,
/blog/static/cc0584fca1e9478e0e3f9ed685442eb8/41689/wehack.jpg 400w,
/blog/static/cc0584fca1e9478e0e3f9ed685442eb8/48a11/wehack.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We had over 60 people attend and over 20 projects were demoed. I
joined a team organized by
&lt;a href=&quot;https://soundcloud.com/joshdevins&quot;&gt;Josh Devins&lt;/a&gt; to build
&lt;a href=&quot;https://github.com/soundcloud/toybox&quot;&gt;ToyBox&lt;/a&gt;, a children’s toy that
plays sounds from SoundCloud in response to physical events. The team
consisted of myself, &lt;a href=&quot;https://soundcloud.com/horaci&quot;&gt;Horaci Cuevas&lt;/a&gt;,
&lt;a href=&quot;https://soundcloud.com/joshdevins&quot;&gt;Josh Devins&lt;/a&gt;, and
&lt;a href=&quot;https://soundcloud.com/oliver-hookins&quot;&gt;Oliver Hookins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first task was to come up with a design for our project. An
&lt;a href=&quot;http://www.arduino.cc/&quot;&gt;Arduino&lt;/a&gt; fitted with gyro, accelerometer
and motion sensors provided the interface between real world events
and the rest of the system. A &lt;a href=&quot;http://www.raspberrypi.org/&quot;&gt;Raspberry Pi&lt;/a&gt;
would then run the following pieces of software:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/soundcloud/toybox/tree/master/pi/downloader&quot;&gt;toybox:downloader&lt;/a&gt; - The downloader polls a webapp for data about what sounds from SoundCloud should be played in response to certain events. In order to make the toy more responsive, toybox:downloader downloads the sounds from SoundCloud and stores them on disk (The Raspberry Pi’s SD card).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/soundcloud/toybox/tree/master/pi/playback&quot;&gt;toybox:playback&lt;/a&gt; - An asynchronous wrapper around mplayer.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/soundcloud/toybox/tree/master/pi/serial&quot;&gt;toybox:serial&lt;/a&gt; - An app that listens for data on the Raspberry Pi’s serial port. Event data from the Arduino is printed out on stdout for other processes to consume. toybox:serial also uses &lt;a href=&quot;http://www.zeromq.org/&quot;&gt;0MQ&lt;/a&gt; to push event data to a web interface so you can monitor a toy’s activity from a browser. The 0MQ consumer also responds to events by playing the appropriate sounds using toybox:playback.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To provide an interface for toy owners to register a toy (id’d by the
Raspberry Pi’s MAC address) and associate SoundCloud sounds with
events, we built a simple &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt;
application that we hosted on
Heroku. &lt;a href=&quot;https://github.com/soundcloud/toybox-webapp&quot;&gt;toybox:webapp&lt;/a&gt;
also provides a couple of endpoints that serve JSON describing what
sounds should be played when for a particular toy. This provided the
service endpoint needed by toybox:downloader.&lt;/p&gt;
&lt;p&gt;Knowing what needed to be built, we each started hacking on a piece. Horaci
worked on toybox:serial, Oliver worked on toybox:downloader and toybox:playback
and I worked on toybox:webapp. Josh focused on the Arduino soldering and coding
and Oliver and Horaci helped get the Pi set up. You can see the &lt;a href=&quot;https://github.com/soundcloud/toybox/blob/master/arduino/arduino.ino&quot;&gt;Arduino code here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There was a bit of frantic integration work at the end, but it did all come
together nicely. It was great to see sounds being played from SoundCloud
whenever a button was pushed, or the toybox was shaken, tilted or spoken to.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0751508718ebb654039047dfd12bc02c/48a11/toybox.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQCAQP/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/2gAMAwEAAhADEAAAAa9ze45cGpLRJBX/xAAcEAACAQUBAAAAAAAAAAAAAAAAAQIDERIhIjH/2gAIAQEAAQUCULS0YXOpkY85pFHaH7//xAAVEQEBAAAAAAAAAAAAAAAAAAARIP/aAAgBAwEBPwFj/8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQIBAT8BSP/EABsQAAIDAAMAAAAAAAAAAAAAAAABEBEhMUFh/9oACAEBAAY/Ane+xg2uzeTCi4//xAAdEAACAgIDAQAAAAAAAAAAAAABEQAhMUFhcYGR/9oACAEBAAE/IbOZ5bi2IHsBuQpxnURZlXIYaJ9wyV6hexc4h8n/2gAMAwEAAgADAAAAEBfAAf/EABcRAAMBAAAAAAAAAAAAAAAAAAEQESH/2gAIAQMBAT8QGogv/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EMDYs//EABwQAQADAAMBAQAAAAAAAAAAAAEAESExQWFRgf/aAAgBAQABPxBDs9sbFcxMwq7BN1rNGWaORNOP2WBUCXzf7KeqV2OYsUHcdj8pXkpbt8T/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ToyBox&quot;
        title=&quot;ToyBox&quot;
        src=&quot;/blog/static/0751508718ebb654039047dfd12bc02c/48a11/toybox.jpg&quot;
        srcset=&quot;/blog/static/0751508718ebb654039047dfd12bc02c/f544b/toybox.jpg 200w,
/blog/static/0751508718ebb654039047dfd12bc02c/41689/toybox.jpg 400w,
/blog/static/0751508718ebb654039047dfd12bc02c/48a11/toybox.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Making it pretty is a job for another hackathon :-)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Evolution of SoundCloud’s Architecture]]></title><description><![CDATA[This is a story of how we adapted our architecture over time to accomodate growth. Scaling is a luxury problem and surprisingly has more to…]]></description><link>https://developers.soundcloud.com/blog/evolution-of-soundclouds-architecture</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/evolution-of-soundclouds-architecture</guid><pubDate>Thu, 30 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a story of how we adapted our architecture over time to accomodate growth.&lt;/p&gt;
&lt;p&gt;Scaling is a luxury problem and surprisingly has more to do with organization than implementation. For each change we addressed the next order of magnitude of users we needed to support, starting in the thousands and now we’re designing for the hundreds of millions.  We identify our bottlenecks and addressed them as simply as possible by introducing clear integration points in our infrastructure to divide and conquer each problem individually.&lt;/p&gt;
&lt;p&gt;By identifying and extracting points of scale into smaller problems and having well defined integration points when the time arrived, we are able to grow organically.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#product-conception&quot;&gt;Product conception&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From day one, we had the simple need of getting each idea out of our heads and in front of eyeballs as quickly as possible. During this phase, we used a very simple setup:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/59a9eaf274f54dee6ae50e1ee9894ef2/54e54/e1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 5.172413793103449%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAAAsSAAALEgHS3X78AAAAPklEQVQI1z3HMRLAIAgEwPz/hbECihgRexia06Gw2GKfv39gZpgZRAREVJat+6ETU8c5o7X3XJGZcPcSEdcGpyJKrIHwepoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e1&quot;
        title=&quot;e1&quot;
        src=&quot;/blog/static/59a9eaf274f54dee6ae50e1ee9894ef2/54e54/e1.png&quot;
        srcset=&quot;/blog/static/59a9eaf274f54dee6ae50e1ee9894ef2/9ec3c/e1.png 200w,
/blog/static/59a9eaf274f54dee6ae50e1ee9894ef2/c7805/e1.png 400w,
/blog/static/59a9eaf274f54dee6ae50e1ee9894ef2/54e54/e1.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Apache was serving our image/style/behavior resources, and Rails backed by MySQL provided an environment where almost all of our product could be modeled, routed and rendered quickly. Most of our team understood this model and could work well together, delivering a product that is very similar to what we have today.&lt;/p&gt;
&lt;p&gt;We consciously chose not to implement high availability at this point, knowing what it would take when that time hopefully arrived. At this point we left our private beta, revealing SoundCloud to the public.&lt;/p&gt;
&lt;p&gt;Our primary cost optimization was for opportunity, and anything that got in the way of us developing the concepts behind SoundCloud were avoided. For example, when a new comment was posted, we blocked until all followers were notified knowing that we could make that asynchronous later.&lt;/p&gt;
&lt;p&gt;In the early stages we were conscious to ensure we were not only building a product, but also a platform. Our &lt;a href=&quot;https://developer.soundcloud.com/&quot;&gt;Public API&lt;/a&gt; was developed alongside our website from the very beginning. We’re now &lt;a href=&quot;https://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/&quot;&gt;driving the website&lt;/a&gt; with the same API we were offering to 3rd party integrations.&lt;/p&gt;
&lt;h2&gt;Growing out of Apache&lt;/h2&gt;
&lt;p&gt;Apache served us well, but we were running Rails app servers on multiple hosts, and the routing and virtual host configuration in Apache was cumbersome to keep in sync between development and production.&lt;/p&gt;
&lt;p&gt;The Web tier’s primary responsibility is to manage and dispatch incoming web requests, as well as buffering outbound responses so to free up an application server for the next request as quickly as possible. This meant the better connection pooling and content based routing configuration we had, the stronger this tier would be.&lt;/p&gt;
&lt;p&gt;At this point we replaced Apache with Nginx and reduced our web tier’s configuration complexity, but our architecture didn’t change.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4ffdf00e28a16c6f91cf7b6bff99bf4a/54e54/e2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 5.172413793103449%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAAAsSAAALEgHS3X78AAAANklEQVQI133HwQkAMAgEwfRfpYKeoAWorwukgDwWZk/AKSKsKqrqszvemxkDYGZyd9ndnJlvF9K4S+EUQzZmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e2&quot;
        title=&quot;e2&quot;
        src=&quot;/blog/static/4ffdf00e28a16c6f91cf7b6bff99bf4a/54e54/e2.png&quot;
        srcset=&quot;/blog/static/4ffdf00e28a16c6f91cf7b6bff99bf4a/9ec3c/e2.png 200w,
/blog/static/4ffdf00e28a16c6f91cf7b6bff99bf4a/c7805/e2.png 400w,
/blog/static/4ffdf00e28a16c6f91cf7b6bff99bf4a/54e54/e2.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#load-distribution-and-a-little-queue-theory&quot;&gt;Load distribution and a little queue theory&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nginx worked great, but as we were growing, we found that some workloads took significantly more time compared to others (in the order of hundreds of milliseconds).&lt;/p&gt;
&lt;p&gt;When you’re working on a slow request when a fast request arrives, the fast request will have to wait until the slow request finishes, called “&lt;a href=&quot;http://en.wikipedia.org/wiki/Head-of-line_blocking&quot;&gt;head of the line&lt;/a&gt; blocking problem”. When we had multiple applications servers each with its own listen socket backlog, analogous to a grocery store, where you inevitably stand at one register and watch all the other registers move faster than your own.&lt;/p&gt;
&lt;p&gt;Around 2008 when we first developed the architecture, concurrent request processing in Rails and ActiveRecord was fairly immature. Even though we felt confident that we could audit and prepare our code for concurrent request processing, we did not want to invest the time to audit our dependencies. So we stuck with the model of a single concurrency per application server process and ran multiple processes per host.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;http://en.wikipedia.org/wiki/Kendall&amp;#x27;s_notation&quot;&gt;Kendall’s notation&lt;/a&gt; once we’ve sent a request from the web server to the application server, the request processing can be modeled by a &lt;a href=&quot;http://en.wikipedia.org/wiki/M/M/1_queue&quot;&gt;M/M/1&lt;/a&gt; queue. The response time of such a queue depends on all prior requests, so if we drastically increase the average work time of one request the average response time also drastically increases.&lt;/p&gt;
&lt;p&gt;Of course, the right thing to do is to make sure our work times are consistently low for any web request, but we were still in the period of optimizing for opportunity, so we decided to continue with product development and solve this problem with better request dispatching.&lt;/p&gt;
&lt;p&gt;We looked at the Phusion passenger approach of using multiple child processes per host but felt that we could easily fill each child with long-running requests. This is like having many queues with a few workers on each queue, simulating concurrent request processing on a single listen socket.&lt;/p&gt;
&lt;p&gt;This changed the queue model from &lt;a href=&quot;http://en.wikipedia.org/wiki/M/M/1_queue&quot;&gt;M/M/1&lt;/a&gt; to &lt;a href=&quot;http://en.wikipedia.org/wiki/M/M/c_queue&quot;&gt;M/M/c&lt;/a&gt; where &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; is the number of child processes for every dispatched request. This is like the queue system found in a post office, or a “take a number, the next available worker will help you” kind of queue. This model reduces the response time by a factor of &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; for any job that was waiting in the queue which is better, but assuming we had 5 children, we would just be able to accept an average of 5 times as many slow requests. We were already seeing a factor of 10 growth in the upcoming months, and had limited capacity per host, so adding only 5 to 10 workers was not enough address the head of the line blocking problem.&lt;/p&gt;
&lt;p&gt;We wanted a system that never queued, but if it did queue, the wait time in the queue was minimal. Taking the &lt;a href=&quot;http://en.wikipedia.org/wiki/M/M/c_queue&quot;&gt;M/M/c&lt;/a&gt; model to the extreme, we asked ourselves “how can we make &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; as large as possible?”&lt;/p&gt;
&lt;p&gt;To do this, we needed to make sure that a single Rails application server never received more than one request at a time. This ruled out TCP load balancing because TCP has no notion of an HTTP request/response. We also needed to make sure that if all application servers were busy, the request would be queued for the next available application server. This meant we must maintain complete statelessness between our servers. We had the latter, but didn’t have former.&lt;/p&gt;
&lt;p&gt;We added &lt;a href=&quot;http://haproxy.1wt.eu/&quot;&gt;HAProxy&lt;/a&gt; into our infrastructure, configuring each backend with a maximum connection count of 1 and added our backend processes across all hosts, to get that wonderful &lt;a href=&quot;http://en.wikipedia.org/wiki/M/M/c_queue&quot;&gt;M/M/c&lt;/a&gt; reduction in resident wait time by queuing the HTTP request until any backend process on any host becomes available. HAProxy entered as our queuing load balancer that would buffer any temporary back-pressure by queuing requests from the application or dependent backend services so we could defer designing sophisticated queuing in other components in our request pipeline.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4339503b67abd7892be39a5ffe110d4a/54e54/e3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.379310344827587%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAAAhUlEQVQY06WOvQ7DIAyEef+nayM1A1PTDIFG0Ahj8TdcA1KjrKTD+WxL/nwixgi1LJieE4gIdQ4hNL8iUYv3HttnO2B/AXPOWNc3lNKo/Q92FSpSSu2YmVFKORKe1fNAMHtIKTE+Rlhr27I+OXtPWkHOYbjfdg14zTMcOWitYYxpXpP3QL/eXdCJnPobGAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e3&quot;
        title=&quot;e3&quot;
        src=&quot;/blog/static/4339503b67abd7892be39a5ffe110d4a/54e54/e3.png&quot;
        srcset=&quot;/blog/static/4339503b67abd7892be39a5ffe110d4a/9ec3c/e3.png 200w,
/blog/static/4339503b67abd7892be39a5ffe110d4a/c7805/e3.png 400w,
/blog/static/4339503b67abd7892be39a5ffe110d4a/54e54/e3.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I heartily recommend &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_J._Gunther&quot;&gt;Neil J. Gunther’s&lt;/a&gt; work &lt;a href=&quot;http://www.perfdynamics.com/iBook/ppa_new.html&quot;&gt;Analyzing Computer System Performance with Perl::PDQ&lt;/a&gt; to brush up on queue theory and strengthen your intuition on how to model and measure queuing systems from HTTP requests all the way down to your disk controllers.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#going-asynchronous&quot;&gt;Going asynchronous&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One class of request that took a long time was the fan-out of notifications from social activity. For example, when you upload a sound to SoundCloud, everyone that follows you will be notified. For people with many followers, if we were to do this synchronously, the request times would exceed the tens of seconds. We needed to queue a job that would be handled later.&lt;/p&gt;
&lt;p&gt;Around the same time we were considering how to manage our storage growth for sounds and images, and had chosen to offload storage to &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt; keeping transcoding compute in &lt;a href=&quot;http://aws.amazon.com/ec2/&quot;&gt;Amazon EC2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Coordinating these subsystems, we needed some middleware that would reliably queue, acknowledge and re-deliver job tickets on failure. We went through a few systems, but in the end settled on AMQP because of having a programmable topology, implemented by &lt;a href=&quot;http://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To keep the same domain logic that we had in the website, we loaded up the Rails environment and built a lightweight dispatcher class with one queue per concern.  The queues had a namespace that describes estimated work times. This created a priority system in our asynchronous workers without requiring adding the complexity of message priorities to the broker by starting one dispatcher process for each class of work that bound to multiple queues in that work class. Most of our queues for asynchronous work performed by the application are namespaced with either “interactive” (under 250ms work time) or “batch” (any work time). Other namespaces were used specific to each application.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/5c33e0ea39caa8b298ce951793d378d5/54e54/e4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.03448275862069%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAA3ElEQVQoz6WSyQqDMBiE8/6vJ7S3upu4VMUENXqYZgJKKVi0PQwhaL6ZfxFKSsRRhGHQsHbGNE2Y5/lnCWMMuq7zF8L+BjZNAykVlmXZge/ajE4Dx3EEUxJorfXiB96vwjywqkrc7zfXwwF933vR5OmS0+hywt71T7rBaK1R17UXgUoW3oSJr5QviiJHEARIswzaJSKMkHVdd9j285mkoixLxHEMnqN7QOCmowF9AwumYK+M0cjSBEmSeAOlFLgBYfhA5PaUm/BZ/uGUt4Hkee6gKdq23af9rjMlvwBNGwUTQPqa3wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e4&quot;
        title=&quot;e4&quot;
        src=&quot;/blog/static/5c33e0ea39caa8b298ce951793d378d5/54e54/e4.png&quot;
        srcset=&quot;/blog/static/5c33e0ea39caa8b298ce951793d378d5/9ec3c/e4.png 200w,
/blog/static/5c33e0ea39caa8b298ce951793d378d5/c7805/e4.png 400w,
/blog/static/5c33e0ea39caa8b298ce951793d378d5/54e54/e4.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#caching&quot;&gt;Caching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When we approached the hundreds of thousands user mark, we saw we were burning too much CPU in the application tier, mostly spent in the rendering engine and Ruby runtime.&lt;/p&gt;
&lt;p&gt;Instead of introducing &lt;a href=&quot;http://memcached.org/&quot;&gt;Memcached&lt;/a&gt; to alleviate IO contention in the database like most applications, we aggressively cached partial DOM fragments and full pages. This turned into an invalidation problem which we solved by maintaining the reverse index of cache keys that also needed invalidation on model changes in memcached.&lt;/p&gt;
&lt;p&gt;Our highest volume request was one specific endpoint that was delivering data for the widget. We created a special route for that endpoint in nginx and added proxy caching to that stack, but wanted to generalize caching to the point where any end point could produce proper HTTP/1.1 cache control headers and would be treated well by an intermediary we control. Now our &lt;a href=&quot;https://blog.soundcloud.com/2012/01/26/html5-widget/&quot;&gt;widget content&lt;/a&gt; is served entirely from our public API.&lt;/p&gt;
&lt;p&gt;We added &lt;a href=&quot;http://memcached.org/&quot;&gt;Memcached&lt;/a&gt; and much later &lt;a href=&quot;https://www.varnish-cache.org/&quot;&gt;Varnish&lt;/a&gt; to our stack to handle backend partially rendered template caching and mostly read-only API responses.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6f3af3d0fe190ec925393aeb5f0fa3c1/54e54/e5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.862068965517246%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAAA/UlEQVQoz52S3W6FIBCEef9H9KInCv4rVlEBvZjD0HMak6atejGSYPh2dmeF7ns8Hh/QWsN7D2stnHPf51UJY0yEWfsFeesuVNRVhTxXQTmklFBKwczzfSAdZlmGdV2xLEvU0eFVqCBoGIY4v+OPu22LcRxRluUP2G/6r4jI0hRJkqAPabP1aZpQ1xWapsG2bdG557n5U47Fvu+g+JAwYwisY0gMrCgKpKGoDMX0azR/OmzC47ZtMYdkOUuGwqA4ChMK8OQ9NZ9IX3QBRiAhdEjgcY6x5ZdOtcwPXXwGB0rJuItcdLbImTrnr4XCtSGw61qoMDcuNp2+A7m6Nk8jJp9bLHXiJAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e5&quot;
        title=&quot;e5&quot;
        src=&quot;/blog/static/6f3af3d0fe190ec925393aeb5f0fa3c1/54e54/e5.png&quot;
        srcset=&quot;/blog/static/6f3af3d0fe190ec925393aeb5f0fa3c1/9ec3c/e5.png 200w,
/blog/static/6f3af3d0fe190ec925393aeb5f0fa3c1/c7805/e5.png 400w,
/blog/static/6f3af3d0fe190ec925393aeb5f0fa3c1/54e54/e5.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#generalization&quot;&gt;Generalization&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our worker pools grew, handling more asynchronous tasks. The programming model was similar for all of them: take a domain model and schedule a continuation with that model state to be processed at a later state.&lt;/p&gt;
&lt;p&gt;Generalizing this pattern, we leveraged the after-save hooks in ActiveRecord models in a way we call &lt;em&gt;ModelBroadcast&lt;/em&gt;. The principle is that when the business domain changes, events are dropped on the AMQP bus with that change for any asynchronous client that is interested in that class of change. This technique of decoupling the write path from the readers enables the next evolution of growth by accommodating integrations we hadn’t foreseen.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;after_create &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;r&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;publish&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;models&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;create.&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter tag&quot;&gt;#{&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token delimiter tag&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

after_save &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;r&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;publish&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;models&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;save.&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter tag&quot;&gt;#{&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token delimiter tag&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;changes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

after_destroy &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;r&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;publish&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;models&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;destroy.&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter tag&quot;&gt;#{&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token delimiter tag&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This isn’t perfect, but it added a much needed non-disruptive, generalized, out-of-app integration point in the course of a day.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#dashboard&quot;&gt;Dashboard&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our most rapid data growth was the result of our Dashboard. The Dashboard is a personalized materialized index of activities inside of your social graph and the primary place to personalize your incoming sounds from the people you follow.&lt;/p&gt;
&lt;p&gt;We have always had a storage and access problem with this component. Looking at the read and write paths separately, the read path needs to be optimized for sequential access per user over a time range. The write path needs to be optimized for random access where one event may affect millions of users’ indexes.&lt;/p&gt;
&lt;p&gt;The solution required a system that could reorder writes from random to sequential and store in sequential format for read that could be grown to multiple hosts. Sorted string tables are a perfect fit for the persistence format, and add the promise of free partitioning and scaling in the mix, we chose &lt;a href=&quot;http://cassandra.apache.org/&quot;&gt;Cassandra&lt;/a&gt; as the storage system for the Dashboard index.&lt;/p&gt;
&lt;p&gt;The intermediary steps started with the model broadcast and used RabbitMQ as a queue for staged processing, in three major steps: fan-out, personalization, and serialization of foreign key references to our domain models.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fan-out finds the areas of the social graph where an activity should propagate.&lt;/li&gt;
&lt;li&gt;Personalization looks at the relationship between the originator and destination users as well as other signals to annotate or filter the index entry.&lt;/li&gt;
&lt;li&gt;Serialization persists the index entry in Cassandra for later lookup and joining against our domain models for display or API representations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/679c7f38401cb7471c9614369c03c6e6/54e54/e6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.51724137931035%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAABZklEQVQ4y6WU65KCMAyF+/6PyCguUOTSglzkzkzMiVZ3Z90d0R+h0w79kpwTUGVR0OHgU1mWNE0TDcMgMY7jW6HathWYO/gEJsAsTSnWmlJeHfDjCsMwpL7v77Dv0K1wBdDpdPql39vAuq4pSZIfh59UqsIgIM/zqGC30XrTNJRlKeV5TvO8bK5SLctCCLQMWNsCmFEcx1z5URI5OV4C5nzZGEPn81m07LqOYBSkuCZot7VsGQYgLgIAoAPAsM0t44FqUJ2OIgqDkPeNzKbv77ny7qlRf4WMDQD4BDHccBzVruvKpswvgx7AridrLbuakb/3KWDXkcDedAUUpmB1gb0zc0bczqVl0Yqh0M+YXMDDMNJ+tyOtY3Zbi+OQ4HhM+ExTxu9gxJAc+5jfi1iuiqVTD0GvWa8S1JRy6wBFES5+cSJzdx9rVVUSbhqwdnxXPdNBPkf+A+EvBDmsNWLOzK1O/wQKuwCEUdIifDjPqQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e6&quot;
        title=&quot;e6&quot;
        src=&quot;/blog/static/679c7f38401cb7471c9614369c03c6e6/54e54/e6.png&quot;
        srcset=&quot;/blog/static/679c7f38401cb7471c9614369c03c6e6/9ec3c/e6.png 200w,
/blog/static/679c7f38401cb7471c9614369c03c6e6/c7805/e6.png 400w,
/blog/static/679c7f38401cb7471c9614369c03c6e6/54e54/e6.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#search&quot;&gt;Search&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our search is conceptually a back-end service that exposes a subset of data store operations over an HTTP interface for queries. Updating of the index is handled similarly to the dashboard via ModelBroadcast with some enhancement from database replicas with index storage managed by &lt;a href=&quot;http://www.elasticsearch.org/&quot;&gt;Elastic Search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/db26cd209c79194cff637894ff4bbc53/54e54/e7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.06896551724138%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAABeklEQVQ4y6WU6Y6CQBCEef+3c+NmAV2PQe7hxgtjartmg8GV6Ko/OjBD+KaqusHKswzL5QJ5nuN4PGK/35s6HA4vldU0jYH1G+/ADDCOIvibDSK59sC3FSqlsNvtLrAh9Fm4RVBRFDf5vQysqgphGF5tvqPUUus1Zq6LTLpN61Vdg7kmSYKu60YPuHeYNaTXAmslU61T06Q0iWUCCnSnE05SPGBYY8ovQGbILMuyFFgIz/PgieJUa9krTCxUzUql4jgeV8im9B2uK1HYtgbK2UzTFMyYZUCyZjQs3o8CPU/B9315qQZHqLdDi+fz2Shn/bV71zKzq6pfq2EQoBZwJBaVWqPdbu825kbhVl7IdAYtFpjbarUylnvrz36SJkMq5E+C1tldwgjgYcx1+BU9ghsglRTSBM4hO5sXJVzHhuO4CPzA2GZm19AHCqmGxTWvHA0tI8N1I8//a/8CZDM+p1OZr8R0e/E9h/1lw7YdzOczfEwmckD28J/5A5jiHk4BTY9BAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e7&quot;
        title=&quot;e7&quot;
        src=&quot;/blog/static/db26cd209c79194cff637894ff4bbc53/54e54/e7.png&quot;
        srcset=&quot;/blog/static/db26cd209c79194cff637894ff4bbc53/9ec3c/e7.png 200w,
/blog/static/db26cd209c79194cff637894ff4bbc53/c7805/e7.png 400w,
/blog/static/db26cd209c79194cff637894ff4bbc53/54e54/e7.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#notifications-and-stats&quot;&gt;Notifications and Stats&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make sure users are properly notified when their dashboard updates, whether this is over iOS/Android push notifications, email or other social networks we simply added another stage in the Dashboard workflow that receives messages when a dashboard index is updated. Agents can get that completion event routed to their own AMQP queues via the message bus to initiate their own logic. Reliable messages at the completion of persistence is part of the eventual consistency we work with throughout our system.&lt;/p&gt;
&lt;p&gt;Our statistics offered to logged in users at &lt;a href=&quot;https://soundcloud.com/you/stats&quot;&gt;https://soundcloud.com/you/stats&lt;/a&gt; also integrates via the broker, but instead of using ModelBroadcast, we emit special domain events that are queued up in a log then &lt;a href=&quot;assets/posts/mysql-stats-ol&quot;&gt;rolled up&lt;/a&gt; into a separate database cluster for fast access across the various time ranges.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/f77332c890943a3818b91ff2bf690bab/54e54/e8.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 129.65517241379308%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsSAAALEgHS3X78AAACIklEQVRIx6VV23aCMBDM//9htXIJELwCAVTUc9zuLA2NFVpsH/YACUxmZ3YXVRwOFAYBFUVBl8uFzuezRNd1fwrVNI2AuYX/gAngZr2mLE1pzVcH+G+GWms6nU4DmA/6KrgCUFmW1H3T78+A1lrK8/xh0Qd7FVjpOKbFYkEHdhup27om6Lrdbul6vT4AzwFX/oO1NbWs6X6/F5N2DFoUpQDfbrcnsDFg5TZQg9CyqiphmLLzSaKZ6Y5qlgUHPEoxwdBPpeZ027Zl4EJYIm0cYvkQAB6Px6EaJhmCmQuwg0lINcsyapq2P4ALP00TClYriqL4x3pVuTG02WzlQwT0AjiuzhQX7uAfTamqkgE30n7O3UaM2fE60jxNOj1pCrQDsOE0YUbNgCgj1Cd0e6UlpVOgHUwwJmdQIwfc73cJvxZntx4AwSiKIhHf1g1FYUDv7ysunVQk6LrLfIYNA+BlpIcyAcMs6ycQ9hFjrTgWA0MYsnh7k9RlAsURBUHoMZwGeQJEuVg2BXWGVgNT9HjGeuIeL34vp/7+q4ad1j3DEhrupc3gtOHaFM34BexD35gZo9jDMJTAMzTXOuEsEloul1J6AojuwL8FmkE/BLR04GA5RPsZx8dAlsCSXoZGrnSwiWf8FtBFYP3KkFV+S/mtFgQrYYqxNceMwZSxxX42WrmOzcFfGT6NftbCmEw01ToWfeaCqql/CVyXmciDwx8QvwF+AIqp1K5IUhrZAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e8&quot;
        title=&quot;e8&quot;
        src=&quot;/blog/static/f77332c890943a3818b91ff2bf690bab/54e54/e8.png&quot;
        srcset=&quot;/blog/static/f77332c890943a3818b91ff2bf690bab/9ec3c/e8.png 200w,
/blog/static/f77332c890943a3818b91ff2bf690bab/c7805/e8.png 400w,
/blog/static/f77332c890943a3818b91ff2bf690bab/54e54/e8.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://gist.github.com/6f734306b06a68ae5c74#whats-next&quot;&gt;What’s next&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have established some clear integration points in the broker for asynchronous write paths and in the application for synchronous read and write paths to backend services.&lt;/p&gt;
&lt;p&gt;Over time, the application server’s codebase has collected both integration and functional responsibilities. As the product development settles, we have much more confidence now to decouple the function from the integration to be moved into backend services that can be consumed à la carte by not only the application but by other backend services, each with a private namespace in the persistence layer.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 580px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a493ebb0d86ca96b889cb2a345706cee/54e54/e9.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 136.20689655172413%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAACXBIWXMAAAsSAAALEgHS3X78AAACG0lEQVRIx6WV23KCQBBE+f/PMy+JSURRrsttuQkYazI9ZA0JVWGND1sqsmd7enrAybOMXHdHeZ7TMAx0Pp9l9X3/r+XUdS0wc+ERmADjKCL/dKKIPw3wYYWe51HXdTfYHHov3AGoKIqFf/8Gaq0pDMMfFx9R6niHA71ut5Rxt1G6riqCr0mS0DiOiwPW4M78R8Wwhj1NUyVNSpJY7AD4crlYKXXMycPQy+ayLAV24s4fWXGiFGk9XbNWaG6AwrpuqOBcpmkqZc8PsQLOvdGlFuhUcihR6vg/AH3ftwPCn2EcJDaAYSmVSMn+14qimAI/oKZp14EwXqmUEPCyKEUVvhs4YoVPqOxsFE5eKdkEvwDAhrZpBNz3k3osqy6Ldwwpy4LL8qVUwIMgoP3e5TKbxYz/qRAlopyMlWJi4BWA1+uVPnjNw209ywCmnDfXdel49CQ6OABRQUPatrWGChBxgV8YP2QPB7zxOLruXr7fDayrmqFT2QC3fE0xGAoVK58D13x0uraTpiiOz9NmQzGDAN2+PNP7Dq+GQqI08izb+OmY3EEh1KBkRAmbzENXy+iFpHiC1hXyJsliHMt4AYiF94zWlcSmqrTcA4+tgGYSMHLGJ0QIyk2oUa51yVAxTcJwu9k04/fT20qhAX6vnifFlzzaPlgXJVfoNKvCPMMvPGXgJXy7K4e4CcA8z6STMTfH5A9ZDNjL+TyvAT8Bn9Eji6XsHUAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;e9&quot;
        title=&quot;e9&quot;
        src=&quot;/blog/static/a493ebb0d86ca96b889cb2a345706cee/54e54/e9.png&quot;
        srcset=&quot;/blog/static/a493ebb0d86ca96b889cb2a345706cee/9ec3c/e9.png 200w,
/blog/static/a493ebb0d86ca96b889cb2a345706cee/c7805/e9.png 400w,
/blog/static/a493ebb0d86ca96b889cb2a345706cee/54e54/e9.png 580w&quot;
        sizes=&quot;(max-width: 580px) 100vw, 580px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The way we develop SoundCloud is to identify the points of scale then isolate and optimize the read and write paths individually, in anticipation of the next magnitude of growth.&lt;/p&gt;
&lt;p&gt;At the beginning of the product, our read and write scaling limitations were consumer eyeballs and developer hours. Today, we’re engineering for the realities of limited IO, network and CPU. We have the integration points set up in our architecture, all ready for &lt;a href=&quot;https://soundcloud.com/jobs&quot;&gt;the continued evolution of SoundCloud&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Shoot yourself in the foot with iptables and kmod auto-loading]]></title><description><![CDATA[As some of you might know, we had an outage yesterday. We believe that in every mistake there is something to learn from, so after each…]]></description><link>https://developers.soundcloud.com/blog/shoot-yourself-in-the-foot-with-iptables-and-kmod-auto-loading</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/shoot-yourself-in-the-foot-with-iptables-and-kmod-auto-loading</guid><pubDate>Tue, 14 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As some of you might know, we had an outage yesterday. We believe that in every mistake there is something to learn from, so after each outage we are writing post-mortems. Usually we do this internally because the issues we run into are very specific to our infrastructure.&lt;/p&gt;
&lt;p&gt;This time we ran into a quite nasty issue which could affect everyone running a linux system with a lot sessions on it and we thought you might be interested to know about that pitfall.&lt;/p&gt;
&lt;h2&gt;What happened?&lt;/h2&gt;
&lt;p&gt;At 4:40pm CEST, we got reports about &lt;code class=&quot;language-text&quot;&gt;Yikes&lt;/code&gt; (503/504 errors) on SoundCloud. Around the same time, our monitoring alerted for a high amount of 503s at our caching layer and right after that one of our L7 routing nginx instances was reported down.&lt;/p&gt;
&lt;p&gt;We were still able to log into that system. dmesg showed:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Aug 13 14:46:52 ams-mid006.int.s-cloud.net kernel: [8623919.136122] nf_conntrack: table full, dropping packet.
Aug 13 14:46:52 ams-mid006.int.s-cloud.net kernel: [8623919.136138] nf_conntrack: table full, dropping packet.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;N.B.: Our systems are set to UTC timezone&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That wasn’t expected. The first thought was: “Someone must have changed the sysctl tunings for that”. Then we realized that this system has no need for connection tracking, so &lt;code class=&quot;language-text&quot;&gt;nf_conntrack&lt;/code&gt; shouldn’t be loaded at all. As a quick contermeasure we raised &lt;code class=&quot;language-text&quot;&gt;net.ipv4.netfilter.ip_conntrack_max&lt;/code&gt;. This fixed that situation and brought the service back up.&lt;/p&gt;
&lt;h2&gt;Why did it happen?&lt;/h2&gt;
&lt;p&gt;After bringing the site back up, we investigated what caused the kernel to enable connection tracking. Doing a &lt;code class=&quot;language-text&quot;&gt;lsmod&lt;/code&gt; showed that connection tracking and iptables modules were actually loaded. Another look into dmesg revealed that right before the outage the &lt;code class=&quot;language-text&quot;&gt;ip_tables&lt;/code&gt; netfilter module was loaded:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Aug 13 14:38:27 ams-mid006.int.s-cloud.net kernel: [8623415.007818] ip_tables: (C) 2000-2006 Netfilter Core Team
Aug 13 14:38:35 ams-mid006.int.s-cloud.net kernel: [8623422.444931] nf_conntrack version 0.5.0 (16384 buckets, 65536 max)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So what happened? One of our enginners was doing some preparations for scaling that layer of our infrastructure. To verify we don’t use any specific iptable rules on that system, he did:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;iptables -L iptables -t nat -L&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Those commands themself are pretty harmless. They will just list configured iptables rules. The first one rules in the &lt;code class=&quot;language-text&quot;&gt;filter&lt;/code&gt; table, the second one in the &lt;code class=&quot;language-text&quot;&gt;nat&lt;/code&gt; table. Nothing which should change any system configuration, right? Nope. Let’s try to reproduce it. Just boot up some system (I’ve tried it on my Ubuntu Laptop). No iptables module should be loaded:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;root@apollon:~# lsmod|grep ipt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now just list your iptable rules:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And check again for loaded modules:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;root@apollon:~# lsmod|grep ipt
iptable_filter 12810 0
ip_tables      27473 1 iptable_filter
x_tables       29846 2 iptable_filter,ip_tables&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Okay, that loaded some iptables module to make it possible to add filter rules via &lt;code class=&quot;language-text&quot;&gt;iptables&lt;/code&gt;. This shouldn’t cause any problems, since without any actual rules the impact on the kernel is negligible. But now check your nat table:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;root@apollon:~# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Completely empty as well. But now look at your kernel modules:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; root@apollon:~# lsmod|grep ipt
 iptable_nat 13229 0
 nf_nat 25891 1 iptable_nat
 nf_conntrack_ipv4 19716 3 iptable_nat,nf_nat
 nf_conntrack 81926 3 iptable_nat,nf_nat,nf_conntrack_ipv4
 iptable_filter 12810 0
 ip_tables 27473 2 iptable_nat,iptable_filter
 x_tables 29846 3 iptable_nat,iptable_filter,ip_tables&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By just listing your iptable rules for the nat table, the kernel loaded &lt;code class=&quot;language-text&quot;&gt;nf_conntrack&lt;/code&gt; which enabled connection tracking. See dmesg:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;[75024.007681] nf_conntrack version 0.5.0 (16384 buckets, 65536 max&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;On your Laptop you probably don’t care – it’s even quite convenient. On a production server that handles a large number of connections the fairly small default &lt;code class=&quot;language-text&quot;&gt;nf_conntrack&lt;/code&gt; table will overflow quite fast and cause dropped connections.&lt;/p&gt;
&lt;h1&gt;How do we prevent it?&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;iptables&lt;/code&gt; doesn’t load the &lt;code class=&quot;language-text&quot;&gt;nf_conntrack&lt;/code&gt; itself, it only loads &lt;code class=&quot;language-text&quot;&gt;ip_tables&lt;/code&gt; which again loads modules it depends on via the kernel’s &lt;code class=&quot;language-text&quot;&gt;kmod&lt;/code&gt; facility.&lt;/p&gt;
&lt;p&gt;But since that module loader uses the modprobe user-space helpers like modprobe, the auto-loading process will honour &lt;code class=&quot;language-text&quot;&gt;modprobe.d/&lt;/code&gt; settings. Unfortunatelly there is no easy way to disable loading of a module altogether, but there is a workaround for that.&lt;/p&gt;
&lt;p&gt;Since we don’t need iptables at all on that system, we’ve created a /etc/modprobe.d/netfilter.conf like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; alias ip_tables off
 alias iptable off
 alias iptable_nat off
 alias iptable_filter off
 alias x_tables off
 alias nf_nat off
 alias nf_conntrack_ipv4 off
 alias nf_conntrack off&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will make &lt;code class=&quot;language-text&quot;&gt;modprobe&lt;/code&gt; load &lt;code class=&quot;language-text&quot;&gt;off&lt;/code&gt; instead of the actual kernel module.&lt;/p&gt;
&lt;p&gt;Trying to run any iptables command now, should now give you now:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;iptables -t nat -L
FATAL: Module off not found. iptables v1.4.12: can&amp;#39;t initialize iptables table `nat&amp;#39;: Table does not exist (do you need to insmod?) Perhaps iptables or your kernel needs to be upgraded.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Introducing the CloudSeeder Devkit]]></title><description><![CDATA[Today we’re featuring a guest post from our friends at
Retronyms. They’ve built some amazing
community features into their app
Tabletop
using the SoundCloud API and have open sourced their CloudSeeder
Devkit.  This post was written for us by David Shu. David is a
software engineer at the Retronyms and has worked on a number of iOS
apps, including Tabletop and Dokobots. He currently resides in San
Francisco, CA. We recently built a SoundCloud-powered community into our app
Tabletop,
a modular audio environment for the iPad, using the
CocoaSoundCloudAPI.
The project, CloudSeeder, lets users browse, stream, favorite, and
comment on Tabletop tracks without ever leaving the app. As developers, we discovered tons of talented users in our Tabletop
community. At the same time, our users found inspiration from each
other and a new showcase for their creations. To share in the
excitement of community creation with all developers, today we’re
releasing the CloudSeeder Devkit as open source on
Google Code.]]></description><link>https://developers.soundcloud.com/blog/introducing-the-cloudseeder-devkit</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-the-cloudseeder-devkit</guid><pubDate>Mon, 13 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Today we’re featuring a guest post from our friends at
&lt;a href=&quot;http://retronyms.com/&quot;&gt;Retronyms&lt;/a&gt;. They’ve built some amazing
community features into their app
&lt;a href=&quot;http://itunes.apple.com/us/app/tabletop/id436080882?mt=8&quot;&gt;Tabletop&lt;/a&gt;
using the SoundCloud API and have open sourced their CloudSeeder
Devkit.  This post was written for us by David Shu. David is a
software engineer at the Retronyms and has worked on a number of iOS
apps, including Tabletop and Dokobots. He currently resides in San
Francisco, CA.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We recently built a SoundCloud-powered community into our app
&lt;a href=&quot;http://itunes.apple.com/us/app/tabletop/id436080882?mt=8&quot;&gt;Tabletop&lt;/a&gt;,
a modular audio environment for the iPad, using the
&lt;a href=&quot;https://github.com/soundcloud/CocoaSoundCloudAPI&quot;&gt;CocoaSoundCloudAPI&lt;/a&gt;.
The project, CloudSeeder, lets users browse, stream, favorite, and
comment on Tabletop tracks without ever leaving the app.&lt;/p&gt;
&lt;p&gt;As developers, we discovered tons of talented users in our Tabletop
community. At the same time, our users found inspiration from each
other and a new showcase for their creations. To share in the
excitement of community creation with all developers, today we’re
releasing the CloudSeeder Devkit as open source on
&lt;a href=&quot;http://code.google.com/p/cloudseeder/&quot;&gt;Google Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 560px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/26291617b09e9b7665fb652c80924386/02744/cloudseeder.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.42857142857143%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABiElEQVQoz51RPU/CUBR1djBx9se4+B/0L+hgcHDQQWUycdBodDDRxInoosKCxEkiMRoDCqYSQkoMpWBpH699ny0g3kKMoi2DJ+e9vI977r3vvDEWBMFZryM/2rLrCWBbcsn537CxnxtKmSu4bjlzsfxSophSGvcVc+FCmY3lKwaGKwgIFUNyTKhlk0RBn4ymx9duJjbSU5uZ3XTFIXRUZdrvtuXQ+XNl8fJ1+ig3c5xbviqXDbvXlTzodb/b9iQvaOjkTl3P4rgmDSYRFYwGOjMsFn2Cvtd1KeepmnwwRIf7UvALUvM+AsRwhgir2X4sDMKYahHVJD1PwFuAbcGwbRPHx5AYCpqEXetiv+w+mxwaPat6B4+N+JOmI+c0q8WymlJtIssyTRNj/C2GUmBI3WYpXW6X3K2ie6h60Rexk3mLxIuRRHE1WVpJlvZuK3XkuELQwLZryAG+I1w1sdFy9CbSDKtu2apFgTr2/4KHGzZwlg4W3P88fwanwD/C+Ci3w8C/+B9xGD4B22nKjRJ46SYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CloudSeeder&quot;
        title=&quot;CloudSeeder&quot;
        src=&quot;/blog/static/26291617b09e9b7665fb652c80924386/02744/cloudseeder.png&quot;
        srcset=&quot;/blog/static/26291617b09e9b7665fb652c80924386/9ec3c/cloudseeder.png 200w,
/blog/static/26291617b09e9b7665fb652c80924386/c7805/cloudseeder.png 400w,
/blog/static/26291617b09e9b7665fb652c80924386/02744/cloudseeder.png 560w&quot;
        sizes=&quot;(max-width: 560px) 100vw, 560px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;What does CloudSeeder do?&lt;/h2&gt;
&lt;p&gt;Let’s run through a typical scenario using Tabletop as an example. When you
start up an app with CloudSeeder integrated, you’ll get an at-a-glance view of
the latest community happenings including app news, latest track posted, and
likes and comments on tracks you’ve uploaded. When someone makes a cool new track,
everyone who launches the app gets notified immediately in their feed.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 514px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/315aa8eedac07b15f8d8b8145399d2c0/a5a7c/cloudseeder_headline.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.90272373540856%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAAC5UlEQVQoz2WSz2sTQRTHZ38ku2l+zG6yP5JsNjHpbn42yWY3uzZtkzYxrWma2GJSBKEiKF7sRQ9S8FLb3lRUWqltERTEg+BNEW8i1YN3K+j/YRA9OCGkUhw+83jMvMf3zZsHKpVKu92enZ1ttVrNZvNsc2GuuYDs/EJrob1YnqlVJ2umZRqWURovjf9bfR+EQiFVVWVZjkQi0WgsJonRABcJ8BE/HwuKPta2Xr315sJr3sPZbKSDommKOgYoipLL5crlcrVaLWQzqbUXE9uf6w/e1++9RTa3/SXfuNRVGgGnCAAgAIYDcAxAspIkjY6OKooqS8HQ3Eps+Ubi/Gqicz3eWZU7N2PmlJ3sR5IYRpwEIM1ut4sevLS01Ol0KlbByqhWNjmglE2Mm4bH5caQ7P/J8XjcMIx8Pq9pmq7ryXRGiSfVRDKmqEMUB0Xhw+QTZXNeloEeLwsDfoHzeUmSQIc4hkGPG12gKwa6Rxz0UA2EZTmJVjyuKgpgxQgXjHFSTJAVZJ2sn3L7aMhDQWbEMAIKYYZhjpWTCdUs6oau6YU8MDa/mVsDjoqbR9rm99xGH33jCFG489XY+uFXzf6bcYLE8XQqYZl60dBMQwPTe72ZvV5tv1c/6J056Jk7vwo7v42d31O7vfKT3tTuz8r+n0B6EgyTM+mkZRnFYsE0dcBkm1xh0TeEL5zzZufZsQbMzMNMw5NpoAAhIA+6bSOIsXTS1DJ6LpUfSwGJh6eCPjXilzhIAYAYxIled4CHfs7j5yBqqh0HdgKjSSynaaWLV63llfbla8BO0XaKslH9De1OSLnpERftYRxu1GgvZH1uhhUF3kFgIzbcZcNzpjW5dn956/G7D4dAFEU03uFwOBqNsixL4DhJknanSwwEQyE08nIwKIk8qopAMHbMrEw37z678uj5x8NPAH2DIAg8z4uigHw07qgWyuWBXo5H54Lg43jJj6TJPjRxulafWN9v3H749OWrv1oOmlg+D9mqAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CloudSeeder Headline view&quot;
        title=&quot;CloudSeeder Headline view&quot;
        src=&quot;/blog/static/315aa8eedac07b15f8d8b8145399d2c0/a5a7c/cloudseeder_headline.png&quot;
        srcset=&quot;/blog/static/315aa8eedac07b15f8d8b8145399d2c0/9ec3c/cloudseeder_headline.png 200w,
/blog/static/315aa8eedac07b15f8d8b8145399d2c0/c7805/cloudseeder_headline.png 400w,
/blog/static/315aa8eedac07b15f8d8b8145399d2c0/a5a7c/cloudseeder_headline.png 514w&quot;
        sizes=&quot;(max-width: 514px) 100vw, 514px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Tabletop displaying the Headline view in the upper left hand corner. All the headline items are cycled through with a nice animation and you can size this view to suit your UI.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tapping on the Headline will bring you to the CloudSeeder
community. The Main view will slide up, showing you News, Featured
Tracks, Popular Tracks, Latest Tracks, My Uploads, My Activity, and
Following. CloudSeeder filters all the tracks for you (using the
&lt;code class=&quot;language-text&quot;&gt;&amp;quot;created_with&amp;quot;&lt;/code&gt; property, for those of you familiar with the SoundCloud
API), so all the tracks and activities you see will be specific to
your application.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 512px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/b8afee81a2778887287217fac298be28/c5a17/cloudseeder_main.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD2ElEQVQ4yz2UfUxVZRzHT7hBvL/LICJBIYcXGOF8CampyeUiFCrTPzDXhivXgpZQWBNnaKu1Gk56waWYuOZWrdVmEEVrbRpbZZQzc4HA5cLlwuW+v5x77tun5xzMZ+e3Z/udPd/n+/v9vt9H0unKKVmTR0FuJgU5qdRUb6WhoQF9bS3Nzc20t7fT2tpKW9tLtItQc3q9HoPBQF2dGnr0hkZajg6wvrwaKSu/hNzHnyW5rAlpTT2fff0jXo+LaaMJ84IFj9tNKBTizxu/MTo8gs/vR5ZlFEVBDigQVhie9NB5PUhh5RNIDz26heIDn1B68CIxO/v4/IebRIJ+XL4AshIkEomgLrfHw+T0LNblZfwC0OfzifCKC2SWHW4ccoTqmu1IaWkZrHskj7UFeUiSxLm+9/G6XSzZXThdHkJBARqNEpD9GM02bEuLuGzLzJlM/HPXyF+zNm5PTrNgsbBjx1NISSnpbNigo6hoLTGrVtH9Zi8zpiVcAtThdOL1ejWG18dv0dF/mb6Rn3m+7yIj18bAMUt4/g5R09/gt1Nbt1swTElBp9PxcH4eMQ9I9Jx6B6NpAbvbh93pweVZAbxwdZRNnac5/s0oLX2XOPvpIPLQB5gudTP74RHk369iaNyLlFmyjcLCImKTs7WSBy6cE71x4nTYCHjsAkrRAK8Mj5Kz/0Wu/HKDJ4+e5I3BIY79AS/8FEA/FOL8XdjXKBhmVTaRk5FK2/YsSvMT2dPRx/EhF698q3DwSz8tX/g4MgwdH39H+uZaDvWcIa5qFyfPf0WvqPT0mJvuax6+n4c9jQ1IcQnJVBYXMNKxDkNVOqVPv01Vl5GKHi+ZHU7i2hxIhx2cHV7i1p1/+XX8JmPjt0WfzYJ3VHxRopGwVsXuegGYkJRKRZmOqo0byc7K5KP+AUxmBxNGK2aLA6vVLQakEApHsZjNOGw27XBQaDMoFKBqNKCEtFxjQ+MKYG5hMZVbthGfvpr+3vcI+d1CgwIkFBQMwlpEBIuZu5NYhNhVUa9EUNt9geAKoL5WyCYxicM16znWVM1zW4sYfPctFk3zWIVY7U6XGJBfO6gKfGZqCuuSVbALauyiolx1yfcY7tMLHWakpfHyY8lcPrSJ1zencebUCWaMc8IZXs0dqg610sJh7ItGnE47gUBAyylaFaH7gPsNu5BiY+OJSUihfu8BMaEkujpfxTQ7h0dW8AeU+4dUQLfVIlzk1qz3f//CIq8EV4bSXCcYPhifqA2joryMnNXZnOh6jamJCZZsTuFbG4pgo9pP3WXh38C9h0EDUy8KhrR/6nqmdif/AS8teLQXeIFOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CloudSeeder Main view&quot;
        title=&quot;CloudSeeder Main view&quot;
        src=&quot;/blog/static/b8afee81a2778887287217fac298be28/c5a17/cloudseeder_main.png&quot;
        srcset=&quot;/blog/static/b8afee81a2778887287217fac298be28/9ec3c/cloudseeder_main.png 200w,
/blog/static/b8afee81a2778887287217fac298be28/c7805/cloudseeder_main.png 400w,
/blog/static/b8afee81a2778887287217fac298be28/c5a17/cloudseeder_main.png 512w&quot;
        sizes=&quot;(max-width: 512px) 100vw, 512px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;The CloudSeeder Main view showing the Featured Tracks tab.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Curating Featured Tracks can be done in the app. When you point
CloudSeeder to a SoundCloud user in the config file, all you have to
do is “like” a track as that user and it automatically shows up in the
Featured Tracks list! If you’re a user, nothing feels better than when
the app creator features your track!&lt;/p&gt;
&lt;h2&gt;Integrating CloudSeeder into your app&lt;/h2&gt;
&lt;p&gt;If you’re ready to poke around CloudSeeder, you can head to the
&lt;a href=&quot;http://code.google.com/p/cloudseeder/&quot;&gt;Google Code project&lt;/a&gt;. We had
fast integration and easy customization in mind when developing
CloudSeeder, so the repo contains source code and images for UI
(including Retina!) that you can just drop into Xcode. You should be
ready to go after adding
&lt;a href=&quot;https://github.com/soundcloud/CocoaSoundCloudAPI&quot;&gt;CocoaSoundCloudAPI&lt;/a&gt;,
filling out the config header file with a few SoundCloud details, and
adding the CloudSeeder resources to your project.&lt;/p&gt;
&lt;p&gt;We also added a couple new features to the upload process. If you
register for the upload notification, you can add tags, description,
and BPM to the track metadata. For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Register for the share notification&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSNotificationCenter defaultCenter&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    addObserver&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;
       selector&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;soundCloudShareWillShow&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
           name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;kCSShareViewControllerWillShowNotification
         object&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;soundCloudShareWillShow&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NSNotification &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;notification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    CSShareViewController &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;shareVC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CSShareViewController &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;notification object&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Add tags, description, bpm to track metadata&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;shareVC setTags&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSArray arrayWithObjects&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;TABLETOP&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;CloudSeeder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;bunnies&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;shareVC setDescription&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;Created with Tabletop for iPad&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;shareVC setBPM&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTempo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Be a part of CloudSeeder Select&lt;/h2&gt;
&lt;p&gt;CloudSeeder has one more trick up its sleeve to benefit developers:
cross promotion. Apps that are a part of CloudSeeder Select get
featured across all other CloudSeeder Select apps. When users check
out their Featured Tracks list, one track out of that list will be a
featured track from another fellow CloudSeeder app. Tapping on that
track will show the usual track view, along with a section promoting
that app.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 512px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/f714bfe686c46edd22b8be909f62f905/c5a17/cloudseeder_select.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADvUlEQVQ4y02TeWxUVRSHH1HRbtMWSqstFiwUNUxpao0LljY12ukyQwWnhqohJDXxL0oVAyJRCi7Fpv9ANKKGIppqpEI0xgYbqhhbJNFGRQqWLmCn6+wzb/Z58z7vexOqJzl5995373d/59xzJGPJBopX51N413IK8zLZ9NijNJgtmEwmmpqstLW10dLSQmtrq+5Wq5Xa2lrq6upobKhlc30NjRYLL+49zv2lG5Fy7n2Ygk07MJRYkIrNfNLzNXOTY8zanczOzbGwME8ikWBgYICT3d3IsozT6SQUCtH5q5/2IS+vD3o4ellhY0UV0krTHtY1H8O4/Ti31hzhzLk/UcMy/lCUcDRGPB5HMw1+aeSqDtKgwWCQ4WmZIVuIwes+/nYrVFZVI2Xmr2PtqnzWFOYjSRJHuzrwej3Y3X7cXr8OUBMKcjjKyLyMy2nHIRTabFP8MzrC1PQsM+NXcM9NUf34EwJoMLDeWELRmrUsEcB93d9x3gG/3PAw4VdxRVTCCkxPz3Dio5OcOd3Ht198xYXBi9iCMOoIcdURxhND5LYOKS0jC6NxPYV3r9QVdvb08YcXhm84mPLF8EUUwiLk7/vPY654kq5DnRzZv5cTX37Dgd+h5ZyPzX1+eibhKXMDkiFrGfcUFbE0Y7kALqGr/SC2sTGcHh8ul5uEkszhpaEfONRSx+kPOnjv5ef5vPcU+3+D3T96sPZ5+WwctloEMCU9k7xlWeysLuC+AgMH3z6Mw+FADoaIxRXxwooOHP65n9e2VdDZ2sSBbZWc6u2l8zJ0XPTzxgWZ/lnYYjEj3ZaSQVnxKs7uLqWmLI9dr+xjYnISt08mGkuq08zv93Fl/DqTo39x7do15ufnxaoqHiyhP5pmDfUCmJZuoLTESPmD5eTmruCd9jeZts3g9QeIiLJRVVUvHUUcdNsX8Hm9xBRVzFW9PrX/4WjyYotoCOn2O1IpKCqm7JEKUrJzOPb+xwTkIAFRJnElIUSIw4oiwo/jd8wRCshEIhEddNMjsaRCs6kGKTsrmxcqjby6pZLnylfz08BZEQjYnW5Rhz4dpinRVDpcLgKB4CJIW9dcy7VmT5tEHRrS02l9IJdPt1ex56E7+bDrXewOF26Pl3A4om/UFcZiYh7Wx7qp6uI3pkUizKoBU1LTuSXNQP3WZ5CWpvBW+2HRDR7RepHFm3UVAnjTVP4zXW0iCWxuFDlMFcDcnBxKN5SQuyKHl3btZGJiQoTsET0b0DdHo1GiIm9xAU16/H/jmPgX1YE7nm3mX4GMiUaAr9BeAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CloudSeeder Select&quot;
        title=&quot;CloudSeeder Select&quot;
        src=&quot;/blog/static/f714bfe686c46edd22b8be909f62f905/c5a17/cloudseeder_select.png&quot;
        srcset=&quot;/blog/static/f714bfe686c46edd22b8be909f62f905/9ec3c/cloudseeder_select.png 200w,
/blog/static/f714bfe686c46edd22b8be909f62f905/c7805/cloudseeder_select.png 400w,
/blog/static/f714bfe686c46edd22b8be909f62f905/c5a17/cloudseeder_select.png 512w&quot;
        sizes=&quot;(max-width: 512px) 100vw, 512px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;CloudSeeder Select: Cross promote your app to users in
other CloudSeeder apps! A banner on top of the Track view shows the
cross promoted app’s details.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A simple email with an icon image and some information on your app
will get you in and ready for cross promotion. Find out the details in
the &lt;a href=&quot;http://code.google.com/p/cloudseeder/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get CloudSeeder!&lt;/h2&gt;
&lt;p&gt;So if you’re the proud developer of an iPad audio app, get the devkit
from the &lt;a href=&quot;http://code.google.com/p/cloudseeder/&quot;&gt;Google Code project&lt;/a&gt;,
see CloudSeeder in action with
&lt;a href=&quot;http://itunes.apple.com/us/app/tabletop/id436080882?mt=8&quot;&gt;Tabletop&lt;/a&gt;,
and check out the
&lt;a href=&quot;https://soundcloud.com/groups/cloudseeder&quot;&gt;CloudSeeder SoundCloud group&lt;/a&gt;. Thanks!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Go at SoundCloud]]></title><description><![CDATA[SoundCloud is a polyglot company, and while we’ve always operated with Ruby on Rails at the top of our stack, we’ve got quite a wide variety…]]></description><link>https://developers.soundcloud.com/blog/go-at-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/go-at-soundcloud</guid><pubDate>Tue, 24 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud is a polyglot company, and while we’ve always operated with Ruby on Rails at the top of our stack, we’ve got quite a wide variety of languages represented in our backend. I’d like to describe a bit about how—and why—we use &lt;a href=&quot;http://www.golang.org&quot;&gt;Go&lt;/a&gt;, an open-source language that recently hit version 1.&lt;/p&gt;
&lt;p&gt;It’s in our company DNA that our engineers are generalists, rather than specialists. We hope that everyone will be at least conversant about every part of our infrastructure. Even more, we encourage engineers to change teams, and even form new ones, with as little friction as possible. An environment of shared code ownership is a perfect match for expressive, productive languages with low barriers to entry, and Go has proven to be exactly that.&lt;/p&gt;
&lt;p&gt;Go has been described by several engineers here as a WYSIWYG language. That is, the code does exactly what it says on the page. It’s difficult to overemphasize how helpful this property is toward the unambiguous understanding and maintenance of software. Go explicitly rejects “helper” idioms and features like the &lt;a href=&quot;https://groups.google.com/forum/?fromgroups#!msg/golang-nuts/oRTdZYmkpqg/e1HAcodU83QJ&quot;&gt;Uniform Access Principle&lt;/a&gt;, operator overloading, default parameters, and even exceptions, on the basis that they create more problems through ambiguity than they solve in expressivity. There’s no question that these decisions carry a cost of keystrokes—especially, as most new engineers on Go projects lament, during error handling—but the payoff is that those same new engineers can easily and immediately build a complete mental model of the application. I feel confident in saying that time from zero to productive commits is faster in Go than any other language we use; sometimes, dramatically so.&lt;/p&gt;
&lt;p&gt;Go’s strict formatting rules and its “only one way to do things” philosophy mean we don’t waste much time bikeshedding about style. Code reviews on a Go codebase tend to be more about the problem domain than the intricacies of the language, which everyone appreciates.&lt;/p&gt;
&lt;p&gt;Further, once an engineer has a working knowledge of &lt;a href=&quot;http://golang.org/doc/effective_go.html&quot;&gt;Effective Go&lt;/a&gt;, there seems to be very little friction in moving from “how the application behaves today” to “how the application should behave in the ideal case.” Should a slow response from this backend abort the entire request? Should we retry exactly once, and then serve partial results? This agent has been acting strangely: can we install a 250ms timeout? Every high-level scenario in the behavior of a system can be expressed in a straightforward and idiomatic implementation, without the need for libraries or frameworks. Removing layers of abstraction reduces complexity; plainly stated, simpler code is better code.&lt;/p&gt;
&lt;p&gt;Go has some other nice properties that we’ve taken advantage of. Static typing and fast compilation enable us to do near-realtime static analysis and unit testing during development. It also means that building, testing and rolling out Go applications through our deployment system is as fast as it gets.&lt;/p&gt;
&lt;p&gt;In fact, fast builds, fast tests, fast peer-reviews and fast deployment means that some ideas can go from the whiteboard to running in production in less than an hour. For example, the search infrastructure on Next is driven by &lt;a href=&quot;http://www.elasticsearch.org&quot;&gt;Elastic Search&lt;/a&gt;, but managed and interfaced with the rest of SoundCloud almost exclusively through Go services. During validation testing, we realized that we needed the ability to mark indexes as read-only in certain circumstances, and needed the indexing applications to detect and respect this new dimension of index-state. Adding the abstraction in the code, polling a new endpoint to reliably detect the state, changing the relevant indexing behaviors, and writing tests for them, all took half an afternoon. By the evening, the changes had been deployed and running under load for hours. That kind of velocity, especially in a statically-typed, natively-compiled language, is exhilarating.&lt;/p&gt;
&lt;p&gt;I mentioned our build and deployment system. It’s called Bazooka, and it’s designed to be a platform for managing the deployment of internal services. (We’ll be open-sourcing it pretty soon; stay tuned!) Scaling &lt;a href=&quot;http://www.12factor.net&quot;&gt;12-Factor apps&lt;/a&gt; over a heterogeneous network can be thought of as one large, complex state machine, full of opportunities for inconsistency and race conditions. Go was a natural choice for this kind of job. Idiomatic Go is safely concurrent by default; Bazooka developers can reason about the complexity of their problem without being distracted by the complexity of their tools. And Bazooka makes use of &lt;a href=&quot;https://github.com/ha/doozer&quot;&gt;Doozer&lt;/a&gt; to coordinate its shared state, which—in addition to being the only open-source implementation of &lt;a href=&quot;http://en.wikipedia.org/wiki/Paxos_(computer_science)&quot;&gt;Paxos&lt;/a&gt; in the wild (that we’re aware of)—is also written in Go.&lt;/p&gt;
&lt;p&gt;All together, SoundCloud maintains about half a dozen services and over a dozen repositories written entirely in Go. And we’re increasingly turning to Go when spinning up new backend projects.&lt;/p&gt;
&lt;p&gt;Interested in writing Go to solve real problems and build real products? &lt;a href=&quot;https://soundcloud.com/jobs&quot;&gt;We’d love to hear from you&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Recording and Sharing Phone Calls]]></title><description><![CDATA[A few weeks ago, I attended News Hack Day in San Francisco.
News Hack Days are events that bring together journalists, developers and designers for multi
day creative coding and brainstorming sessions. I really like the idea of hack days that bring together people from different backgrounds. After
chatting with a few journalists, it became obvious to me that recording interviews on the phone
is a real pain. I saw this as an opportunity to build a fun app that would make this easier for
people.]]></description><link>https://developers.soundcloud.com/blog/recording-and-sharing-phone-calls</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/recording-and-sharing-phone-calls</guid><pubDate>Thu, 12 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, I attended &lt;a href=&quot;http://newshackdaysf.tumblr.com/&quot;&gt;News Hack Day&lt;/a&gt; in San Francisco.
News Hack Days are events that bring together journalists, developers and designers for multi
day creative coding and brainstorming sessions.&lt;/p&gt;
&lt;p&gt;I really like the idea of hack days that bring together people from different backgrounds. After
chatting with a few journalists, it became obvious to me that recording interviews on the phone
is a real pain. I saw this as an opportunity to build a fun app that would make this easier for
people.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;I spent most of my time during the two hacking days building &lt;a href=&quot;http://phonerecord.us&quot;&gt;Phone Recorder&lt;/a&gt;.
It’s a simple &lt;a href=&quot;http://nodejs.org/&quot;&gt;node.js&lt;/a&gt; application that uses &lt;a href=&quot;http://www.twilio.com/&quot;&gt;Twilio&lt;/a&gt; and
SoundCloud to record conference calls and upload them to the user’s SoundCloud account as either public
or private sounds. You can give it a whirl &lt;a href=&quot;http://phonerecord.us&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://github.com/paulosman/phonerecord.us&quot;&gt;check out the source code
on GitHub&lt;/a&gt;. I demoed the application at the end of the
event and got some really good feedback.&lt;/p&gt;
&lt;p&gt;The idea is pretty simple. A user signs into the app using their SoundCloud account and creates a new
call. They’re given a number to call, and two sets of codes: one for the moderator and another for
participants. When they call the number, they’re prompted to enter a code. Once the call is finished
(the moderator hangs up), the audio recording of the call is downloaded from Twilio and uploaded to
the moderator’s SoundCloud account.&lt;/p&gt;
&lt;p&gt;One tricky bit was associating the moderator’s SoundCloud account (and access token) with a specific
call. I decided to use &lt;a href=&quot;http://redis.io&quot;&gt;Redis&lt;/a&gt; for this. You could use any server-side data store,
Redis is just nice and simple, which is great when you’re at a hack day rushing to get something done.&lt;/p&gt;
&lt;p&gt;Once that bit was solved, the code that uploads to SoundCloud was simple to write:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;exports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;postTrack&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sharing&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; stat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;statSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/octet-stream&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; req &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;https://api.soundcloud.com/me/tracks.json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    multipart&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;track[title]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;track[sharing]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sharing&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;track[asset_data]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;oauth_token&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;complete&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// download the audio recording from Twilio and post to SoundCloud&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downloadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordingUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;private &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;private&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;public&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; soundcloud&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postTrack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Posted: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Attending the Hack Day was a good opportunity to build something useful that
uses sound on the web in a new, interesting way.&lt;/p&gt;
&lt;p&gt;Built an awesome hack using our API? Let me know about it in the comments.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SocialFolders Releases SoundCloud Ruby Gem]]></title><description><![CDATA[There are many approaches to building libraries that wrap HTTP APIs. For many of our
officially supported SDKs we
chose to build light wrappers around HTTP client libraries with a few added features
to make it easier to work with the SoundCloud API. This approach has a few benefits.
It guarantees a certain consistency and is relatively easy to maintain. It’s also fairly
future proof. Changes in the HTTP API do not typically require updates to client libraries. Sometimes however, you might be looking for something a bit more feature-full, or with
more abstraction from our HTTP API. That’s why I was really happy to see that the great folks at
SocialFolders built an alternative SoundCloud Ruby gem and
released it to the public. You can check out their blog post
about it or go straight to the source on GitHub.]]></description><link>https://developers.soundcloud.com/blog/socialfolders-releases-soundcloud-gem</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/socialfolders-releases-soundcloud-gem</guid><pubDate>Fri, 06 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There are many approaches to building libraries that wrap HTTP APIs. For many of our
&lt;a href=&quot;https://developers.soundcloud.com/docs/api/sdks&quot;&gt;officially supported SDKs&lt;/a&gt; we
chose to build light wrappers around HTTP client libraries with a few added features
to make it easier to work with the SoundCloud API. This approach has a few benefits.
It guarantees a certain consistency and is relatively easy to maintain. It’s also fairly
future proof. Changes in the HTTP API do not typically require updates to client libraries.&lt;/p&gt;
&lt;p&gt;Sometimes however, you might be looking for something a bit more feature-full, or with
more abstraction from our HTTP API. That’s why I was really happy to see that the great folks at
&lt;a href=&quot;http://socialfolders.me/&quot;&gt;SocialFolders&lt;/a&gt; built an alternative SoundCloud Ruby gem and
released it to the public. You can check out their &lt;a href=&quot;http://socialfolders.me/2012/06/new-soundcloud-gem/&quot;&gt;blog post&lt;/a&gt;
about it or go straight to the source on &lt;a href=&quot;https://github.com/SocialFolders/soundcloud-client&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;You can install the library from source or by running:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gem &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; soundcloud-client&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The library is based on the excellent &lt;a href=&quot;https://github.com/technoweenie/faraday&quot;&gt;Faraday&lt;/a&gt; HTTP
client library and can therefore be used really easily with libraries like &lt;a href=&quot;http://rubyeventmachine.com/&quot;&gt;EventMachine&lt;/a&gt;
or &lt;a href=&quot;https://github.com/typhoeus/typhoeus&quot;&gt;Typhoeus&lt;/a&gt;. It also provides wrapper methods for all
SoundCloud API endpoints. This means that instead of calling a method named for an HTTP verb
and providing an endpoint path, you can write code that looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;soundcloud-client&apos;&lt;/span&gt;

client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Soundcloud&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;API&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ACCESS_TOKEN&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
tracks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tracks&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:order&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hotness&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:limit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
tracks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;track&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  puts &lt;span class=&quot;token string&quot;&gt;&quot;Track: &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter tag&quot;&gt;#{&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token delimiter tag&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s great to see tools like this coming out of our developer
community. Huge shout out to &lt;a href=&quot;https://github.com/rewritten&quot;&gt;Saverio Trioni&lt;/a&gt;
and the rest of the &lt;a href=&quot;http://socialfolders.me&quot;&gt;SocialFolders&lt;/a&gt; team.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Music Hack Day Barcelona 2012]]></title><description><![CDATA[SoundCloud loves hack days.   Our latest hack day adventure brought us to Music Hack Day in Barcelona and we thought we’d share a bit of the great experience we had there. 
Photo by Thomas Bonte]]></description><link>https://developers.soundcloud.com/blog/music-hack-day-barcelona-2012</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/music-hack-day-barcelona-2012</guid><pubDate>Fri, 29 Jun 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud &lt;a href=&quot;https://developers.soundcloud.com/blog/2012-01-03-music-hack-day-london-2011&quot;&gt;loves&lt;/a&gt; &lt;a href=&quot;https://blog.soundcloud.com/2011/06/03/music-hack-day-berlin-2011/&quot;&gt;hack&lt;/a&gt; &lt;a href=&quot;https://developers.soundcloud.com/blog/soundcloud-at-music-hack-day-sf&quot;&gt;days&lt;/a&gt;.   Our latest hack day adventure brought us to Music Hack Day in Barcelona and we thought we’d share a bit of the great experience we had there.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0d96b8d3f9cbe32c84eadd64c8812429/48a11/venue.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABhdnVFQIMv//EABoQAQACAwEAAAAAAAAAAAAAAAIBAwAREiL/2gAIAQEAAQUCsRUWsOOxnW4sjwAGf//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/AUf/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARL/2gAIAQIBAT8Baj//xAAbEAADAAIDAAAAAAAAAAAAAAAAAhEBEBITof/aAAgBAQAGPwLGFF60k1BS8fT/xAAcEAADAAIDAQAAAAAAAAAAAAAAAREhYUFRcZH/2gAIAQEAAT8haLm+GYa1vZXn6UwIelQWXZPQ/9oADAMBAAIAAwAAABCH3//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPxBqv//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxCTcb//xAAbEAEAAgMBAQAAAAAAAAAAAAABACERMUFhgf/aAAgBAQABPxBcDgXhOk3y6DoUns3Ej0wEhSwIOjkUEMsK+rEsKkTD7c//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Music Hack Day Barcelona Venue&quot;
        title=&quot;Music Hack Day Barcelona Venue&quot;
        src=&quot;/blog/static/0d96b8d3f9cbe32c84eadd64c8812429/48a11/venue.jpg&quot;
        srcset=&quot;/blog/static/0d96b8d3f9cbe32c84eadd64c8812429/f544b/venue.jpg 200w,
/blog/static/0d96b8d3f9cbe32c84eadd64c8812429/41689/venue.jpg 400w,
/blog/static/0d96b8d3f9cbe32c84eadd64c8812429/48a11/venue.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Photo by &lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/7380645010/in/pool-1495069@N24/&quot;&gt;Thomas Bonte&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;a href=&quot;http://bcn.musichackday.org/2012/&quot;&gt;MHD Barcelona&lt;/a&gt; was organized by the &lt;a href=&quot;http://bcn.musichackday.org/2012/&quot;&gt;Music Technology Group of Universitat Pompeu Fabra&lt;/a&gt;. The first event on the program was a pre-meeting where all hack day attendees were invited to the university to enter the research labs and see what is under development.  Several great projects were demoed including the &lt;a href=&quot;http://www.reactable.com/products/&quot;&gt;Reactable&lt;/a&gt; (pictured below), and a melody extraction algorithm that when ran extracted a main melody out of a song with several instruments.  Fed back into a vocalizer the computer generated singing was eerily, impressively human.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0d4a8f3fce7fd6236daa5c36a39f4d7e/48a11/reactable.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIDBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAADBP/aAAwDAQACEAMQAAABiuuzBxBQKP/EABoQAAMBAAMAAAAAAAAAAAAAAAECAwAEERL/2gAIAQEAAQUCQetSOYAHiANNhqL2/wD/xAAYEQEBAAMAAAAAAAAAAAAAAAABAAMREv/aAAgBAwEBPwFyPWob/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAIRAf/aAAgBAgEBPwFVWDZNh//EABkQAAIDAQAAAAAAAAAAAAAAAAERABAhUf/aAAgBAQAGPwJtcFKMjaM//8QAGxABAAICAwAAAAAAAAAAAAAAAQARITFBYaH/2gAIAQEAAT8hEi9RgbLtmQkTYygCnMwp1Nyn/9oADAMBAAIAAwAAABBH7//EABYRAQEBAAAAAAAAAAAAAAAAAAEAIf/aAAgBAwEBPxDMSILf/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQAx/9oACAECAQE/ECYhUOX/xAAbEAEBAAIDAQAAAAAAAAAAAAABEQAhMVFh0f/aAAgBAQABPxCSBLSpJt9zTEFFBbXHkUBMj55i/Zgxz8wkSmzlJpGcdZ//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Reactable&quot;
        title=&quot;The Reactable&quot;
        src=&quot;/blog/static/0d4a8f3fce7fd6236daa5c36a39f4d7e/48a11/reactable.jpg&quot;
        srcset=&quot;/blog/static/0d4a8f3fce7fd6236daa5c36a39f4d7e/f544b/reactable.jpg 200w,
/blog/static/0d4a8f3fce7fd6236daa5c36a39f4d7e/41689/reactable.jpg 400w,
/blog/static/0d4a8f3fce7fd6236daa5c36a39f4d7e/48a11/reactable.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Photo by &lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/7374274310/in/pool-1495069@N24/&quot;&gt;Thomas Bonte&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The MTG had arranged that Music Hack Day would happen within the &lt;a href=&quot;http://www.sonar.es/es/2012/&quot;&gt;Sonar music festival&lt;/a&gt; and so the hacking venue was surrounded by music-goers of all kinds and the thumping bass of some of the world’s best new artists.  Working on your hack while &lt;a href=&quot;https://soundcloud.com/flyinglotus&quot;&gt;Flying Lotus&lt;/a&gt; or &lt;a href=&quot;https://soundcloud.com/jacquesgreene&quot;&gt;Jacques Greene&lt;/a&gt; are playing several steps away proved to be one of the most challenging parts of the event but when the clock ran down to zero and all hacks had to be in 41 projects had made it to the wiki and were “ready” for demoing.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/853e72964762d6586a7b3020769ba5f8/48a11/end.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAEDBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABqtyzeMAx/8QAGxAAAgMAAwAAAAAAAAAAAAAAAQIAAxEEEjH/2gAIAQEAAQUCSnQ/HwaZW79XZ4ff/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8Bh//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/Aaf/xAAbEAACAgMBAAAAAAAAAAAAAAABEQAQISIxgf/aAAgBAQAGPwLpmC/KaCh1Ff/EABsQAAMAAgMAAAAAAAAAAAAAAAABESExQXGB/9oACAEBAAE/IeOojNeoO2yMtzgnDw7yb+z/2gAMAwEAAgADAAAAEAM//8QAFxEAAwEAAAAAAAAAAAAAAAAAARARMf/aAAgBAwEBPxCzi//EABcRAAMBAAAAAAAAAAAAAAAAAAEQESH/2gAIAQIBAT8QyYv/xAAbEAEBAAMBAQEAAAAAAAAAAAABEQAhMUGR8f/aAAgBAQABPxAjHxojL3TjcyeTfo4FFE1iNVmq9+4dD3otT9y1YFTDzP/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Final Countdown&quot;
        title=&quot;The Final Countdown&quot;
        src=&quot;/blog/static/853e72964762d6586a7b3020769ba5f8/48a11/end.jpg&quot;
        srcset=&quot;/blog/static/853e72964762d6586a7b3020769ba5f8/f544b/end.jpg 200w,
/blog/static/853e72964762d6586a7b3020769ba5f8/41689/end.jpg 400w,
/blog/static/853e72964762d6586a7b3020769ba5f8/48a11/end.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Photo by &lt;a href=&quot;http://www.flickr.com/photos/81499407@N04/7465448984/in/pool-1495069@N24/&quot;&gt;Music Technology Group&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The quality of projects completed in such a short time span was really impressive -  a sentiment repeated in other articles like &lt;a href=&quot;http://thenextweb.com/events/2012/06/17/music-hackday-at-sonar-provides-the-link-between-technology-and-top-tracks/&quot;&gt;The Next Web’s review of the event&lt;/a&gt;.  Our favorites:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Jamming_Invasion&quot;&gt;Jamming Invasion&lt;/a&gt; - Winners of the SoundCloud prize of 400 Euros towards travel to the next Music Hack Day this hack was a Space Invaders style of game that used SoundCloud tracks and frequency analysis to generate the enemies.  The demo was great and you could really see the effect the levels of the music had on the gameplay.&lt;/p&gt;
&lt;p&gt;[Free(CC)it!](&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Free(CC%5C)it!&quot;&gt;http://wiki.musichackday.org/index.php?title=Free(CC\)it!&lt;/a&gt;) - This was an incredibly clever hack that took a normal music track, analysed it and then built another replacement track out of the closest matching samples from the FreeSound archive.  The idea and analysis behind this was really inspiring.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Legalize_It!&quot;&gt;Legalize It!&lt;/a&gt; - This hack was an app inside of Spotify that used the Music Metric API to gather the latest quite literally “hot” tracks from torrent sites and then mapped them to available tracks on Spotify so that you can listen to the latest and greatest legally&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=AriHits&quot;&gt;AriHits&lt;/a&gt; - A tilt-controlled iPhone beat machine on that mashed up an instrumental track and a vocal track from SoundCloud.  An inspired performance that had heads nodding to the beat.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Tunemap&quot;&gt;Tunemap&lt;/a&gt; - A very impressive music visualisation that mapped Echnoest and Deezer music info.  Incredibly polished for the short time frame the hackers had to work on it.&lt;/p&gt;
&lt;p&gt;So to wrap up this post we would like to again thank the amazing organizers of this event.  We are crossing our fingers that they decide to organize the same again next year, and if so, we will see you all back in beautiful Barcelona.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Waveforms, Let's Talk About Them]]></title><description><![CDATA[Waveforms I’ve worked at SoundCloud for over two years now, and if there’s one thing I do a lot, it’s color waveforms. Tons of them. And, I’ve done it several different ways. Today, Johannes and I are pumped to announce a new JavaScript library called Waveform.js that will assist you in your coloring efforts. But first, let’s take some time to look back and learn from past techniques.]]></description><link>https://developers.soundcloud.com/blog/waveforms-let-s-talk-about-them</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/waveforms-let-s-talk-about-them</guid><pubDate>Tue, 26 Jun 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24131769094098_raw.png&quot; alt=&quot;Waveforms&quot;&gt;&lt;/p&gt;
&lt;p&gt;I’ve worked at &lt;a href=&quot;https://soundcloud.com&quot;&gt;SoundCloud&lt;/a&gt; for over two years now, and if there’s one thing I do a lot, it’s color &lt;strong&gt;waveforms&lt;/strong&gt;. Tons of them. And, I’ve done it &lt;em&gt;several&lt;/em&gt; different ways. Today, &lt;a href=&quot;https://twitter.com/johanneswagener&quot;&gt;Johannes&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/leemartin&quot;&gt;I&lt;/a&gt; are &lt;strong&gt;pumped&lt;/strong&gt; to announce a new JavaScript library called &lt;a href=&quot;http://www.waveformjs.org&quot;&gt;Waveform.js&lt;/a&gt; that will assist you in your coloring efforts. But first, let’s take some time to look back and learn from past techniques.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Element Stacking&lt;/h2&gt;
&lt;p&gt;If you’ve used our &lt;a href=&quot;https://dev.soundcloud.com&quot;&gt;API&lt;/a&gt; before, you’ll know that each track resource contains a &lt;code class=&quot;language-text&quot;&gt;waveform_url&lt;/code&gt; that points to a 1800 x 280 pixel &lt;a href=&quot;https://w1.sndcdn.com/cWHNerOLlkUq_m.png&quot;&gt;PNG&lt;/a&gt; of that particular sound. The classic way of coloring this image was to &lt;em&gt;not color it at all&lt;/em&gt;. Instead, you would simply &lt;em&gt;stack&lt;/em&gt; it on top of two or three absolutely positioned DIVs whose size was adjusted given the &lt;strong&gt;loading&lt;/strong&gt; or &lt;strong&gt;playing&lt;/strong&gt; state of that sound.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24131748089754_raw.png&quot; alt=&quot;Stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, what’s wrong with this technique? Well, you can’t adjust the &lt;strong&gt;outer color&lt;/strong&gt; of the waveform image. So unless the area &lt;em&gt;around&lt;/em&gt; the waveform is &lt;strong&gt;#E5E5E5&lt;/strong&gt;, things are going to look subpar.&lt;/p&gt;
&lt;h2&gt;Webkit Mask&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I saw the future, and it was Webkit&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Webkit browsers can overcome the outer color issue with a new css function called &lt;a href=&quot;https://developer.mozilla.org/en/CSS/-webkit-mask-box-image&quot;&gt;webkit-mask-box-image&lt;/a&gt;. This function colors a div to the shape of a provided mask. In this case, our &lt;strong&gt;waveform image&lt;/strong&gt;. Check out the example below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.waveform&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;-webkit-mask-box-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;https://w1.sndcdn.com/cWHNerOLlkUq_m.png&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #81D8D0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 500px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&amp;lt;div class=&lt;span class=&quot;token string&quot;&gt;&quot;waveform&quot;&lt;/span&gt;&amp;lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24131758138488_raw.png&quot; alt=&quot;Webkit Mask Box Image&quot;&gt;&lt;/p&gt;
&lt;p&gt;It’s simple to code and damn powerful. You can combine this function with the &lt;strong&gt;element stacking&lt;/strong&gt; technique above to generate nice waveforms in most environments. But what about &lt;em&gt;non-webkit&lt;/em&gt; browsers? &lt;strong&gt;HTML 5&lt;/strong&gt; to the rescue!&lt;/p&gt;
&lt;h2&gt;Canvas&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Canvas_element&quot;&gt;Canvas&lt;/a&gt; adoption is rapidly increasing and for good reason - it fucking rules. With &lt;code class=&quot;language-text&quot;&gt;canvas&lt;/code&gt; we can dynamically manipulate an area of pixels with basic JavaScript. By placing the &lt;strong&gt;waveform image&lt;/strong&gt; mask onto the canvas and looping through each pixel, we can adjust the &lt;strong&gt;outer color&lt;/strong&gt; accordingly by simply asking &lt;strong&gt;“Is this pixel non-transparent?”&lt;/strong&gt; and changing it to a specified color.&lt;/p&gt;
&lt;p&gt;However, there is one &lt;strong&gt;caveat&lt;/strong&gt;: the &lt;a href=&quot;http://en.wikipedia.org/wiki/Same_origin_policy&quot;&gt;Cross-Domain Origin Policy&lt;/a&gt;. If you’ve developed with &lt;code class=&quot;language-text&quot;&gt;canvas&lt;/code&gt; before, you know that accessing and manipulating images from different domains or origins is prohibited. [1] But, we can get around this by accessing the image via the server, and I wrote a service that does exactly that.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wave64.it&quot;&gt;Wave64&lt;/a&gt; exists as an endpoint you pass a SoundCloud &lt;code class=&quot;language-text&quot;&gt;waveform_url&lt;/code&gt; to from the client. It will then read that image on the server and return a &lt;strong&gt;Base64&lt;/strong&gt; encoded text blob [2] which can be manipulated by &lt;code class=&quot;language-text&quot;&gt;canvas&lt;/code&gt;. I combined this endpoint with some jQuery and wrote the &lt;a href=&quot;http://wave64.it/#plugin&quot;&gt;$.wave64&lt;/a&gt; plugin which takes a waveform url, height, width, and color and returns the colored waveform.&lt;/p&gt;
&lt;p&gt;Problem solved? &lt;strong&gt;Never.&lt;/strong&gt; Programming is a math problem you can only make harder on yourself, and as such Johannes and I sought out to simplify this even more.&lt;/p&gt;
&lt;h2&gt;Waveform.js&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Damn Good Waveforms - Eric Wahlforss, CTO of SoundCloud&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/johanneswagener&quot;&gt;Johannes&lt;/a&gt; had the bright idea to analyze the waveform image’s &lt;strong&gt;peaks&lt;/strong&gt; and store their values in an array of floating points. &lt;strong&gt;HUH..?&lt;/strong&gt; Take a look at the image below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24131771379756_raw.png&quot; alt=&quot;Peaks&quot;&gt;&lt;/p&gt;
&lt;p&gt;If every column was considered a &lt;strong&gt;point&lt;/strong&gt;, it’s value would be a floating decimal from 0 to 1 depending on it’s height. So, we wrote a basic server-side script using &lt;a href=&quot;http://rmagick.rubyforge.org/&quot;&gt;RMagick&lt;/a&gt; to analyze the columns, convert them into 1800 floating points, and cache the result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don’t believe me?&lt;/strong&gt; Here’s &lt;a href=&quot;http://www.waveformjs.org/w?url=https://w1.sndcdn.com/cWHNerOLlkUq_m.png&amp;callback=?&quot; target=&quot;_blank&quot;&gt;Forss - Flickermood&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition, Johannes also wrote the &lt;strong&gt;new&lt;/strong&gt;  &lt;a href=&quot;http://www.waveformjs.org&quot;&gt;Waveform.js&lt;/a&gt; JavaScript library to convert this data into a &lt;code class=&quot;language-text&quot;&gt;canvas&lt;/code&gt; generated (and interpolated) waveform. Simply provide the &lt;strong&gt;container&lt;/strong&gt; where it will live, an &lt;strong&gt;innerColor&lt;/strong&gt; &amp;#x26;&amp;#x26;|| &lt;strong&gt;outerColor&lt;/strong&gt;, and the provided &lt;strong&gt;waveform data&lt;/strong&gt; array. Like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Waveform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  container&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;waveform&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  innerColor&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#81D8D0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WAVEFORM_DATA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24132127653036_raw.png&quot; alt=&quot;Waveform.js&quot;&gt;&lt;/p&gt;
&lt;p&gt;Psyched? Yeh, me too. And, not only can you pass &lt;em&gt;colors&lt;/em&gt; to the &lt;strong&gt;inner&lt;/strong&gt; and &lt;strong&gt;outer&lt;/strong&gt; color, you can also provide &lt;strong&gt;functions&lt;/strong&gt;. Check out a few &lt;a href=&quot;http://www.waveformjs.org/#examples&quot;&gt;examples&lt;/a&gt; of this technique on &lt;a href=&quot;http://www.waveformjs.org&quot;&gt;waveformjs.org&lt;/a&gt;, including the ability to render both the &lt;strong&gt;loading&lt;/strong&gt; and &lt;strong&gt;playing&lt;/strong&gt; progress right into a single Waveform.&lt;/p&gt;
&lt;p&gt;Waveform.js is open-sourced &lt;a href=&quot;https://github.com/soundcloud/waveform.js&quot;&gt;on Github&lt;/a&gt; so go ahead and push, pull, and &lt;strong&gt;fork&lt;/strong&gt; the hell out of it. You can report bugs and discuss features on the &lt;a href=&quot;https://github.com/soundcloud/waveform.js/issues&quot;&gt;Github issues page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The End?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Probably not.&lt;/strong&gt; It would rock if the SoundCloud API provided the waveform array data right from the API so we wouldn’t even need the endpoint. I’ll keep complaining internally until this happens.&lt;/p&gt;
&lt;h2&gt;One More Thing&lt;/h2&gt;
&lt;p&gt;Johannes and I would like to thank our fellow co-workers &lt;a href=&quot;https://twitter.com/tsenart&quot;&gt;Tomas&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/ceterum_censeo&quot;&gt;Robb&lt;/a&gt; for their wisdom when building &lt;a href=&quot;http://www.waveformjs.org&quot;&gt;Waveform.js&lt;/a&gt;. Make sure to throw some virtual &lt;strong&gt;high-fives&lt;/strong&gt; their way.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;[1] However, there are a few &lt;a href=&quot;http://en.wikipedia.org/wiki/Same_origin_policy#Workarounds&quot;&gt;workarounds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] Thanks to &lt;a href=&quot;https://twitter.com/maxnovakovic&quot;&gt;Max Novakovic&lt;/a&gt; for the technique.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[OceaniaSmashing Pumpkins use Premiere 1.5 BETA to debut "Oceania"]]></title><description><![CDATA[Premiere 1.5 Smashing Pumpkins are using a BETA of the next version of Premiere to debut their new record “Oceania.” Here’s what’s new about the app.]]></description><link>https://developers.soundcloud.com/blog/smashing-pumpkins-use-premiere-1-5-beta-to-debut-oceania</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/smashing-pumpkins-use-premiere-1-5-beta-to-debut-oceania</guid><pubDate>Mon, 18 Jun 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;http://img.svbtle.com/inline_leemartin_24119721339192_raw.jpg&quot; alt=&quot;Premiere 1.5&quot;&gt;&lt;/p&gt;
&lt;p&gt;Smashing Pumpkins are using a BETA of the next version of &lt;a href=&quot;http://bit.ly/sc-premiere&quot;&gt;Premiere&lt;/a&gt; to debut their new record “Oceania.” Here’s what’s new about the app.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;strong&gt;Moving Stacks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I converted the app from a basic Sinatra app over to my &lt;a href=&quot;http://leemart.in/hipster-stack&quot;&gt;Hipster Stack&lt;/a&gt; of Node.js, Jade, and Stylus for rapid development. I chose to use &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; similarly to how I utilized it in &lt;a href=&quot;https://stratus.sc&quot;&gt;Stratus 2&lt;/a&gt; to define a basic player structure. It’s really nice to be able to define a single model for &lt;code class=&quot;language-text&quot;&gt;tracks&lt;/code&gt; and use it for multiple views. In this case, the model is used to create a track list item and each waveform sound element.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meta Data&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In an effort to make deploying the app even quicker, I’m pulling as much &lt;strong&gt;data&lt;/strong&gt; from your SoundCloud account, album, and track as possible. Simply adjust the single setup line and everything will populate automatically.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;purchase&lt;/strong&gt; link is pulled from the set. You can even give it a custom title now from SoundCloud.com.&lt;/li&gt;
&lt;li&gt;Your &lt;strong&gt;Official Site&lt;/strong&gt;, &lt;strong&gt;Twitter&lt;/strong&gt;, and &lt;strong&gt;Facebook&lt;/strong&gt; links are pulled directly from your account profile.&lt;/li&gt;
&lt;li&gt;And, of course, the &lt;strong&gt;album&lt;/strong&gt; and &lt;strong&gt;track&lt;/strong&gt; data is used to generate all the tracks and artwork.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I’m trying to say is be sure to fill out those SoundCloud profiles &lt;strong&gt;completely&lt;/strong&gt;, and this app will make your life easier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Waveform&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was going to write an entire section on waveform coloring (exciting, I know) here, but I’ve decided to wait until next week because &lt;a href=&quot;https://twitter.com/johanneswagener&quot;&gt;Johannes&lt;/a&gt; and I have written a &lt;strong&gt;new&lt;/strong&gt; library called &lt;a href=&quot;http://waveformjs.org&quot;&gt;Waveform.js&lt;/a&gt; to handle this. More on that soon. &lt;strong&gt;;-)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve used it in Premiere 1.5 to construct the &lt;strong&gt;sound navigator&lt;/strong&gt; which is heavily inspired by our team’s work on &lt;a href=&quot;next.soundcloud.com&quot;&gt;SoundCloud Next&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permalinks with Pushstate&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Each album track is given its own &lt;strong&gt;permalink&lt;/strong&gt; mirrored from the its SoundCloud permalink url. This permalink also drives the URL system of Premiere 1.5 which will change automatically via &lt;code class=&quot;language-text&quot;&gt;history.pushState&lt;/code&gt;. This allows for deep linking and individual tracks to be shared.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s next?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m going to build more of the JavaScript &lt;a href=&quot;https://developers.soundcloud.com/docs/api/sdks#javascript&quot;&gt;SDK’s&lt;/a&gt; authenticated functions like commenting, liking, and following right into the app. Once that’s done, I’ll document the app and make it available via &lt;strong&gt;open-source&lt;/strong&gt; on Github.&lt;/p&gt;
&lt;p&gt;Premiere 1.5 represents a stepping stone to &lt;strong&gt;2.0&lt;/strong&gt; which will see the app being &lt;em&gt;hosted&lt;/em&gt; for the first time. This will make it as easy to deploy an album premiere as it is to launch an &lt;strong&gt;email for download&lt;/strong&gt; campaign with &lt;a href=&quot;http://emailunlock.com&quot;&gt;Email Unlock&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building The Next SoundCloud]]></title><description><![CDATA[This article is also available in: Serbo-Croatian: Pravljenje novog SoundCloud Armenian: SoundCloud (ձայնամպ) ծրագրավորողների համար The…]]></description><link>https://developers.soundcloud.com/blog/building-the-next-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-the-next-soundcloud</guid><pubDate>Thu, 14 Jun 2012 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This article is also available in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serbo-Croatian: &lt;a href=&quot;http://science.webhostinggeeks.com/pravljenje-novog-soundcloud&quot;&gt;Pravljenje novog SoundCloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Armenian: &lt;a href=&quot;http://students.studybay.com/?p=318&quot;&gt;SoundCloud (ձայնամպ) ծրագրավորողների համար&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 113px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4c740be23bd4eeba13011264809c5994/ae69b/playing.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAEH0lEQVQ4y02U/U9WZRjHb3hQQUh4eDGwWrUVbJZZoMg7Qj0FITaBeJH3h3eUPWJpqcvaWq1+yNrSaOk0RFlr2svcMtdKKrXFaMt+aJX1B9Dol36TA+fT9z7nATrb99z3ue/r+l7f67qvcxv6jKHfeA9hE2uiD7UmVd+NDMYdZ2jVNwyvuab5hOyH3Gbz4LJdrwksz8Vl3J5lMm+DcpPAQMzrRFLmeDEDjmTCgVQ4qLn9Prwe9qc7CnKBFpPt+QyujqXVrDx0+2Ruu3mIvWtv8VIW7FuHFDmK6kjFQhSOgjmMJPmBIuscN2w6fNKEWLfRknT6adJsyZL+5fAG6I+5TY9ZFIErwv8hxh97hF4zL6W+6m7T53H0RNOnRGkOJ97ihSy7acmgN9Z3tPMldAtdZmW9U6pHgjCahgQVraQcjn2DQ3db49uC7/SMcG4Evj8Dz2otHJDyBNgTtES+jQ3QoTI8dycMJM74ZGUmjcHgPwyqZq1Ks02Gw+kuXXFw8Sj8ehVq5LhTOBmGnz6DJkuo/XbZtlmlAYdRke42O4xezUSy7IdDa8CVo8v5A3B9Es7ugx8vwKvl8E49jHXC79dBbtQLbbLfbWy6DhEdUnfiuHHb4t9jWIVt0GLLKpeQDN7vhWuWcL9SPgcfvwxT4/B2I/x8RWnfC0dLoNqSibRBtezWyYeDvxi3NWmKrmTYpUWrLizpf83AVydh/Hn4btIfLx+Xylb4QYrH+sFdVOsU4/nsUqlaE5V62t+GlpQbtAftxiJPaTOyGe+5PAZnlPq35/3xixPwlghvXBRxh28zNgRPGtfzbVqrM0idM9QlTmoCVUq5VikXKo13peDrCR2CUr6qlMePwJVT8GazanpJvVcBp6S6SLa1ca58F6kXYVPqn4aa1XtoU0FDIqzWZoEinj6kQ/lUtRxV7T6CYz0w8Qq81gQ3p/CCVghV6lWr8An5NqsX61IuGXJNNg2ZDlVroFKbJcJALuwthA8OwvSXsFXO24QTEfht2icMBXz7SoNbqb+mORM3FBj0etENJXxCgw6jWBvbY/FIH5DTafXhtE71Ec1Lhcb7YLjI2qFLxBXsfJGn71Arpc5yj0nxm3uTyaEmwyGUZJUsUBqwKvS3bHBpyfHVlShQvsZcrRd78G3K4uZpuMvOB3yy4hj/2tpiOtipBq8Q6RbVpEAOecJjcirQpbAtOhYGXC/AVikrCsxTJ7Ky+LMeR+TxGJ+0Kt2/cfJMH9UiDaVaB0ffjsgXNC56yPVg1+cpV+BakZUkjC/fCRtNlDAoVGf6pNmmiOLgDNtV01IRF6g+efEKoIshX81brJ+gUkFL02fZHE3T+uUskeVHF7z0E1eu8/vNDjYmfUju+ptsCs7yaMac8AcPJ39OTvyg7JP9NGtjlsiUlfkPN6xP1XWpt1IAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HTML5 widget&quot;
        title=&quot;HTML5 widget&quot;
        src=&quot;/blog/static/4c740be23bd4eeba13011264809c5994/ae69b/playing.png&quot;
        srcset=&quot;/blog/static/4c740be23bd4eeba13011264809c5994/ae69b/playing.png 113w&quot;
        sizes=&quot;(max-width: 113px) 100vw, 113px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;The front-end team at SoundCloud has been building upon our experiences with the &lt;a href=&quot;https://blog.soundcloud.com/2012/01/26/html5-widget/&quot;&gt;HTML5 widget&lt;/a&gt; to make the recently-released &lt;a href=&quot;https://soundcloud.com/&quot;&gt;Next SoundCloud&lt;/a&gt; beta as solid as possible. Part of any learning also includes sharing your experiences, so here we outline the front-end architecture of the new site.&lt;/p&gt;
&lt;h2&gt;Building a single-page application&lt;/h2&gt;
&lt;p&gt;One of the core features of Next SoundCloud is &lt;em&gt;continuous playback&lt;/em&gt;, which allows users to start listening to a sound and continue exploring without ever breaking the experience. Since this really encourages lots of navigation around the site, we also wanted to make that as fast and smooth as possible. These two factors were enough in themselves for us to decide to build Next as a single-page Javascript application. Data is drawn from our &lt;a href=&quot;https://developers.soundcloud.com/docs&quot;&gt;public API&lt;/a&gt;, but all rendering and navigation happens in the browser for near-instant navigation without the need to make round-trip requests to the server.&lt;/p&gt;
&lt;p&gt;As a basis for this style of application, we have used the massively popular &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt;. What attracted us to Backbone (apart from the fact that we’re already using it for our Mobile site and the Widget), is that it doesn’t prescribe too much about how it should be used. Backbone still provides a solid basis for working with views, data models and collections, but leaves lots of questions unanswered, and this is where its flexibility and strength really lies.&lt;/p&gt;
&lt;p&gt;For rendering the views on the front end, we use the &lt;a href=&quot;http://handlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt; templating system. We evaluated several other templating engines, but settled on Handlebars for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No logic is performed inside the templates, which enforces good separation of concerns.&lt;/li&gt;
&lt;li&gt;It can be precompiled before deployment which results both in &lt;a href=&quot;http://jsperf.com/handlebars-js-vs-mustache-js/3&quot;&gt;faster rendering&lt;/a&gt; and a smaller payload that needs to be sent to clients (the runtime library is only 3.3kb even before gzip).&lt;/li&gt;
&lt;li&gt;It allows for custom helpers to be defined.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Modular code&lt;/h2&gt;
&lt;p&gt;One technique we used with the Widget which ended up being a great success was to write our code in modules and declare all dependencies explicitly.&lt;/p&gt;
&lt;p&gt;When we write code, we write in &lt;a href=&quot;http://wiki.commonjs.org/wiki/Modules/1.1.1&quot;&gt;CommonJS-style modules&lt;/a&gt; which are converted to &lt;a href=&quot;http://requirejs.org/docs/whyamd.html#amd&quot;&gt;AMD modules&lt;/a&gt; when they’re executed in the browser. There are some reasons we decided to have this conversion step, possibly best explained by seeing what each style looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// CommonJS module ////////////////&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; View &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;lib/view&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Sound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;models/sound&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    MyView&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

MyView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; View&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Equivalent AMD module //////////&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;require&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;exports&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;module&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lib/view&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;models/sound&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; View &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;lib/view&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      Sound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;models/sound&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    MyView&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  MyView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; View&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The extra &lt;code class=&quot;language-text&quot;&gt;define&lt;/code&gt; boilerplate is tedious to write&lt;/li&gt;
&lt;li&gt;Duplication of module dependencies is also tedious and error-prone&lt;/li&gt;
&lt;li&gt;Conversion from CommonJS to AMD is easily automated, so why not?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During local development, we convert to AMD modules on-the-fly and use &lt;a href=&quot;http://requirejs.org/&quot;&gt;RequireJS&lt;/a&gt; to load them individually. This makes development quite frictionless as we can just save and refresh to see the updates, however it’s not so great for production since this method creates hundreds of HTTP requests. Instead, the modules are concatenated into several packages, and we drop RequireJS for the super-lightweight module loader &lt;a href=&quot;https://github.com/jrburke/almond&quot;&gt;AlmondJS&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;CSS and Templates as dependencies&lt;/h3&gt;
&lt;p&gt;Since we’re already including all of our code by defining explicit dependencies, we thought it made sense to also include the CSS and template for a view in the same way. Doing this for templates was rather straight-forward since Handlebars compiles the templates into a Javascript function anyway. For the CSS, it was a new paradigm:&lt;/p&gt;
&lt;p&gt;Views define which CSS files are needed for it to display properly, just the same as you would define the javascript modules you need to execute. Only when the view is rendered do we insert its CSS to the page. Of course, there are some common global styles, but mostly, each view has its own small CSS file which just defines the styles for that view.&lt;/p&gt;
&lt;p&gt;When writing the code, we write in plain vanilla CSS (without help of preprocessors such as SCSS or LESS), but since they are being included by Require/Almond they need to be converted into modules as well. This is done with a build step which wraps the styles into a function which returns a &lt;code class=&quot;language-text&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt; DOM element. Here’s an example of how it looks in essence:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Input is plain CSS&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.myView&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #f0f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.myView__foo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid #0f0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Result is an AMD module&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;views/myView.css&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;style&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createTextNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.myView { padding: 5px; color ... }&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Views as components&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 113px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2623543bfa5746756f3fd0e5f1345b8e/ae69b/repost.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAD6UlEQVQ4y2VUTUxcVRQ+b4a2TGcszABlUFloUkha0QawRYa/Do5CgcZMsR06w8AMUKZYMraG/mCMrvxJdGGiTTU2sWpZNTbGxIUL24W6MN3UZTEujDGl0jQ2NVoecz+/e+90QL3J9+59557znfOde98TTIngsJiBrHikODAkIb4fQK7sPRzZ8A1mNn3L9af0P6ISsq3kNyne0ppcoiZKZGYD3eLDtPM68pXLOFUDvBwG5kLACa71+/xW4Hi1yyQXMSINJia30YOkrA1kLJkalR04unkRr9QBL24BK3KZ1WUVq0W4TOZiNmAT5be4KitpS+rzqAOaZMzKREKTBe5g/kHgsHMPE1IggSLh/zFBTMoKK7VVZ2TKcEwU5aODMmf8izhZpzfvUT5MkAXXzjqss4+x6tkgcKwKLKh9TXLW8yZOPwyMk2xc9KxoU8g4OghIr4Pez3qsT8bYXLxUC0z7r1qyLqlCLngLOfbsEGWOOgppYlj0OwPLgEk/K9ts5yRtei/NZKMkTelKvS6O1Wr/QeEjgXydfnF5agpJku2n00dT7CVP97sFmHH3FvDXHeCPJeDsmCVNlSkcIvFBxuZ5SBn/eVGp8jOYqdEOLhJehWfp+O6IJfnhEq/IDuDtuMVrPcDCvN2bexzG93nRalaR4clngz+KSgauYLwCiNM4yM03hmxAoWDnr88CT4sNHmfzM0z++y/ApbeooF7bFPaxVSm2Y6zqpmCk8nuMUtogjZrw3HHg8meUd5dyLwJfnWHgo0CMhAuv2iR//2nn2zdYVb1CH2MTrDAZWhbE/QtcgEYXA+zfHgZ2EbfZq3NzQBPXvUVEiMsXUBqno0APi9hLwv08tIOhnwQDG19AKqwrcPGM3tyg+PkB72Rs0E3K+20RuPEz8Ot14GgLcO0K8P4ssJv+/TyYXt1/tiNe+aWgWRowHHbRtwmsTiFK9Dq2mlwr8MEJ4MOTRZwCnmNgVGCURI0/VJRfTSIMFfPmzF1UMd/nGK7VJCvodrRkhR5e3l0M2vkfdBRbon26TeIC9j7AMwgtoV4q7eVukkYM1LiIBbSMVVNdhAEd/Fo6vfgXtN2AZE9x7ipbwfBDej1tySKO/W21Shr7eMH3kLSVPdlN0jaxfVqPNjMDT7Kydu8K4iTrKv/EcOR7HUvaV23/OC0yhX6SxkI6wGV/NVZpLxg0G7hcszVMPESyDt/50j9huxQJg0R/2JI2SDsiwavoYU87SdzG/rSUM4GPPeXljfAjiDJpZ/USnijK1HGN98l2FQ1Gvn/td/6IDGJ74GM0b72GpuASdtYsE9fxWMUXaCzP0b/Cyhxy7pNRlfwDNk5LbS4SsLAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;repost&quot;
        title=&quot;repost&quot;
        src=&quot;/blog/static/2623543bfa5746756f3fd0e5f1345b8e/ae69b/repost.png&quot;
        srcset=&quot;/blog/static/2623543bfa5746756f3fd0e5f1345b8e/ae69b/repost.png 113w&quot;
        sizes=&quot;(max-width: 113px) 100vw, 113px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;A central concept in developing Next is that of treating views as &lt;strong&gt;independent&lt;/strong&gt; and &lt;strong&gt;reusable&lt;/strong&gt; components. Each view can include other ‘sub’ views, which can themselves include subviews and so on. The effect of this is that some views are merely composites of other views and cover an entire page, whereas others can be as small as a button, or even a label in some cases.&lt;/p&gt;
&lt;p&gt;Keeping these views independent is very important. Each view is responsible for its own setup, events, data, and clean up. Views are ‘banned’ from modifying the behaviour or appearance of their subviews, or even making assumptions about how or where this view itself is being included. By removing these external factors, it means that each view can be included in any context with absolute minimum fuss, and we can be sure that it will work as it is supposed to.&lt;/p&gt;
&lt;p&gt;As an example, the ‘play’ button on Next is a view. To include one anywhere on the site, all that we need to do is create an instance of the button, and tell it the id of the sound it should play. Everything else is handled internally by the button itself.&lt;/p&gt;
&lt;p&gt;To actually create these subviews, most of the time they are created inside the template of the parent view. This is done by use of a custom Handlebars helper. Here is a snippet from a template which uses the &lt;code class=&quot;language-text&quot;&gt;view&lt;/code&gt; helper:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;listenNetwork__creator&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; {{view &quot;views/user/user-badge&quot; resource_id=user.id}}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, adding a subview is as simple as specifying the module name of the view and passing some minimal instantiation variables. What actually happens behind the scenes goes like this:&lt;/p&gt;
&lt;p&gt;When a view is rendered, the template must return a string. When the view helper is invoked, it pushes the attributes passed to it, plus a reference to the requested view class, into a temporary object with an id, and outputs a placeholder element (we use &lt;code class=&quot;language-text&quot;&gt;&amp;lt;view data-id=&amp;quot;xxxx&amp;quot;&amp;gt;&lt;/code&gt;). The id is just a unique, incrementing number. After a template is rendered, the output would be a string which might look something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;foo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;123&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we find the placeholders and replace those elements with the subview’s element which it automatically creates for itself. In essence, the code does this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;parentView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;view&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data-id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; theTemporaryObject&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      SubView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attrs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ViewClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      subView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;attrs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  subView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// repeat the process again&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Sharing Models between Views&lt;/h2&gt;
&lt;p&gt;So, we now have a system where there will be dozens of views on the screen at one time, many of which will be views of the same model. Take, for example, a “listen” page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/assets/posts/Selection_006.png&quot;&gt;&lt;img src=&quot;/blog/assets/posts/Selection_006-505x231.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There would be a view for the play button, the title of the sound, the waveform, the time since the sound was uploaded &lt;em&gt;(this dynamically updates itself, which is why it is a view)&lt;/em&gt;, and so on. Each of these views are of the same sound model, but we wouldn’t want each to duplicate the data. Instead we need to find a way to share the model.&lt;/p&gt;
&lt;p&gt;Also remember that each of these views has to handle the case where there is no data yet. Almost all views are instantiated only with the id of its model, so it’s quite possible that the data for that model hasn’t been loaded yet.&lt;/p&gt;
&lt;p&gt;To solve this, we use a construct we call the &lt;em&gt;instance store&lt;/em&gt;. This store is an object which is implicitly accessed and modified each time a constructor for a model is called. When a model is constructed for the first time, it injects itself into the store, using its id as a unique key. If the same model constructor is called with the same id, then the original instance is returned.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; s1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    s2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

s1 &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; s2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// true, these are the exact same object.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This works because of a surprisingly little-known feature of Javascript. If a constructor returns an object, then that is the value assigned. Therefore, if we return a reference to the instance created earlier, we get the desired behaviour. Behind the scenes, the constructor is basically doing this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; store &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// check if this model has already been created&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// if yes, return that&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// otherwise, store this instance&lt;/span&gt;
    store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is not a particularly new pattern: it’s simply just the Factory Method Pattern wrapped up into the constructor. It could have been written as &lt;code class=&quot;language-text&quot;&gt;Sound.create({id: 123})&lt;/code&gt;, but since Javascript gives us this expressive ability, it makes sense to use it.&lt;/p&gt;
&lt;p&gt;So, this feature means that it’s completely simple for views to share the same instance of a model without knowing anything about the other views, simply by calling the constructor with a single id. We can then use this shared instance as a localised ‘event bus’ to facilitate communication and synchronisation of the views. Usually this is in the form of listening to changes to the model’s data. If the views subscribe to the ‘change’ events which affect it, then they will be notified immediately upon change and the page can be kept up-to-date with very little effort required by the developer.&lt;/p&gt;
&lt;p&gt;This also is how we solve the issue of there being no data on the model. On the first pass, several views might have a reference to a model which only contains an id and no other attributes. When the first view is rendered, it can detect that the view does not have enough information and so it would ask the model to fetch its data from the API. The model keeps track of this request, so that when the other views also ask it to fetch, we do nothing and avoid duplicate requests. When the data comes back from the server, the attributes of the model will be updated, causing ‘change’ events which then notify all the views.&lt;/p&gt;
&lt;h2&gt;Making full use of data&lt;/h2&gt;
&lt;p&gt;A common feature of many APIs is that when one particular resource is requested, other related resources are included in the response. For example, on SoundCloud, when you request information about a sound, included in the response is a representation of the user who created that sound.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* api.soundcloud.com/tracks/49931 */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;49931&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hobnotropic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ...
  &lt;span class=&quot;token property&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1433&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;permalink&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;matas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;matas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;avatar_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://i1.soundc...&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rather than let this extra data go to waste, each model is aware of which ‘sub-resources’ it can expect in its responses. These sub-resources are inserted into the instance store in case any views need to use the data. This means we can save a lot of extra trips to the API and display the views much faster.&lt;/p&gt;
&lt;p&gt;So, for our example above, the Sound model would know that in its property “user” it has a representation of a User model. When that data is fetched, then two models are created and populated on the client side:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;49931&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

sound
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;             &lt;span class=&quot;token comment&quot;&gt;// get the data&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// and when it&apos;s done&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sound&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &apos;matas&apos; -- we already have the model data&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s important to remember is that because there’s only ever one instance of each model, even pre-existing instances are updated. Here’s the same example from above, but note when the User is created.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;49931&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1433&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// undefined -- we haven&apos;t fetched anything yet.&lt;/span&gt;
sound
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &apos;matas&apos; -- the previous instance is updated&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Letting go&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 113px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/625e95ddaf0c1936b44c4c0d32018589/ae69b/notification.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAD20lEQVQ4y42UXWxTZRjHn3Y4Vlq3tdtw8+PCC7eEDyUb4NjWdW7UDOgQR9mK3fcHK1NS8RvUC2+IXBBjYogxMXGaeGGAZI4LjIF4oYbICDpJFDejBL0YmYkxRmGnff/+n/OWNsYYPcm/fc/7Ps/v+TrnCMZFsE/cCyPildyFLgnxvgepFW9g/23nMLnyU67fo/1+k5D78nZjUpRfkyVmNA9zDxARHyY8R5AuX8ILVcCL1cCzIeA5rvX+8GrgqUqHQU5gr9S6PqliL5JSuDBsYaZf1uKJVfN4uQZ4shTMyGFUh1lkcnIYzMGBgA2ULnXMiAxYqM9rehQyaMtEQmGB33D4TmCf9yZGJUuAIfCfGqXGZJmZ2qyHZdxljObKRwvLnPTP4/kaPVQYMOaxjrr+u4x7NuYBBpn1gSBwsAJMqKlQ8oj3VRy6GxgibIhOSTr1Uf1c9/2L1EZtB9iGp+8AJvyzFtYqFUgFf0GqVA2z6KXhB4cMrn8P/HgRuDZHfQ1c/RL46TLwA/cWF4Azr4FDsUEH2euDhD4mMeFPAukavXGQZCm7aXT5LP7z+vkb9TGUlusgzSEN+6fE9JUcxyQbG+dmr9dgFw0uTFunm38Cr7QBz9wPHItzWMzi6pw9mz8P9IjK0DeDYU5+JDgnJhn4BENlICiDOIExBc5Yp99/ZY/Y9E6BG2grdelMDvgF0C0qg0fZqqSfpVdcF+wt/xz9dIpxcxeB6nzhdKG0j98GHuZehDq6p7C/MAvsFJXhW8Xer+KgQkuCbv/7XGgWDmIEahbnP7RON/6w/6eOAUdY8vINIJuxe1eY4XZRaRJZ7CawN7Qg2FH8OPrY0CiBnQS20uDS/xjKtW/VxzB7TcJBgs9id/mMoF5qEa8mbCXQzsMW6qVHgI+mgJk3gZOv0/kKYAxw+i1g+rg9OzoEhGnb4YFp51uTqIaJFqXcZ9FEfacQ5wSbedDGN2AzDR9gOfXUWm08X8fP2IY2L7CO9xuojbSJeNQni+23cwahRdwj5fbhXi912FHlIMrRP8hph4uA1hUGYSpSDGwioINT3MrzFm2LntFmi+h6GfG7dD1hYc0e+9naKAPYyQf8oYBGdwgGGkUDGGwhRLNWNVJ6tomZNRUto5uw1pJ3XUa6w2OhnZX2i9Mg49hGaDSkDg5LVmW4n81L9xrYmggDdxHW4pvKfxPWSA4YpLZVW2itNKE5OIs29jRMcCP701DCAD72lmU38yVoZ9Bw5SL7PJGH1d2Cbc5tuOX7C5/zeyWGNYF3UL/6K6wPLmJD1RL1HdaVTaOuJEX7Mltml+cWjFXJX4h7cyAYrpgIAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;notification&quot;
        title=&quot;notification&quot;
        src=&quot;/blog/static/625e95ddaf0c1936b44c4c0d32018589/ae69b/notification.png&quot;
        srcset=&quot;/blog/static/625e95ddaf0c1936b44c4c0d32018589/ae69b/notification.png 113w&quot;
        sizes=&quot;(max-width: 113px) 100vw, 113px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;Holding on to every instance of every model forever isn’t feasible, especially for the Next SoundCloud. Because of the nature of the site, it’s quite possible that a user might go for several &lt;em&gt;hours&lt;/em&gt; without ever performing a page load. During this time, the memory consumption of the application would just continue to grow and grow. Therefore, at some point we need to flush some models out of the instance store. To decide when it is safe to do this, the instance store increments a usage counter each time an instance has been requested, and views can ‘release’ a model when it is no longer needed and the count is decremented.&lt;/p&gt;
&lt;p&gt;Periodically, we check the store to see if there are any models with a count of zero, and they’re purged from the store, allowing the browser’s garbage collector to free up the memory. This usage count is encapsulated in the store object, but in essence it’s something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; store &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    counts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    counts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  store&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  counts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Sound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;release&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  counts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The reason for performing the cleanup on a timer, rather than whenever a usage count hits zero is so that the model stays in the store when you switch views. If you navigate to another page, there will be a single moment between cleaning up the existing views and setting up the new ones when every single model’s count is zero. The new page might actually contain views of one or more of these models, so it’d be quite wasteful to remove them instantly.&lt;/p&gt;
&lt;h2&gt;A long journey…&lt;/h2&gt;
&lt;p&gt;This has been a brief introduction into some of the methods and concepts we’re using to create &lt;a href=&quot;https://next.soundcloud.com/&quot;&gt;Next SoundCloud&lt;/a&gt;, but it’s just the beginning. There are plenty more features which we have yet to build and therefore plenty more challenges to tackle. If you want join us along the way, remember that &lt;a href=&quot;https://soundcloud.com/jobs&quot;&gt;we’re always hiring&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud at HackTO]]></title><description><![CDATA[On Saturday, I joined over a hundred other hackers at HackTO. This has become a regular event in the Toronto tech scene, thanks to excellent organizing by Leila Boujnane and Corey Reid. SoundCloud joined several other API providers, including Atomic Reach, Context.IO, FreshBooks, Shopify, TinEye, Twilio, Trendspottr, WordPress and YellowAPI. The idea behind the event is simple: find a team (or go solo) and build an app using one or more of the APIs presented by sponsors. All apps have to be demoable by 5pm at which time you pitch to the judges who select the 1st, 2nd and 3rd place winners.]]></description><link>https://developers.soundcloud.com/blog/soundcloud-at-hackto</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-at-hackto</guid><pubDate>Tue, 17 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On Saturday, I joined over a hundred other hackers at &lt;a href=&quot;http://hackdays.ca&quot;&gt;HackTO&lt;/a&gt;. This has become a regular event in the Toronto tech scene, thanks to excellent organizing by &lt;a href=&quot;https://twitter.com/leilaboujnane&quot;&gt;Leila Boujnane&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/#!/barsoomcore&quot;&gt;Corey Reid&lt;/a&gt;. SoundCloud joined several other API providers, including &lt;a href=&quot;http://www.atomicreach.com/&quot;&gt;Atomic Reach&lt;/a&gt;, &lt;a href=&quot;http://context.io/&quot;&gt;Context.IO&lt;/a&gt;, &lt;a href=&quot;http://www.freshbooks.com&quot;&gt;FreshBooks&lt;/a&gt;, &lt;a href=&quot;http://www.shopify.com&quot;&gt;Shopify&lt;/a&gt;, &lt;a href=&quot;http://www.tineye.com/&quot;&gt;TinEye&lt;/a&gt;, &lt;a href=&quot;http://www.twilio.com&quot;&gt;Twilio&lt;/a&gt;, &lt;a href=&quot;http://trendspottr.com/welcome.php&quot;&gt;Trendspottr&lt;/a&gt;, &lt;a href=&quot;http://wordpress.com/&quot;&gt;WordPress&lt;/a&gt; and &lt;a href=&quot;http://www.yellowapi.com/&quot;&gt;YellowAPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea behind the event is simple: find a team (or go solo) and build an app using one or more of the APIs presented by sponsors. All apps have to be demoable by 5pm at which time you pitch to the judges who select the 1st, 2nd and 3rd place winners.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/adb2c9d218b9f84f430ab1cc403fdfc6/df2c7/hackto.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQL/xAAXAQADAQAAAAAAAAAAAAAAAAAAAQMC/9oADAMBAAIQAxAAAAGpxoTtYyYy9MS3jCh//8QAGxAAAgMBAQEAAAAAAAAAAAAAAQIAAxEiEjH/2gAIAQEAAQUCWswjJ4lTuY6ZWl+Ck82Dk/f/xAAXEQEAAwAAAAAAAAAAAAAAAAAAARAh/9oACAEDAQE/AWor/8QAFhEBAQEAAAAAAAAAAAAAAAAAEAEx/9oACAECAQE/AS6f/8QAHBAAAQQDAQAAAAAAAAAAAAAAAAECEBEhMXFh/9oACAEBAAY/AspgqKbr0Xhobyf/xAAbEAADAQADAQAAAAAAAAAAAAAAAREhMUFRYf/aAAgBAQABPyGnSux/Y2KuaOIKfH3RMGNeOdMGRrUEWKk0P//aAAwDAQACAAMAAAAQD+eA/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQAQEf/aAAgBAwEBPxDrLg3/xAAXEQADAQAAAAAAAAAAAAAAAAAAAREQ/9oACAECAQE/EKroj//EABwQAQEBAAIDAQAAAAAAAAAAAAERACFBMVGRof/aAAgBAQABPxBQu4Ql709gFCd+ppVIb1dFxOsF4cNQcKUp5PH37miaLayfmYkkGIgRnvCnAu//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Hackers&quot;
        title=&quot;Hackers&quot;
        src=&quot;/blog/static/adb2c9d218b9f84f430ab1cc403fdfc6/df2c7/hackto.jpg&quot;
        srcset=&quot;/blog/static/adb2c9d218b9f84f430ab1cc403fdfc6/f544b/hackto.jpg 200w,
/blog/static/adb2c9d218b9f84f430ab1cc403fdfc6/41689/hackto.jpg 400w,
/blog/static/adb2c9d218b9f84f430ab1cc403fdfc6/df2c7/hackto.jpg 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;For a one day hackathon, there were an impressive number of hacks demoed — 23 in total. Nearly half used SoundCloud, which was awesome. One thing that struck me was how much sound enhanced the demo process. Having an app that produces audio content makes for a much more engaging demonstration. It’s hard to ignore a demo when speakers are blaring.&lt;/p&gt;
&lt;p&gt;Here’s a few of my favourites from the event:&lt;/p&gt;
&lt;h3&gt;HackPad&lt;/h3&gt;
&lt;p&gt;Built by &lt;a href=&quot;http://alanhietala.tumblr.com/&quot;&gt;Alan Hietala&lt;/a&gt;, HackPad allows you to create loops and triggers using the SoundCloud API. Alan gave an impressive demo that had the crowd moving (not an easy task after a long day of hacking!). He’s got a write up with &lt;a href=&quot;http://alanhietala.tumblr.com/post/21151951258/hackpad-a-hackto-joint&quot;&gt;an overview of the app and a link to Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;80s Joke Line&lt;/h3&gt;
&lt;p&gt;This Twilio and SoundCloud mashup lets users call up a Twilio provisioned number and record or listen to jokes. Jokes are all stored and served by SoundCloud. &lt;a href=&quot;https://twitter.com/funkaoshi&quot;&gt;Ram&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/mveytsman&quot;&gt;Max&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/letstweetbro&quot;&gt;Houssam&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/osirius&quot;&gt;Sam&lt;/a&gt; did a great job demoing. Protip: If your app is based on jokes, your demo will be pretty funny! &lt;a href=&quot;http://jokeline.ontoillogical.com/&quot;&gt;Check out the app&lt;/a&gt; to post your own jokes and listen to others.&lt;/p&gt;
&lt;h3&gt;Podcasting Robots&lt;/h3&gt;
&lt;p&gt;Feeling too lazy to read a blog? That’s cool, &lt;a href=&quot;https://twitter.com/mjangda&quot;&gt;Mo&lt;/a&gt; built this WordPress, SoundCloud and Twilio mashup so you can have robots read it to you. Despite the fun angle, this kind of thing could be a great accessibility app. Could really use some &lt;a href=&quot;https://developers.soundcloud.com/blog/contest-soundcloud-and-acapela-group&quot;&gt;different voices&lt;/a&gt; though :-)&lt;/p&gt;
&lt;p&gt;It was great to see so many people finish their app in just under 6 hours. Thanks to everyone who participated and special thanks to &lt;a href=&quot;http://www.freshbooks.com&quot;&gt;FreshBooks&lt;/a&gt; for hosting!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Create rich media experiences using timed comments]]></title><description><![CDATA[In the last weeks we got pretty excited about the idea of using timed comments to create and script rich media experiences. Imagine being able to trigger all kinds of visualizations & interactions for a timed comment while playing a track. Timed Comments with Media]]></description><link>https://developers.soundcloud.com/blog/rich-media-using-timed-comments</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/rich-media-using-timed-comments</guid><pubDate>Wed, 11 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the last weeks we got pretty excited about the idea of using timed comments to create and script rich media experiences. Imagine being able to trigger all kinds of visualizations &amp;#x26; interactions for a timed comment while playing a track.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://dl.dropbox.com/u/12477597/Permanent/DesktopSharing/timedcomment2.jpg&quot; alt=&quot;Timed Comments with Media&quot;&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;We’ve already built a few experiments around this concept:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;http://ontimed.co&quot;&gt;onTimedComments hack&lt;/a&gt; is a simple example showing off the full power of this idea. It takes any kind of link in the comments and translates it into a rich media embed using &lt;a href=&quot;http://embed.ly&quot;&gt;Embed.ly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://waveraid.com&quot;&gt;WaveRaid&lt;/a&gt; lets you literally play a track. Collect all the timed comments using the play button as your space ship.&lt;/li&gt;
&lt;li&gt;With &lt;a href=&quot;http://storywheel.cc&quot;&gt;StoryWheel&lt;/a&gt; you can create a slideshow from your &lt;a href=&quot;http://instagr.am&quot;&gt;Instagram&lt;/a&gt; pictures and record your story around them. It’s using the timed comments to store where each picture has to show up. Alex &amp;#x26; Eric used this to tell the &lt;a href=&quot;http://storywheel.cc/alex-eric/soundcloud-story&quot;&gt;story of SoundCloud&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the latest version of our &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;Javascript SDK&lt;/a&gt; we’ve made it very simple for everyone to use this technique using the new
&lt;code class=&quot;language-text&quot;&gt;ontimedcomments&lt;/code&gt; event. Here is a basic example (press play to try it):&lt;/p&gt;
&lt;iframe style=&quot;width: 100%; height: 310px&quot; src=&quot;http://jsfiddle.net/johannes/BaGSc/embedded/&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;We are looking forward to all the innovative apps &amp;#x26; hacks you can come up with using this! As always, we are happy to hear your ideas &amp;#x26; thoughts about this.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Desktop Sharing Kits for OS X and Windows]]></title><description><![CDATA[Last week we announced a new integration with Ableton Live 8, that lets you easily share your sounds from within Ableton Live to SoundCloud. Today we’re making the technology behind that integration available to everyone through our new Desktop Sharing Kits. Mac Desktop Sharing Kit]]></description><link>https://developers.soundcloud.com/blog/desktop-sharing-kits</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/desktop-sharing-kits</guid><pubDate>Tue, 10 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week we &lt;a href=&quot;https://blog.soundcloud.com/2012/04/02/soundcloud-and-ableton/&quot;&gt;announced&lt;/a&gt; a new integration with Ableton Live 8, that lets you easily share your sounds from within Ableton Live to SoundCloud. Today we’re making the technology behind that integration available to everyone through our new Desktop Sharing Kits.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://dl.dropbox.com/u/12477597/Permanent/DesktopSharing/mac-sharing-small.png&quot; alt=&quot;Mac Desktop Sharing Kit&quot;&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Similar to our iOS &amp;#x26; Android Sharing Kits, our Desktop Sharing Kits let you easily add “Share to SoundCloud” functionality to your Windows and OS X applications.&lt;/p&gt;
&lt;p&gt;They come as seperate executables for Microsoft Windows and Mac OS that you include in your application and invoke to let users share a sound to SoundCloud. The sharing kits take care of everything from there by providing a nice UI and handling the entire upload process.&lt;/p&gt;
&lt;p&gt;The kits are open sourced and available for download on GitHub (&lt;a href=&quot;https://github.com/soundcloud/soundcloud-win-sharing&quot;&gt;Windows&lt;/a&gt; and &lt;a href=&quot;https://github.com/soundcloud/soundcloud-mac-sharing&quot;&gt;Mac&lt;/a&gt;. We hope to enable some great integrations with this. Let us know what you think!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Wave Raid powered by Timed Comments]]></title><description><![CDATA[One of the SoundCloud API’s most powerful features is the timed comment. At its core it seems simple enough: a piece of text associated with a point in time for a particular track. But where you see timed opinions, I see a light-weight game scripting engine. So when Johannes Wagener told me he was adding an  event to our JS SDK, I immediately began work on Wave Raid: Quest for the Timed Comment.]]></description><link>https://developers.soundcloud.com/blog/wave-raid</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/wave-raid</guid><pubDate>Thu, 05 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 520px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/42ad0c25d706a6ab9bba1c74244ebf5d/249e6/waveraid.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.76923076923077%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAACs0lEQVQozzWSy08TURjFJzFtJZ33dB6dmb5LC22B2hp8JMSNEY3ySKoo0KbFuMGNPIyJDdpSo2BKZWigUkKJ0KgrKSBiQFQU4x+ACRv5G3RnGljUr62e1Z17c+5vzvkuYvkvs9lsNBodDofdbrfZbCaTSa/XC4JAkiQsZFk2GAywabVanU6ny+Xy+XwIfMMunPE8z3GcJEmwBo/f71cUZX9/v6WlBUVRfUWiKAIA/MBobGxEqvexLAtn4IRji9UCtwQCgYmJ1HKhAAuNRsOxrE7HAkCq+OHX3G43Ah7w02VRFEVrCYpmdBhJowRpkESe1Wm1qBYnjqOEjqHhUo4XAAAZy2Zgwg4EU9do263qoaZjN5ya2w2qQK160KuSqZqhE+ouh7qnThXx1ES8JM8LwIOOIDYC8YAs6gWc5s476JFzXJuH7fMzPU1Mp4uyGYSIj7nqpkJ+LtwsXvMKgIZ0UGl9fT0iVgR8UdTzouzxnZJNFmudx2hzSCYrVFrrajDb67zNZ06ePosRFIHjkLyKRPiKKpnpwXvRhaV8OpubnpmZy2bn57LpdDqTyUwqU7FYXFGmhoeHMByrjg06R6olw5RYvfw9N1rKXT9aGzs8Ovp5cLC782nrw/bO12+r6xuzc/NvVtY3N7eADFgoHGKXM4Mf5sAbzbvdplIAKV5Bfm0vLb/fnp5MJRKPniZTyWfKw9HE4/FkNDoCMydJqvp+EBzHIbCOYWhB+tJlLLUjxUvI78+vXq+8G0vEHsTi0ej92Eh8/ElyPJkaGLgDZBhq9SEhBEEwDEPBrFj+YyZeeh4qFib+FA/39n5srK8VVt++zC/mc4vZ6fkX+fzCwixBkHilM9A/M00zGIZeuNzRP3A3dKs/HI7cLKsvGAyGI33dvb3tHZ29wVBr60WVSoVhGFiA/Beej8GaQWXIQQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Wave Raid&quot;
        title=&quot;Wave Raid&quot;
        src=&quot;/blog/static/42ad0c25d706a6ab9bba1c74244ebf5d/249e6/waveraid.png&quot;
        srcset=&quot;/blog/static/42ad0c25d706a6ab9bba1c74244ebf5d/9ec3c/waveraid.png 200w,
/blog/static/42ad0c25d706a6ab9bba1c74244ebf5d/c7805/waveraid.png 400w,
/blog/static/42ad0c25d706a6ab9bba1c74244ebf5d/249e6/waveraid.png 520w&quot;
        sizes=&quot;(max-width: 520px) 100vw, 520px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;166&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; src=&quot;https://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F42017162&amp;amp;auto_play=false&amp;amp;show_artwork=true&amp;amp;color=ff7700&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;One of the &lt;a href=&quot;https://developers.soundcloud.com&quot;&gt;SoundCloud API’s&lt;/a&gt; most powerful features is the &lt;a href=&quot;https://developers.soundcloud.com/docs/api/comments&quot;&gt;timed comment&lt;/a&gt;. At its core it seems simple enough: a piece of text associated with a point in time for a particular track. But where you see timed opinions, I see a light-weight game scripting engine.&lt;/p&gt;
&lt;p&gt;So when &lt;a href=&quot;https://twitter.com/johanneswagener&quot;&gt;Johannes Wagener&lt;/a&gt; told me he was adding an &lt;code class=&quot;language-text&quot;&gt;ontimedcomments&lt;/code&gt; event to our &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;JS SDK&lt;/a&gt;, I immediately began work on &lt;a href=&quot;http://waveraid.com&quot;&gt;Wave Raid: Quest for the Timed Comment&lt;/a&gt;.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Pre Game&lt;/h2&gt;
&lt;p&gt;I’m fairly new to gaming, html5, canvas… pretty much everything used in building this hack. So Daniel X Moore’s &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/canvas/notearsgame/&quot;&gt;No Tears Guide to HTML5 Games&lt;/a&gt; was an absolute godsend. It gives you an extremely solid base to develop HTML5 games while &lt;strong&gt;simultaneously&lt;/strong&gt; explaining the basics of canvas. I wish more tutorials were written like this.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;OnTimedComments&lt;/h2&gt;
&lt;p&gt;So, how does this &lt;code class=&quot;language-text&quot;&gt;ontimedcomments&lt;/code&gt; event work? Just add it as a function to your &lt;code class=&quot;language-text&quot;&gt;SC.stream&lt;/code&gt; event and tell your app what to do with the comment chunks as they come in. In my case, I’m creating coins (comments) to collect in game.&lt;/p&gt;
&lt;p&gt;Disclaimer: &lt;a href=&quot;http://coffeescript.org&quot;&gt;CoffeeScript&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;coffeescript&quot;&gt;&lt;pre class=&quot;language-coffeescript&quot;&gt;&lt;code class=&quot;language-coffeescript&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stream &lt;span class=&quot;token constant&quot;&gt;TRACK_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;ontimedcomments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; comment &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; comments
            coin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Coin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            coins&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Simple? Yes, indeed. Just loop through the comments and do &lt;em&gt;whatever&lt;/em&gt; you want with them. &lt;strong&gt;[nerdrant]&lt;/strong&gt;I &lt;em&gt;may&lt;/em&gt; even want to look at the comment user’s details and vary the coin’s properties. Maybe if the comment came from the track owner, it would be a power up or obstacle.&lt;strong&gt;[/nerdrant]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Post Game&lt;/h2&gt;
&lt;p&gt;“That’s great Lee. but I’m not building a game.”&lt;/p&gt;
&lt;p&gt;OKAY &lt;strong&gt;:(&lt;/strong&gt; … You can use &lt;code class=&quot;language-text&quot;&gt;ontimedcomments&lt;/code&gt; in all sorts of ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Display comments in time with a playing track visualized in interesting ways. 3D, perhaps?&lt;/li&gt;
&lt;li&gt;Maybe your track has an accompanied transcript of timed comments. Wouldn’t it be nice to display those while the track is playing? Yes, it would!&lt;/li&gt;
&lt;li&gt;How about rich media content? Add some &lt;em&gt;photos&lt;/em&gt; as timed comments to your track and then build an app that displays those in realtime. We used a similar technique on &lt;a href=&quot;http://storywheel.cc&quot;&gt;Storywheel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But don’t take my word for it. Get in there, build something awesome, and let us know about it. We’ll be waiting.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Developer Contest: SoundCloud + Acapela Group Mashup]]></title><description><![CDATA[SoundCloud is teaming up with Acapela Group for
our first Developer Contest. Acapela Group offers amazing text to speech solutions
and have a variety of SDKs
so you can write apps that create sound files using one of their voices (including
hip-hop and country!). Take a listen to some sample voices. We’re calling on our developer community to mashup SoundCloud and Acapela. Show
us what you can do using a text to speech service with the best audio content
platform on the web. Maybe you want to have your email read (or sung!) to you
layered on some instrumental or drum tracks. We think there are loads of options
for making interesting and accessible apps using these two services.]]></description><link>https://developers.soundcloud.com/blog/contest-soundcloud-and-acapela-group</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/contest-soundcloud-and-acapela-group</guid><pubDate>Wed, 04 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud is teaming up with &lt;a href=&quot;http://www.acapela-group.com&quot;&gt;Acapela Group&lt;/a&gt; for
our first Developer Contest. Acapela Group offers amazing text to speech solutions
and have a variety of &lt;a href=&quot;http://www.acapela-group.com/develop-with-text-to-speech.html&quot;&gt;SDKs&lt;/a&gt;
so you can write apps that create sound files using one of their voices (including
hip-hop and country!). &lt;a href=&quot;https://soundcloud.com/user6773090/sets/acapela-tts-voices/&quot;&gt;Take a listen to some sample voices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’re calling on our developer community to mashup SoundCloud and Acapela. Show
us what you can do using a text to speech service with the best audio content
platform on the web. Maybe you want to have your email read (or sung!) to you
layered on some instrumental or drum tracks. We think there are loads of options
for making interesting and accessible apps using these two services.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h3&gt;Deadline: May 15th&lt;/h3&gt;
&lt;p&gt;The deadline for submissions is &lt;strike&gt;May 1st&lt;/strike&gt; May 15th. That gives you just under a month to
&lt;a href=&quot;https://developers.soundcloud.com&quot;&gt;check out our API docs&lt;/a&gt; and get started
with a &lt;a href=&quot;http://www.acapela-group.com/develop-with-text-to-speech.html&quot;&gt;Acapela SDK&lt;/a&gt;.
Sign up for a free Acapela evaluation account &lt;a href=&quot;http://www.acapela-vaas.com/signup-for-free-evaluation-account.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you’re app is ready to show off, send it to us using &lt;a href=&quot;https://docs.google.com/a/soundcloud.com/spreadsheet/viewform?formkey=dDZGZ0tCdUlnQ3dCNURKd1Rla3ZQNWc6MQ&quot;&gt;this form&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Prizes&lt;/h3&gt;
&lt;p&gt;This is a chance to get creative and show us something useful and fun. We’ve got some
great prizes lined up for the top 3 picks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3rd place — A pair of &lt;a href=&quot;http://www.monstercable.com/productdisplay.asp?pin=6403&quot;&gt;Monster iSport&lt;/a&gt; headphones.&lt;/li&gt;
&lt;li&gt;2nd place — A pair of &lt;a href=&quot;http://www.puritybymonster.com/&quot;&gt;Monster Purity&lt;/a&gt; headphones.&lt;/li&gt;
&lt;li&gt;1st place — A special prize from &lt;a href=&quot;http://www.monstercable.com/&quot;&gt;Monster&lt;/a&gt; (to be announced) and a 1 year subscription to &lt;a href=&quot;http://www.acapela-vaas.com&quot;&gt;Acapela VaaS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of our top picks will be showcased on this blog. Special thanks to our contest sponsor &lt;a href=&quot;http://www.monstercable&quot;&gt;Monster&lt;/a&gt; and our partner &lt;a href=&quot;http://acapela-group.com&quot;&gt;Acapela&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 229px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/69ee5739e44097bf42f4e5fbb107cfc2/91578/isport-headphones.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.37991266375545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAATABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQL/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHdz4bR0sBIEIP/xAAbEAADAAMBAQAAAAAAAAAAAAABAgMABDIRFP/aAAgBAQABBQIsq5s2RTrOWmzU+iimmvMeTHeS4//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EAB0QAAICAgMBAAAAAAAAAAAAAAECABESIRAxUYH/2gAIAQEABj8C2auYsmUPejQjELtdCWyjOoteRuPpn//EABwQAQADAAIDAAAAAAAAAAAAAAEAETEhQRBhkf/aAAgBAQABPyGsr6LdnNEPdVNACot8R79HK4rG5syEalUyEGO6vxsdAH2f/9oADAMBAAIAAwAAABAABwD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAeEAEBAAICAgMAAAAAAAAAAAABEQAxIUFRYRDh8f/aAAgBAQABPxBktKGCvWU9MIs0+HZrKYDAkgNvcqX1jTWiSB5u9p+ZwsRDyQ2Xr7wZ4w6PG8MSJa88J8GB0x4Bwz//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;iSport by Monster&quot;
        title=&quot;iSport by Monster&quot;
        src=&quot;/blog/static/69ee5739e44097bf42f4e5fbb107cfc2/91578/isport-headphones.jpg&quot;
        srcset=&quot;/blog/static/69ee5739e44097bf42f4e5fbb107cfc2/f544b/isport-headphones.jpg 200w,
/blog/static/69ee5739e44097bf42f4e5fbb107cfc2/91578/isport-headphones.jpg 229w&quot;
        sizes=&quot;(max-width: 229px) 100vw, 229px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 229px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9056956503086f1ecf1160e8ad4c7227/91578/purity-headphones.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQb/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHXrWMY6ICEJgf/xAAcEAACAgIDAAAAAAAAAAAAAAACAwEEEBIAERP/2gAIAQEAAQUCsHokO1N5YHdAMks+YRj/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAfEAACAQIHAAAAAAAAAAAAAAABAhAAEQMhIkFRYZH/2gAIAQEABj8CJpdRKuN+YYdVhhjexnJR5H//xAAbEAACAwADAAAAAAAAAAAAAAABEQAQMSFhcf/aAAgBAQABPyEgso456NI1RZ7yEJ7DHVlAhljKf//aAAwDAQACAAMAAAAQK888/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHBABAQACAgMAAAAAAAAAAAAAAREQIQAxgZHB/9oACAEBAAE/EEFQAOysvAKjYiADurq4QZfYG/nHtA0ZUaPEzCHMIDaY/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Purity by Monster&quot;
        title=&quot;Purity by Monster&quot;
        src=&quot;/blog/static/9056956503086f1ecf1160e8ad4c7227/91578/purity-headphones.jpg&quot;
        srcset=&quot;/blog/static/9056956503086f1ecf1160e8ad4c7227/f544b/purity-headphones.jpg 200w,
/blog/static/9056956503086f1ecf1160e8ad4c7227/91578/purity-headphones.jpg 229w&quot;
        sizes=&quot;(max-width: 229px) 100vw, 229px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; We’ve moved the contest deadline to May 15th. You have a couple more weeks to get those entries in!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[HTML5 Widget API]]></title><description><![CDATA[Today we’re officially announcing our JavaScript API for the new HTML5 SoundCloud Widget. To use it, just insert the script tag on a page…]]></description><link>https://developers.soundcloud.com/blog/html5-widget-api</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/html5-widget-api</guid><pubDate>Thu, 22 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 520px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e0f4c43c1c010b26b98e338381deec51/249e6/html5-api.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.923076923076927%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAIAAABM9SnKAAAACXBIWXMAAAsSAAALEgHS3X78AAABAElEQVQY032PS0vDQBSF8/cFcZFAd1lod+LaGKQ7Y0QNBlqYmTzIE5PMpJlOqgh5jN4kIHXjtzjce+A+jkIpFUIwxmi9Z43ISyY+vrg4jvL7H/q+77pOSZIkz/OiolVMamS/v20+011fp1mAn19eAcdxXNfFGCOELMvKsqxpmrIs27ZVoijabbfID8OnO+9+HW/W7cNV+Xhze315dn6haZqqqqvVyjAMXdehME0Tdtm2HQSBkqYpIcQDfB8R3wvCw0HwPasoresaLhRFAa+xGXCmgJSCAzoNxwtRBEIwPrbtKOWSahgGKKSU48zSDjNTZsgQ/wV2c86bE/gJvyZ88QMw4D6JucbFMgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HTML5 Widget API&quot;
        title=&quot;HTML5 Widget API&quot;
        src=&quot;/blog/static/e0f4c43c1c010b26b98e338381deec51/249e6/html5-api.png&quot;
        srcset=&quot;/blog/static/e0f4c43c1c010b26b98e338381deec51/9ec3c/html5-api.png 200w,
/blog/static/e0f4c43c1c010b26b98e338381deec51/c7805/html5-api.png 400w,
/blog/static/e0f4c43c1c010b26b98e338381deec51/249e6/html5-api.png 520w&quot;
        sizes=&quot;(max-width: 520px) 100vw, 520px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Today we’re officially announcing our JavaScript API for the new HTML5 SoundCloud Widget. To use it, just insert the script tag on a page where you’re using our HTML5 Widget.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://w.soundcloud.com/player/api.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;What does it permit?&lt;/h2&gt;
&lt;h3&gt;Access and control the properties of HTML5 Widget&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;iframe&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;sc-widget&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://w.soundcloud.com/player/?url=https://api.soundcloud.com/users/1539950/favorites&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100%&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;465&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;scrolling&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;frameborder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;iframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://w.soundcloud.com/player/api.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; widgetIframe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sc-widget&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        widget       &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;widgetIframe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;READY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PLAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// get information about currently playing sound&lt;/span&gt;
        widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCurrentSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;currentSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sound &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; currentSound&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;began to play&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// get current level of volume&lt;/span&gt;
      widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVolume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;volume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;current volume value is &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; volume&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// set new volume level&lt;/span&gt;
      widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVolume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// get the value of the current position&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Load widgets&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;iframe&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;sc-widget&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100%&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;166&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;scrolling&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;frameborder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F1848538&amp;amp;show_artwork=true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;iframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://w.soundcloud.com/player/api.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; widgetIframe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sc-widget&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        widget       &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;widgetIframe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        newSoundUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.soundcloud.com/tracks/13692671&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;READY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// load new widget&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FINISH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newSoundUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          show_artwork&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;take a look at our &lt;a href=&quot;/blog/docs/html5-widget&quot;&gt;documentation&lt;/a&gt; for more details and technical descriptions&lt;/li&gt;
&lt;li&gt;check out our &lt;a href=&quot;https://w.soundcloud.com/player/api_playground.html&quot;&gt;playground&lt;/a&gt; to get an overview of functionalities provided by the API&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;If you’ve got a suggestion, found a bug, please let us know by leaving a comment below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hacker Time – the first 3 months]]></title><description><![CDATA[Over 3 months ago we started the “Hacker Time” initiative (see here) and now it is time for a recap what’s happened so far. From the outset…]]></description><link>https://developers.soundcloud.com/blog/hacker-time-the-first-3-months</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/hacker-time-the-first-3-months</guid><pubDate>Mon, 19 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over 3 months ago we started the “Hacker Time” initiative (see &lt;a href=&quot;https://backstage.soundcloud.com/2011/12/stop-hacker-time/&quot; title=&quot;here&quot;&gt;here&lt;/a&gt;) and now it is time for a recap what’s happened so far. From the outset, people outside of the engineering department were very forthcoming in suggesting ideas for Hacker Time projects. While it was great to see so much interest, the essence of Hacker Time is that engineers create the projects that they’re personally motivated to develop. (It’s absolutely key to keep things separate from the general backlog!). Once that clicked, our engineers started initiating and working on their passion projects. Hacker Time has found momentum; currently around 50% of our engineers (who have been in the company longer than 6 months) have a project they are currently working on. These projects range from doing remote online courses to participating in competitions to cool hacks (which sometimes start at “Hack Days” and are then continued within Hacker Time). Some of the highlights are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=InstaSound&quot; title=&quot;InstaSound&quot;&gt;Instasound&lt;/a&gt; lets you record your sounds, apply a range of filters in real time, and instantly share to SoundCloud &lt;a href=&quot;http://vimeo.com/36169628&quot; title=&quot;here&quot;&gt; – video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot; title=&quot;Large Hadron Migrator&quot;&gt;Large Hadron Migrator&lt;/a&gt; – a tool to migrate database tables online without downtime&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rubygems.org/gems/hyperspec&quot; title=&quot;Hyperspec&quot;&gt;HyperSpec&lt;/a&gt; – HyperSpec provides a Ruby DSL for testing HTTP APIs from the “outside”&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://storywheel.cc/&quot; title=&quot;Storywheel&quot;&gt;Story Wheel&lt;/a&gt; lets you record a story around your Instagram pictures and share it on the web as a nostalgic slideshow.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rngtng/massive_sitemap#readme&quot; title=&quot;Massive Sitemap&quot;&gt;Massive Site Map&lt;/a&gt; – a ruby gem to build painfree google sitemaps for webpages with millions of pages. Differential updates keep generation time short and reduces load on DB.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kddcup2012.org/&quot;&gt;KDD Cup 2012&lt;/a&gt; – a team of 5 SoundCloud engineers is participating&lt;/li&gt;
&lt;li&gt;Some engineers are taking online courses from Stanford, e.g. the &lt;a href=&quot;http://jan2012.ml-class.org/&quot; title=&quot;link&quot;&gt;machine learning online course&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve decided not to track the aggregate hours spent on Hacker Time projects, but to leave it to the teams to organize. It’s an approach that’s working well, because it reinforces the spontaneity and informality that this kind of initiative needs in order to be credible.&lt;/p&gt;
&lt;p&gt;So what about the 50% of engineers that haven’t yet worked on their own Hacker Time project? Well, no big surprises here, there are two main reasons: Either they feel they have don’t have enough time or they haven’t found a topic yet.&lt;/p&gt;
&lt;p&gt;Overall we’ve seen the initial concept successfully initiated and it’s begun to get into full swing. We’re really pleased with the output so far and will have more updates soon – some engineers will present their projects in more detail!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Stratus 2 BETA]]></title><description><![CDATA[Stratus is a jQuery powered SoundCloud player that lives at the bottom (or top) of your website or blog. In version 2, we’ve rebuilt Stratus from the ground up to include many requested features, a nicer aesthetic, and a much easier installation. Pumped? Yeh, me too. Here’s how to participate in the BETA:]]></description><link>https://developers.soundcloud.com/blog/stratus-2-beta</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/stratus-2-beta</guid><pubDate>Mon, 12 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 550px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/32c3986a57ccc05df110971382a6512a/2cf63/stratus2-beta.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.36363636363636%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsSAAALEgHS3X78AAAB2klEQVQoz21STU/CQBDtz1J/Gokx3DgRvGnCBTXx42BQqYHUA94sCQRM+EzEIsVCW/sBbbcUqL7dFULEl3Q67e6bmTczwmKxWK0Rx3FIyGw2c6cUcGBt23YcJwiCJQPuL9cQwPnewnw+x1XXdbnlsJkPvu/7iOh5Hnf+IeMeTe46U2ppETAu8wghYRhu7A45mjuuw8hTRuMKfskbjagZ9i85iqLJZKKq6nA4HI/Ho9EIjsqAT0Txt/CXjNiQpOv6SFVN0/yk0PAgChxD178YTPPLsqx/yGiVZVuofNOeDaZrcDm/5JgBDtqgaShwjGNOQAhKY9rJGiHDbuZlEBAuCTw0BgNCIyzL9v2ApaHXYpZPwDE+JrrearUNw4C2drutKAqK/9S0breLZXnrv2HU+Anxg4+PwWCA5GAJiIrXcSZzsLd/fnZeKpUKhUI2m71gEEVRkqRcLtfr9YrFYqVSkWW5VquhLkrmmRv1+s3VNWK/Nhqy/FIul/P5/KMoVqvV+7s7SXpCF5rNJlZoW6MQEW9JvBWTgjNsM+ass5GgclisGtabr3RAQRuGmyhZMF6fverD5e390eFhMplMpVLpdDqRSJycnGKckKe8K7AYe6fT7ff7GAVi8YH9AGFWA3L6tEQWAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Stratus 2 BETA&quot;
        title=&quot;Stratus 2 BETA&quot;
        src=&quot;/blog/static/32c3986a57ccc05df110971382a6512a/2cf63/stratus2-beta.png&quot;
        srcset=&quot;/blog/static/32c3986a57ccc05df110971382a6512a/9ec3c/stratus2-beta.png 200w,
/blog/static/32c3986a57ccc05df110971382a6512a/c7805/stratus2-beta.png 400w,
/blog/static/32c3986a57ccc05df110971382a6512a/2cf63/stratus2-beta.png 550w&quot;
        sizes=&quot;(max-width: 550px) 100vw, 550px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stratus.sc&quot;&gt;Stratus&lt;/a&gt; is a &lt;a href=&quot;http://jquery.com&quot;&gt;jQuery&lt;/a&gt; powered &lt;a href=&quot;https://soundcloud.com&quot;&gt;SoundCloud&lt;/a&gt; player that lives at the bottom (or top) of your website or blog. In &lt;strong&gt;version 2&lt;/strong&gt;, we’ve rebuilt Stratus from the ground up to include many requested features, a nicer aesthetic, and a much easier installation.&lt;/p&gt;
&lt;p&gt;Pumped? &lt;em&gt;Yeh&lt;/em&gt;, me too. Here’s how to participate in the BETA:&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;See installation guides for:&lt;/strong&gt; &lt;a href=&quot;https://stratus.sc/guides#wordpress&quot;&gt;Wordpress&lt;/a&gt; &lt;a href=&quot;https://stratus.sc/guides#tumblr&quot;&gt;Tumblr&lt;/a&gt; &lt;a href=&quot;https://stratus.sc/guides#blogger&quot;&gt;Blogger&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Installing Stratus is easy. All you need to do is add some code to the &lt;code class=&quot;language-text&quot;&gt;head&lt;/code&gt; section of your HTML.&lt;/p&gt;
&lt;p&gt;First, make sure you have &lt;a href=&quot;http://jquery.com&quot;&gt;jQuery&lt;/a&gt; installed.&lt;/p&gt;
&lt;p&gt;Next, include the main Stratus library.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://stratus.sc/stratus.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https://stratus.sc/#customization&quot;&gt;configure&lt;/a&gt; and load the Stratus plugin.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ready&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;body&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stratus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      links&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://soundcloud.com/foofighters/sets/wasting-light&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;That’s it!&lt;/strong&gt; For a list of all configuration options, go &lt;a href=&quot;https://stratus.sc/#customization&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;All good BETA tests require good feedback. If you’ve got a suggestion, found a bug, or deployed the player on your site: &lt;strong&gt;Please&lt;/strong&gt;, let me know &lt;a href=&quot;https://twitter.com/leemartin&quot;&gt;@leemartin&lt;/a&gt; or at &lt;a href=&quot;mailto:lee@soundcloud.com&quot;&gt;lee@soundcloud.com&lt;/a&gt;. We really appreciate the help!&lt;/p&gt;
&lt;p&gt;Thanks and happy BETA testing!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Moving API Support to Stack Overflow]]></title><description><![CDATA[The SoundCloud Developer Community has grown immensely. We have over ten thousand registered applications and over three hundred showcased in our App Gallery. Our goal as the Platform Team is to provide the best API tools possible, while also providing support and inspiration. So far, our primary channels have been this blog, our Twitter account, and our mailing list, hosted on Google Groups. In the coming months, you can expect some changes as we retool to accommodate the growth of our developer community. We are revisiting everything and doubling down on our efforts to provide the best, most accessible platform possible for building amazing applications that use sound in exciting new ways on the web.]]></description><link>https://developers.soundcloud.com/blog/moving-api-support-to-stack-overflow</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/moving-api-support-to-stack-overflow</guid><pubDate>Wed, 07 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The SoundCloud Developer Community has grown immensely. We have over ten thousand registered applications and over three hundred showcased in our &lt;a href=&quot;https://soundcloud.com/apps&quot;&gt;App Gallery&lt;/a&gt;. Our goal as the Platform Team is to provide the best API tools possible, while also providing support and inspiration. So far, our primary channels have been this blog, our &lt;a href=&quot;https://twitter.com/soundclouddev&quot;&gt;Twitter account&lt;/a&gt;, and our &lt;a href=&quot;https://groups.google.com/group/soundcloudapi&quot;&gt;mailing list&lt;/a&gt;, hosted on Google Groups.&lt;/p&gt;
&lt;p&gt;In the coming months, you can expect some changes as we retool to accommodate the growth of our developer community. We are revisiting everything and doubling down on our efforts to provide the best, most accessible platform possible for building amazing applications that use sound in exciting new ways on the web.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Our first major change is that starting today, we are moving our primary support channel from the mailing list to &lt;a href=&quot;http://stackoverflow.com/questions/tagged/soundcloud&quot;&gt;Stack Overflow&lt;/a&gt;. Stack Overflow is a hugely popular Q&amp;#x26;A site, powered by &lt;a href=&quot;http://stackexchange.com/&quot;&gt;Stack Exchange&lt;/a&gt;. Developers from all communities have been using Stack Overflow to support each other and provide guidance on a great variety of programming topics.&lt;/p&gt;
&lt;p&gt;We encourage you to start using the &lt;a href=&quot;http://stackoverflow.com/questions/tagged/soundcloud&quot;&gt;‘soundcloud’ tag&lt;/a&gt; on Stack Overflow from now on for all questions related to the SoundCloud Platform. You can set up email notifications for new questions that show up with our tag, or you can subscribe to an &lt;a href=&quot;http://stackoverflow.com/feeds/tag/soundcloud&quot;&gt;RSS feed&lt;/a&gt;. We are monitoring questions, and will chime in when we can be of assistance. Our hope is that, using this new format, we will be able to build up a knowledge base, and will be able to take advantage of the expertise of our platform users as well as members of the broader programming community.&lt;/p&gt;
&lt;p&gt;On April 2nd, we will be turning the Google Group into a announcement only list. You can stay subscribed and expect regular updates on new features to the platform. Our team will continue to be active on the mailing list during the transition phase.&lt;/p&gt;
&lt;p&gt;See you all on Stack Overflow!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Announcing new Python SDK]]></title><description><![CDATA[It’s no secret that SoundCloud is a Ruby shop, but that doesn’t stop us from giving some love to the Pythonistas in our community. Our old Python API wrapper has been neglected. It doesn’t support OAuth 2 or all of the resources made available by our API. It’s old and crufty and we’re sorry for letting it get that way. In order to make it up to you, we wrote a new one and made it much better.]]></description><link>https://developers.soundcloud.com/blog/announcing-new-python-sdk</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/announcing-new-python-sdk</guid><pubDate>Tue, 06 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s no secret that SoundCloud is a Ruby shop, but that doesn’t stop us from giving some love to the Pythonistas in our community.&lt;/p&gt;
&lt;p&gt;Our old Python API wrapper has been neglected. It doesn’t support OAuth 2 or all of the resources made available by our API. It’s old and crufty and we’re sorry for letting it get that way. In order to make it up to you, we wrote a new one and made it much better.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Our new Python SDK has an interface that should be very familiar to anyone who has used our Ruby or JS SDKs:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; soundcloud

&lt;span class=&quot;token comment&quot;&gt;# Create a new client that uses the user credentials oauth flow&lt;/span&gt;
client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; soundcloud&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Client&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;client_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;YOUR_CLIENT_ID&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           client_secret&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;YOUR_CLIENT_SECRET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           username&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;YOUR_USERNAME&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           password&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;YOUR_PASSWORD&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# print the username of the authorized user&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/me&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;username&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is available on &lt;a href=&quot;http://pypi.python.org/pypi/soundcloud/&quot;&gt;PyPi&lt;/a&gt;, so you can install it using easy_install, or better yet, &lt;a href=&quot;http://www.pip-installer.org/en/latest/index.html&quot;&gt;pip&lt;/a&gt;. The source code is available on &lt;a href=&quot;https://github.com/soundcloud/soundcloud-python&quot;&gt;GitHub&lt;/a&gt;, contributions of all kind are very welcome.&lt;/p&gt;
&lt;p&gt;If you want to give it a whirl, you can install it from PyPi and take a look at the examples in the &lt;a href=&quot;https://github.com/soundcloud/soundcloud-python/blob/master/README.rst&quot;&gt;README&lt;/a&gt; file. As always, let us know if you’re building something cool, we’d love to hear about it!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How we do… Retrospectives]]></title><description><![CDATA[SoundCloud started its agile journey with Scrum and eventually moved to an approach based on Kanban (more on that in one of the next blog…]]></description><link>https://developers.soundcloud.com/blog/how-we-do-retrospectives</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/how-we-do-retrospectives</guid><pubDate>Sun, 04 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SoundCloud started its agile journey with Scrum and eventually moved to an approach based on Kanban (more on that in one of the next blog posts). Regardless of the methodology we follow, though, we strongly believe that the most important tools an agile team can use to practice continuous improvement are retrospectives. You might ask why we consider the retrospective to be the most important one? We think it is key to constantly learn and to improve based on our experiences. The best way to achieve this is to look back at our work in regular intervals and to give every team member the opportunity to give input on the past work – and to make sure we have actually improved something. This is done by reviewing action items from the last retrospective as the first step.&lt;/p&gt;
&lt;h2&gt;What kind of retrospectives?&lt;/h2&gt;
&lt;p&gt;The most frequent retrospective meeting is done after each iteration – in our context this means every two weeks. Every three months we do a big retrospective meeting with all of the engineers in one room to find common patterns in our development department. One major result of these big retrospectives is our “hacker time” project – see &lt;a href=&quot;https://backstage.soundcloud.com/2011/12/stop-hacker-time/&quot; title=&quot;here&quot;&gt;here&lt;/a&gt; for more details.
The length of the retrospective depends on the size of the team and the time between retrospectives. We usually schedule 30 minutes for the short one after each iteration and 90 minutes for big ones.&lt;/p&gt;
&lt;h2&gt;How is the retrospective done?&lt;/h2&gt;
&lt;p&gt;Pre-requisites:&lt;/p&gt;
&lt;p&gt;You need only a few things to do a good retrospective – you can even do one
only with Google Docs – but it works best with the following things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Post-its in 3 different colors (optimal is green, red, yellow)&lt;/li&gt;
&lt;li&gt;Enough pens so everybody has one – rather use big pens than ball pens so the content is short and everybody in the room can read it&lt;/li&gt;
&lt;li&gt;A big wall to put the post-its on, a whiteboard will do&lt;/li&gt;
&lt;li&gt;And someone to facilitate the retrospective, in Scrum it should be the Scrum Master. Really experienced teams do not necessarily need a facilitator. The facilitator keeps track of the time and leads the grouping of items.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The procedure&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;You put three columns on the wall with the title: less – same – more. Ideally you label each columns with one of the three differently colored post-it’s (red= less, green=same, yellow=more).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, all items put under “less” are things which either should be done less or should be stopped completely. “Same” is obvious, and “more” are items which should be done more or should be started.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; This scheme avoids the bad/good categories often used and allows an open communication where putting a sticker somewhere does not mean something is “bad” – it allows a more nuanced conversation.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Collecting items: everybody gets around 5-10 minutes to write down their points. During that time no discussions are allowed, it is only about collecting items.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; Everybody can give their input, often some people tend to dominate a discussion and the other people are not heard as much. This approach enables everybody to give their input.&lt;/p&gt;
&lt;p&gt;People are free to put post-it’s on the wall (on the three columns) anytime. Latest after 10 minutes or when nobody is writing anymore the next phase starts.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Group items&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The facilitator starts grouping items into clusters to find common themes. Usually there always are topics which a lot of people mention. Every item is read out to the group and if everybody agrees put to a cluster.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Find good names for the clusters&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the final test if the grouping made sense – if you cannot agree on a name the cluster has too many different items and has to be split.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://backstage.soundcloud.com/?attachment_id=540&quot;&gt;&lt;img src=&quot;https://backstage.soundcloud.com/wp-content/uploads/2012/03/retrospective_big-505x377.jpg&quot; title=&quot;retrospective_big&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Vote&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To identify the most pressing issues (number of post-its in a cluster is also a pretty meaningful indicator) everybody gets three votes and can give one vote to three different clusters. The top three items should be addressed during the next iteration.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Discuss&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If there is time left start discussing the clusters starting with the one which got the most votes. Try to get action items out of it.&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Action-items&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A retrospective tends to be useless if it is “just talk” and no actions follow.
So the key ending of a retrospective is to to collect action-items to work on at least the top voted clusters. The next retrospective starts then with a review of the action-items from the last retrospective.&lt;/p&gt;
&lt;p&gt;We have been doing it that way for the last 8 months with pretty good results. There are around 8 engineering teams at SoundCloud and 45 engineers overall. The key challenge is here to continue doing retrospectives and to follow up on the action items.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud at Music Hack Day SF]]></title><description><![CDATA[This past weekend, hundreds of hackers showed up at the TokBox HQ with a mission to build the future of music. The event started with pitches — giving sponsors a chance to show off their APIs — followed by in-depth workshops where attendees could learn more about each platform and ask questions related to their project. Once the hacking time started, people quickly broke off into groups and got to work.]]></description><link>https://developers.soundcloud.com/blog/soundcloud-at-music-hack-day-sf</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-at-music-hack-day-sf</guid><pubDate>Tue, 14 Feb 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This past weekend, hundreds of hackers showed up at the &lt;a href=&quot;http://www.tokbox.com/&quot;&gt;TokBox HQ&lt;/a&gt; with a mission to build the future of music. The event started with pitches — giving sponsors a chance to show off their APIs — followed by in-depth workshops where attendees could learn more about each platform and ask questions related to their project. Once the hacking time started, people quickly broke off into groups and got to work.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8ab74287b4c087ff584c878847bb6439/48a11/hackers.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAABA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHscmQmHL//xAAbEAACAgMBAAAAAAAAAAAAAAABAgARAwQSMf/aAAgBAQABBQJqEPpZ4Ni2L9nrGZ//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAHBAAAgICAwAAAAAAAAAAAAAAABEBEiExAiJB/9oACAEBAAY/At48GzZF4tgfGKnazP/EABoQAQEBAAMBAAAAAAAAAAAAAAERACExUWH/2gAIAQEAAT8haRJy9YhA2drcN768uo3BonE91V+jf//aAAwDAQACAAMAAAAQ/M//xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQMBAT8QW0v/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/EB//xAAcEAEBAAICAwAAAAAAAAAAAAABEQBBITFxkbH/2gAIAQEAAT8QCHYno6mjCiApoU7nGHNxegwwUEQ5XS3xlcxVS+mLlzegT7n/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Room full of Hackers&quot;
        title=&quot;Room full of Hackers&quot;
        src=&quot;/blog/static/8ab74287b4c087ff584c878847bb6439/48a11/hackers.jpg&quot;
        srcset=&quot;/blog/static/8ab74287b4c087ff584c878847bb6439/f544b/hackers.jpg 200w,
/blog/static/8ab74287b4c087ff584c878847bb6439/41689/hackers.jpg 400w,
/blog/static/8ab74287b4c087ff584c878847bb6439/48a11/hackers.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;SoundCloud were well represented. &lt;a href=&quot;mailto:lee@soundcloud.com&quot;&gt;Lee Martin&lt;/a&gt;, &lt;a href=&quot;https://soundcloud.com/jwagener&quot;&gt;Johannes Wagener&lt;/a&gt;, &lt;a href=&quot;https://soundcloud.com/hannes&quot;&gt;Hannes Tydén&lt;/a&gt; and &lt;a href=&quot;https://soundcloud.com/jstnw&quot;&gt;Justin Street&lt;/a&gt; built a hack that shows the future of concerts where a major ticket corporation has taken over the world and all concerts must be attended virtually. &lt;a href=&quot;http://concert2021.com&quot;&gt;Check it out&lt;/a&gt;, it’s really quite fun. Also, &lt;a href=&quot;http://timbormans.com/&quot;&gt;Tim Bormans&lt;/a&gt; got SoundCloud working with Sonos. Check out his &lt;a href=&quot;http://www.youtube.com/watch?v=DxownCUQSi4&quot;&gt;demo video&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3d521bf844e75ef5465b82c3980c3d08/48a11/lee-johannes-hacking.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/2gAMAwEAAhADEAAAAU5vVl5ZeN//xAAaEAACAwEBAAAAAAAAAAAAAAAAAgEDEgQU/9oACAEBAAEFAoaB202iutDzVqNzLr//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8Bq6//xAAWEQADAAAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8BiIj/xAAcEAACAgIDAAAAAAAAAAAAAAAAAREhAlEiMTL/2gAIAQEABj8Ci6QkslZ0eUXynY4bS0f/xAAaEAEBAQEAAwAAAAAAAAAAAAABEQAhMUFR/9oACAEBAAE/IUkdUvCGVAZeofckutpZUrxlfDQ3/9oADAMBAAIAAwAAABCEz//EABYRAAMAAAAAAAAAAAAAAAAAABARQf/aAAgBAwEBPxBoD//EABURAQEAAAAAAAAAAAAAAAAAACEQ/9oACAECAQE/EAZf/8QAHBABAAMAAgMAAAAAAAAAAAAAAQARITFRQWHB/9oACAEBAAE/EFQWNDqc1e6zsTCqAC/kHNTXkY1NK2W1AMkYqPfTmKlu04Z7Z//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Lee and Johannes Hacking&quot;
        title=&quot;Lee and Johannes Hacking&quot;
        src=&quot;/blog/static/3d521bf844e75ef5465b82c3980c3d08/48a11/lee-johannes-hacking.jpg&quot;
        srcset=&quot;/blog/static/3d521bf844e75ef5465b82c3980c3d08/f544b/lee-johannes-hacking.jpg 200w,
/blog/static/3d521bf844e75ef5465b82c3980c3d08/41689/lee-johannes-hacking.jpg 400w,
/blog/static/3d521bf844e75ef5465b82c3980c3d08/48a11/lee-johannes-hacking.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Lee and Johannes hacking away. Photo taken by &lt;a href=&quot;http://www.flickr.com/photos/schill/6868010723/in/photostream&quot;&gt;Scott Schiller&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/3ccd7308176bea53890a8e52b046ff09/48a11/professor-hannes.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAwb/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABoL78+XhQT//EABoQAAEFAQAAAAAAAAAAAAAAAAABAgMQEiH/2gAIAQEAAQUCGy6WuOm2p//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAAICAwAAAAAAAAAAAAAAAAEQABEhQWH/2gAIAQEABj8ChGK0+Wv/xAAZEAADAQEBAAAAAAAAAAAAAAAAATEhEBH/2gAIAQEAAT8hUJcSveJ4NwaKVdP/2gAMAwEAAgADAAAAELPf/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8Qp//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/EIf/xAAYEAEBAQEBAAAAAAAAAAAAAAABEQAhQf/aAAgBAQABPxAysRgAQ7ezuaPbdC8qANMOUuNT1erv/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Professor Hannes&quot;
        title=&quot;Professor Hannes&quot;
        src=&quot;/blog/static/3ccd7308176bea53890a8e52b046ff09/48a11/professor-hannes.jpg&quot;
        srcset=&quot;/blog/static/3ccd7308176bea53890a8e52b046ff09/f544b/professor-hannes.jpg 200w,
/blog/static/3ccd7308176bea53890a8e52b046ff09/41689/professor-hannes.jpg 400w,
/blog/static/3ccd7308176bea53890a8e52b046ff09/48a11/professor-hannes.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Hannes demonstrates the very scientific sprite transition process.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Hacking went on through the night, with many attendees going non-stop without sleep, wrapping up with demos at 3pm on Sunday. In total, more than 60 hacks were completed. Here are some of our favorites:&lt;/p&gt;
&lt;h3&gt;Cloud Player&lt;/h3&gt;
&lt;p&gt;Cloud Player, created by Aaron Leese, is an Ableton Live plugin that lets you drag and drop sounds from all over the web (including SoundCloud) into your Digital Audio Workstation. It was great to see someone bridging the gap between web platforms and audio production software. We picked Aaron’s hack as our favorite, so we’ll be flying him out to the next North American Music Hack Day.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/996aa0c3405802366ce3d8b0b66b6e47/d19c0/cloudbrowser.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACe0lEQVQoz21RSU9TYRT9Encakyort0bFxJShBUNpCw9KaakmdFGoJGUMgg0IFdCwgETURDAhcQiBYCKF0r7X19KBFigxDsgkUEbdgD/meN+HQ1AWJ+fcc2/OvV8+NiXH8Hrch/X0d2wfHHEsfviKpZVd7Hz7gfT+4R9s7x/9p7cJW3uHWF7fxfJGGiwYX8DLsUl8Wk1jbeuAI/VxBe+X1rle3dw/HVt/9fLGHr6s7eDz5gaYL5zE0Ku3eBeYgT8yB184Af9Mkutp0tOkOXOdOFX7QrMIRJLwBaNgXlHEizdj8EoxiNF5ahwH8VCCUv8Lf+Sk/3tO8ZkYnsbwyCgm5Tjk2RTEGIVG5yDFUwhSLcUW+AWTweOFSq3McESPEfjFocQimBRfxCA92StFyKSNtE2i50xJM5jwy/AGQvCHYpBjcxAjCUjRJOdQfJ57MnFQYUI4kQILJ5XAcYx6w/AGk5gQozCbLbBZy9DS5ITL5UJdbS06Ozrg8XTC7XYTe3C/vR3d3d3c7+rqwgPqdZDPhke8aO/pQ/+zATwZGkTv4+c4r7qIAu0V2G8ZUG4qRGWlnZaY4XA4OFutVuj1elgsFhgMNEMsFBejlHrMJ8+iracfekGH3Js5cHse4VrmDWRevQxNrhpaTQ6qnU7YbDbY7XYIggBTqQlarRZGoxEajQZCSQny8/NQZi4HG5+SUH/vIc5euAR25hyqGpuRqb4OxhhUKhUyMjJgq6iATqfjoUUUYqSr1Go1CsnLIlZ6OdlZKCBmyg929g6g0lEFk/U2evqeoqahFdmaPBiKBBTTNc2tbtTWN6LF3Ya6hia46hpQ5aw5wY7qO6hvuoufLHE7nlgHQZoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Drag and Drop Plugin&quot;
        title=&quot;Drag and Drop Plugin&quot;
        src=&quot;/blog/static/996aa0c3405802366ce3d8b0b66b6e47/d19c0/cloudbrowser.png&quot;
        srcset=&quot;/blog/static/996aa0c3405802366ce3d8b0b66b6e47/9ec3c/cloudbrowser.png 200w,
/blog/static/996aa0c3405802366ce3d8b0b66b6e47/c7805/cloudbrowser.png 400w,
/blog/static/996aa0c3405802366ce3d8b0b66b6e47/d19c0/cloudbrowser.png 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Drag a loop from SoundCloud into Ableton&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Buddhafy&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Buddhafy&quot;&gt;Buddhafy&lt;/a&gt; was one of the more out there hacks. &lt;a href=&quot;https://twitter.com/#!/ocelma&quot;&gt;Oscar Celma&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/#!/cweichen&quot;&gt;Ching-Wei Chen&lt;/a&gt; hooked a NeuroSky MindWave controller up to Spotify, allowing you to control playlists with your mind. The users mental state would determine what songs would be played. Yes. Seriously. Sounds like something out of a sci-fi movie, right?&lt;/p&gt;
&lt;h3&gt;whsprs.co&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://whsprs.co&quot;&gt;whsprs.co&lt;/a&gt; lets you play a game of telephone using SoundCloud. It was built by &lt;a href=&quot;https://twitter.com/#!/ianshmean&quot;&gt;Ian Butterworth&lt;/a&gt; who was actually our pick for winner of the last music hack day,  so we weren’t that surprised that he built something awesome again. Users connect with their SoundCloud account, and using the Javascript SDK can record a ‘whisper’. Other users can look for a whisper recorded by someone nearby and repeat it, and so on and so on. You can check on the status of a whisper and see how far around the globe it’s gotten. Kudos to Ian for finding an interesting new way to use sound on the web.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1297efc7957969977f4d573057196bc5/48a11/whsprs-demo.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAAD/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwEC/9oADAMBAAIQAxAAAAHQSBiu0e1P/8QAGBABAQEBAQAAAAAAAAAAAAAAAQIAAxL/2gAIAQEAAQUCvlWmM0iPuYuV8dTf/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8Bqv/EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/AYf/xAAgEAACAQEJAAAAAAAAAAAAAAAAAQIDEBESISIxQVGB/9oACAEBAAY/AsEVlyXs69JSqS0o3dn/xAAbEAACAwEBAQAAAAAAAAAAAAABEQAhMUFhcf/aAAgBAQABPyEDNonsL0o0osI+CUbPqFsM2I8BDEcsP6Z//9oADAMBAAIAAwAAABB/H//EABcRAQADAAAAAAAAAAAAAAAAAAABUXH/2gAIAQMBAT8Qm7T/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8Qhkf/xAAdEAEBAAICAwEAAAAAAAAAAAABEQAhMUFhkaFR/9oACAEBAAE/EC2IrYWHXgwE6YJCzS37igxfpnoxXxxJrXy695xAaIjuD44xUBpURWOf/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Ian demoing whsprs.co&quot;
        title=&quot;Ian demoing whsprs.co&quot;
        src=&quot;/blog/static/1297efc7957969977f4d573057196bc5/48a11/whsprs-demo.jpg&quot;
        srcset=&quot;/blog/static/1297efc7957969977f4d573057196bc5/f544b/whsprs-demo.jpg 200w,
/blog/static/1297efc7957969977f4d573057196bc5/41689/whsprs-demo.jpg 400w,
/blog/static/1297efc7957969977f4d573057196bc5/48a11/whsprs-demo.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;Ian demoing &lt;a href=&quot;http://whsprs.co&quot;&gt;whsprs.co&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;SoundCloud Wordpress Plugin&lt;/h3&gt;
&lt;p&gt;Richard Cáceres took on the task of integrating SoundCloud with WordPress. The plugin allows WordPress users to connect their blog to their SoundCloud account, then choose from a list of tracks to embed in a blog post. This is an incredibly useful plugin. The source code is already up on &lt;a href=&quot;https://github.com/rcaceres/SoundCloud-Wordpress&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was great to see all of the hacks people worked on. Thanks to &lt;a href=&quot;http://the.echonest.com/&quot;&gt;The Echo Nest&lt;/a&gt;, &lt;a href=&quot;http://www.tokbox.com/&quot;&gt;TokBox&lt;/a&gt; and &lt;a href=&quot;http://www.rdio.com&quot;&gt;Rdio&lt;/a&gt; for organizing a great event and thanks to everyone who participated. It was a lot of fun.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Tenacious D's Fenix rizes with SoundCloud]]></title><description><![CDATA[This is not The Greatest Website in the World, no. This is just a tribute. I was approached by Tenacious D a week ago with an incredible quest: raising a phallus shaped phoenix onto the internet (oh and the first clip of audio from their new record.) How could I raise this majestic beast with the power of SoundCloud’s legendary API? The answer was simple: Have the sound itself rize the phoenix from its ashes.]]></description><link>https://developers.soundcloud.com/blog/tenacious-d-s-fenix-rizes-with-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/tenacious-d-s-fenix-rizes-with-soundcloud</guid><pubDate>Fri, 03 Feb 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 550px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/abc0fb4d58b738f63ae37a9d59deff25/2cf63/fenix.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.72727272727273%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAADOElEQVQoz0WT22/TdhTH/T61oZcNBKzNhSZpm2Rpm8ZJbCe+/Rw7viWO7fpCVqdL2/QiZZlAqdZNGoKBBEjwwAMqFKTCNG1IICEEk5j2hAQPSNMe9jAh7WF/wPYvcChIO/rJb9/z/Z7POcZM0/R9f2Vl5Qt4q6uG79c0TdU1Wa7JsiwIAl2plMtlHMcXFhYymczMzMzU1FQkEgmHw5ht247j+J7XCoJuq9Xj2CVZNi3bdF213kBCtUIzFEUVCoXFxcVsNgv6ZDIZi8Wi0Si2dFiWaTZdd01Vbo59dP740d1YdC2d/qpzemu93dB1hkM8z0OLXC43Nzc3PT0dOywMbN+pbbDyOqpy65PhW9EjeyeHzo2O7nWkx9e6D6/1Lg8CyM+yLPiDeHZ2Nh6PQ/gPYse2Lc8LJPHu+PCT4sgjMnQ7cfQgf+x8JnzdJc9aBZphYPJisQjiVCoF5olE4kNsaNB0nM8ROggN7R8Zebp47O/B8Qf+xKWp0af1iUqWZBHPsSxJkvPz8+l0GsQw+f9icO4o8sHQ0E/xE39sRP7dP3EzCLez47/3p+9tOohDkiSVKQqYvxfD9x1tKNdZsnx/VVV++Dj0sv7pX73Ifz9OvujEv58c+W23/vLCl70qJah1jmWAGTCHhUEEzLKAtAnO74HdOzn8QEz+2T71z2Dy13LifmT8qs3strQdWzIMneN4giAAWz6fh+Gxer3eaDSAdtNx13XlxmRoLY/vK6XXRvJNL/ZmY+J5UHx8Z+fVo3Nntv0KzTEMQ9M0x3FgicECYIdyrdaw7HVDOUOPaXguKBAXGeKGmNnB43fb0vOfL75+duXCt12OF0SxCsN7ntftdrFSqQTbR4gXJcW3RK8xxmXTjWxOySzQ8RQZSXy3Yf5y/5uHe4Ov+4GsaHASrRZcY7C9vY3BALAAQUAMKxg6NVgOoWRSSMxW05/VcFxnmJUlre2qqiSoqqppGqR1XXdra6vf72P8YUF4usJWJcL3U4ikaggpsqzpOo8QQZaLJVKqyYBG13UQA+Pl5eXNzU1MOixgABFYhuORzPF8haZLBEGRZAHHCwUc/is4T0kUgW6z2YQW1WrVMIy3s6EP3M9L+GIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Rize of the Fenix&quot;
        title=&quot;Rize of the Fenix&quot;
        src=&quot;/blog/static/abc0fb4d58b738f63ae37a9d59deff25/2cf63/fenix.png&quot;
        srcset=&quot;/blog/static/abc0fb4d58b738f63ae37a9d59deff25/9ec3c/fenix.png 200w,
/blog/static/abc0fb4d58b738f63ae37a9d59deff25/c7805/fenix.png 400w,
/blog/static/abc0fb4d58b738f63ae37a9d59deff25/2cf63/fenix.png 550w&quot;
        sizes=&quot;(max-width: 550px) 100vw, 550px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This is not The Greatest Website in the World, no. This is just a tribute.&lt;/p&gt;
&lt;p&gt;I was approached by &lt;a href=&quot;https://soundcloud.com/tenaciousd&quot;&gt;Tenacious D&lt;/a&gt; a week ago with an incredible quest: raising a phallus shaped phoenix onto the internet (oh &lt;em&gt;and&lt;/em&gt; the first clip of audio from their new record.) How could I raise this majestic beast with the power of SoundCloud’s legendary API? The answer was simple: Have the sound itself &lt;strong&gt;rize&lt;/strong&gt; the phoenix from its ashes.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;The Journey Begins&lt;/h3&gt;
&lt;p&gt;I started my journey by creating a Photoshop layered version of the &lt;em&gt;then&lt;/em&gt; flat painting. Using the ever faithful polygonal lasso tool, I sliced out all the typography. Next I reached for my eraser tool and carefully removed the background around all the fenixs’ &lt;em&gt;curvatures&lt;/em&gt;. Finally, using the sorcery power of the stamp and patch tools, I was able to create backgrounds where they did not exist. &lt;strong&gt;Black magic?&lt;/strong&gt; Yes sir.&lt;/p&gt;
&lt;p&gt;With the fenix now in ashes (layers), I quickly jumped into my Textmate editor in an effort to raise him. In addition to jQuery, I included the great knight, Johannes Wagener’s, powerful SoundCloud &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;Javascript SDK&lt;/a&gt; which now includes the ability to stream sound. Incredible! I also chose to quicken my pursuits by relying on the genius of such great libraries as &lt;a href=&quot;http://learnboost.github.com/stylus/&quot;&gt;Stylus&lt;/a&gt;, &lt;a href=&quot;http://visionmedia.github.com/nib/&quot;&gt;Nib&lt;/a&gt;, and &lt;a href=&quot;http://jade-lang.com/&quot;&gt;Jade&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each fenix layer was placed into the painting frame as an absolutely positioned DIV. I was able to initially hide each of these layers out of view, because this was no ordinary frame - it had the magical CSS property of overflow equals hidden.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;The Fenix Rizes&lt;/h3&gt;
&lt;p&gt;I then called on the power of the Javascript &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;SDK&lt;/a&gt;, uttering:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/tracks/35303281&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a second, my website was filled with the glorious sounds of Tenacious D, but if I was going to control it I needed &lt;a href=&quot;http://www.schillmania.com/projects/soundmanager2/&quot;&gt;SoundManager2’s&lt;/a&gt; aide.&lt;/p&gt;
&lt;p&gt;Using the &lt;em&gt;whileplaying&lt;/em&gt; function of SoundManager2, I created a &lt;em&gt;percent played&lt;/em&gt; property that I would reference to raise each part of the fenix perfectly in sync with the sounds of the D. However, the wings were out of control, falling each time I attempted to revive the bird.&lt;/p&gt;
&lt;p&gt;All hope was lost, until I remembered SoundManager2’s ability to use the &lt;em&gt;peak data&lt;/em&gt; of both the left and right channel of the incoming sound. I applied the fix as quickly as possible and soon the wings were glowing as if Jables were singing right through them.&lt;/p&gt;
&lt;p&gt;The Fenix had Rizen.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;The Journey Ends&lt;/h3&gt;
&lt;p&gt;In an effort to let the kingdom know what had transpired I used another great feature of our Javascript &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;SDK&lt;/a&gt;: Favoriting. With as little as three lines of coded runes, users were able to favorite the track on SoundCloud without ever having to leave the website.&lt;/p&gt;
&lt;p&gt;As so concludes my tale. If you yield a modern browser, you can rize the fenix for yourself at &lt;a href=&quot;http://rizeofthefenix.com/&quot;&gt;rizeofthefenix.com&lt;/a&gt;. If it’s not rizing quite right you can journey to SoundCloud to listen to the &lt;a href=&quot;https://soundcloud.com/tenaciousd/rize&quot;&gt;track&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;Until next time. Go with Courage and the SoundCloud API.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[oEmbed support for the new HTML5 widget]]></title><description><![CDATA[Today we’ve added support for our new HTML5 widget to the oEmbed endpoint. Aside from a brand new user interface with lots of improvements, the HTML5 widget doesn’t require Adobe Flash anymore. That means the widget will load much faster and finally work well on most mobile platforms, including Android & iOS. And we won’t stop here. We’ll continue to improve the new widget in the coming months.]]></description><link>https://developers.soundcloud.com/blog/oembed-support-for-the-html5-widget</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/oembed-support-for-the-html5-widget</guid><pubDate>Mon, 09 Jan 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today we’ve added support for our &lt;a href=&quot;https://blog.soundcloud.com/2011/11/03/html5/&quot;&gt;new HTML5 widget&lt;/a&gt; to the oEmbed endpoint.&lt;/p&gt;
&lt;p&gt;Aside from a brand new user interface with lots of improvements, the HTML5 widget doesn’t require Adobe Flash anymore. That means the widget will load much faster and finally work well on most mobile platforms, including Android &amp;#x26; iOS.&lt;/p&gt;
&lt;p&gt;And we won’t stop here. We’ll continue to improve the new widget in the coming months.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;To retrieve the embed code for a HTML5 widget just pass the &lt;strong&gt;iframe=true&lt;/strong&gt; parameter. For example a call to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://soundcloud.com/oembed?iframe=true&amp;amp;url=https://soundcloud.com/erasedtapes/olafur-arnalds-poland&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;will return this:&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;166&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; src=&quot;https://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F32431542&amp;show_artwork=true&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;We are planning to make the HTML5 widget the default for oEmbed by the end of January. So if you prefer to stick to the old widget, please update your oEmbed calls to include the &lt;strong&gt;iframe=false&lt;/strong&gt; parameter.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Music Hack Day London 2011]]></title><description><![CDATA[Last month we attended Music Hack Day London 2011. A special one, Music Hack Day was born in London 3 years ago and we were happy to attend a hack day again in it’s homeland. Picture For those who do not know of Music Hack Day, it’s a great weekend-long event aimed at music and sound hackers. Anyone can attend the event for free, and their goal is to conceptualize and develop innovative hacks of either software or hardware.]]></description><link>https://developers.soundcloud.com/blog/music-hack-day-london-2011</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/music-hack-day-london-2011</guid><pubDate>Tue, 03 Jan 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last month we attended Music Hack Day London 2011. A special one, Music Hack Day was born in London 3 years ago and we were happy to attend a hack day again in it’s homeland.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://farm8.staticflickr.com/7164/6449320395_c39bd50b92_d.jpg&quot; alt=&quot;Picture&quot;&gt;&lt;/p&gt;
&lt;p&gt;For those who do not know of Music Hack Day, it’s a great weekend-long event aimed at music and sound hackers. Anyone can attend the event for free, and their goal is to conceptualize and develop innovative hacks of either software or hardware.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;a href=&quot;http://davehaynes.me/&quot;&gt;Dave Haynes&lt;/a&gt; and &lt;a href=&quot;http://flavors.me/martyndavies&quot;&gt;Martyn Davies&lt;/a&gt; raised the bar again, by organizing a hack day right in the center of east London, &lt;a href=&quot;http://www.barbican.org.uk/&quot;&gt;the Barbican&lt;/a&gt;.
Having gained so much experience in organizing hack days, it was clear from the start that this was going to be good. The Barbican had made three rooms and a cinema hall available to us, plus a central area to serve coffee, drinks and of course pizza.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://farm8.staticflickr.com/7165/6449374135_33dcc56139_d.jpg&quot; alt=&quot;Picture&quot;&gt;
&lt;img src=&quot;http://farm8.staticflickr.com/7167/6449370055_e981750e37_d.jpg&quot; alt=&quot;Picture&quot;&gt;&lt;/p&gt;
&lt;p&gt;It was great seeing all those excited faces coming in, getting ready to hack on their ideas. There were lots of different hackers attending, from hardware to clothing, to musical instruments, from web to applications, some forming cross teams to combine their skills.&lt;/p&gt;
&lt;p&gt;Although Spotify had a lot of interest in their newly launch &lt;a href=&quot;http://developer.spotify.com/en/spotify-apps-api/overview/&quot;&gt;Apps API&lt;/a&gt;,
it seemed that this hack day was dominated by interactive hacks, where the audience could participate or needed to contribute. During the presentations there were quite some hacks where people could ‘tune in’ their phones or laptops.&lt;/p&gt;
&lt;p&gt;While the overall level of the presented hacks was amazing, I can only highlight a few here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=The_Inhuman_Microphone&quot;&gt;The Inhuman Microphone&lt;/a&gt; - A circumvention for the “No megaphones” rule. You shout a message in your phone to upload it to the service, where it will send it synchronized to all connected phones and laptops. The only thing you need to do is open a web page.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Notorious_Siri&quot;&gt;Notorious Siri&lt;/a&gt; - Apple’s iPhone 4S voice recognition service called ‘Siri’ is being fooled into singing rap songs. Check out the video:&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 45%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;http://player.vimeo.com/video/33402886?color=B185EA&quot; frameborder=&quot;0&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Waveformer&quot;&gt;Waveformer&lt;/a&gt; - An interface to create high precision audio waveforms by outputting JSON data for every point.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://scotd.herokuapp.com/&quot;&gt;SCOTD Showcase&lt;/a&gt; - A gallery to show all the SoundClouders of the Day, synced from a Google Spreadsheet.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://owloctave.com&quot;&gt;Owloctave.com&lt;/a&gt; - An octave of owls, the hoots are streamed from SoundCloud. HTML5 audio, works on iPad. Tap or click on an owl to see where it lives and hear it’s hoots.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://CloudChamber.co&quot;&gt;CloudChamber.co&lt;/a&gt; - A service to reverbize your sounds using SoundCloud and the reverb chamber at the National Physical Laboratory UK. You submit a SoundCloud track and it will play in a chamber designed for perfect reverb, get recorded and send back to you when it’s done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a look at the photos made by &lt;a href=&quot;https://twitter.com/thomasbonte&quot;&gt;Thomas Bonte&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 90%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;object style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/object&gt; &lt;/div&gt; &lt;param name=&quot;flashvars&quot; value=&quot;offsite=true&amp;amp;lang=en-us&amp;amp;page_show_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157628266066707%2Fshow%2F&amp;amp;page_show_back_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157628266066707%2F&amp;amp;set_id=72157628266066707&amp;amp;jump_to=&quot;&gt; &lt;param name=&quot;movie&quot; value=&quot;http://www.flickr.com/apps/slideshow/show.swf?v=109615&quot;&gt; &lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;embed type=&quot;application/x-shockwave-flash&quot; src=&quot;http://www.flickr.com/apps/slideshow/show.swf?v=109615&quot; allowfullscreen=&quot;true&quot; flashvars=&quot;offsite=true&amp;amp;lang=en-us&amp;amp;page_show_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157628266066707%2Fshow%2F&amp;amp;page_show_back_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157628266066707%2F&amp;amp;set_id=72157628266066707&amp;amp;jump_to=&quot; width=&quot;100%&quot; height=&quot;450&quot;&gt;&lt;/object&gt;&lt;/p&gt;
&lt;p&gt;That’s a wrap! Want to attend the next Music Hack Day?
Keep an eye on the website for upcoming events in your area: &lt;a href=&quot;http://musichackday.org/&quot;&gt;http://musichackday.org/&lt;/a&gt;
The next ones are happening in &lt;a href=&quot;http://cannes.musichackday.org/2012/&quot;&gt;Cannes&lt;/a&gt; and &lt;a href=&quot;http://sf.musichackday.org/2012/&quot;&gt;San Francisco&lt;/a&gt; early this year.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Stop! Hacker Time.]]></title><description><![CDATA[At SoundCloud we like to invent new ideas. But we’re not adverse to implementing really great tried and tested ideas like the 20% time…]]></description><link>https://developers.soundcloud.com/blog/stop-hacker-time</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/stop-hacker-time</guid><pubDate>Fri, 09 Dec 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At SoundCloud we like to invent new ideas. But we’re not adverse to implementing really great tried and tested ideas like the 20% time concept made famous by Google.&lt;/p&gt;
&lt;p&gt;We’re calling it Hacker Time. We’re still very much in start-up mode so we’re keen to nurture the spirit of hacking. We’ve been testing out Hacker Time for a few weeks now and we’re excited about its potential, from industry-changing initiatives like &lt;a href=&quot;http://areweplayingyet.org/&quot; title=&quot;Are we playing yet&quot;&gt;“Are we playing yet”&lt;/a&gt; to unusual passion projects like the &lt;a href=&quot;http://owloctave.com/&quot; title=&quot;Owl Octave&quot;&gt;“Owl Octave”&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why we’re doing it**&lt;/h2&gt;
&lt;p&gt;When I arrived at SoundCloud I gathered all the engineers and developers in a room and asked them what they wanted more of. This was one of the top requests:
&lt;img src=&quot;https://backstage.soundcloud.com/wp-content/uploads/2011/12/Retrospective-505x377.jpg&quot; title=&quot;Retrospective Results&quot;&gt;&lt;/p&gt;
&lt;p&gt;Like every other fast-paced tech company out there, the everyday demands of development leave little time for pet projects or invention outside of the roadmap. But we shouldn’t let great ideas slip away: it’s wasteful of talent and it’s frustrating for developers themselves. Hacker Time is an attempt to keep both the ideas and the employees focused on making the product even better.&lt;/p&gt;
&lt;p&gt;I don’t think there’s a single developer at SoundCloud who isn’t a sound creator. Or at least they quickly become one once they’ve joined. We’re a company of electronic music producers, acoustic musicians, field recordists and social sound fanatics. Of course we’re developing the product for 9 million people, but we’re also developing something we want to use ourselves.&lt;/p&gt;
&lt;p&gt;Ironically we’re not trying to invent anything new by initiating Hacker Time. Aside from Google, we’ve been watching the Atlassian experiment and are taking a similar approach; we’ll start with a simple set of rules and adapt to what works best for us. Like Atlassian, we’ll be blogging about our experiences too.&lt;/p&gt;
&lt;h2&gt;Which projects are engineers allowed to work on?&lt;/h2&gt;
&lt;p&gt;We’ve compiled a list which is not meant to be restrictive but rather should be a guideline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pet features/improvements that never made it onto the roadmap&lt;/li&gt;
&lt;li&gt;Apps based on the Soundcloud API&lt;/li&gt;
&lt;li&gt;Hack Days&lt;/li&gt;
&lt;li&gt;Anything for SoundCloudLabs.com&lt;/li&gt;
&lt;li&gt;“this always annoyed me” bug-fixes or architectural improvements&lt;/li&gt;
&lt;li&gt;Integration of some technology-du-jour with Soundcloud&lt;/li&gt;
&lt;li&gt;Contributing to OSS used at Soundcloud&lt;/li&gt;
&lt;li&gt;Other cool projects using SoundCloud&lt;/li&gt;
&lt;li&gt;Conferences&lt;/li&gt;
&lt;li&gt;Writing Blog Posts, Technical Articles&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How will time be allocated?&lt;/h2&gt;
&lt;p&gt;The big decision is how to allocate time to these projects. We don’t want to cannibalize valuable product development time but we do want to give people as much freedom as possible,&lt;/p&gt;
&lt;p&gt;There are lots of approaches out there – from reserving one day a week, accumulating
time to set aside whole weeks or handling that time like vacation. We’ve decided
to put the decision-making in the hands of each team, allowing them to allocate
Hacker Time according to each team’s workstyle. It’s an experiment, and something
we’ll be reviewing in the early stages.&lt;/p&gt;
&lt;h2&gt;Demo it!&lt;/h2&gt;
&lt;p&gt;We’re pretty disciplined about demoing work to the whole company. Hacker Time projects will be included in the demos (which happen every 2 weeks at SoundCloud), giving developers a chance to showcase their projects and sparking interest in their hacks from everyone in the organization.&lt;/p&gt;
&lt;p&gt;Watch this blog for further reports!&lt;/p&gt;
&lt;h2&gt;Many thanks to&lt;/h2&gt;
&lt;p&gt;Atlassian for blogging about their experiences&lt;/p&gt;
&lt;p&gt;Simon Stewart from Google&lt;/p&gt;
&lt;p&gt;Jim Webber from Neo4J&lt;/p&gt;
&lt;p&gt;Jan Lehnhardt from Couchbase&lt;/p&gt;
&lt;p&gt;Stefan Roock from it-agile&lt;/p&gt;
&lt;p&gt;for sharing their experiences with us.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Front-end JavaScript bug tracking]]></title><description><![CDATA[Proper and effective error tracking is a common issue for front-end JavaScript code compared to back-end environments. We felt this pain as…]]></description><link>https://developers.soundcloud.com/blog/front-end-javascript-bug-tracking</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/front-end-javascript-bug-tracking</guid><pubDate>Mon, 21 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Proper and effective error tracking is a common issue for front-end JavaScript code compared to back-end environments.&lt;/p&gt;
&lt;p&gt;We felt this pain as well and experimented with different solutions over the past months on the&lt;a href=&quot;https://m.soundcloud.com/&quot;&gt; SoundCloud Mobile site&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Analytics&lt;/h3&gt;
&lt;p&gt;The first approach we had was to track errors with&lt;a href=&quot;http://www.google.com/analytics/&quot;&gt; Google Analytics&lt;/a&gt;. Their library permits to fire custom events and whenever an ajax error would occur, we would log it.&lt;/p&gt;
&lt;p&gt;The biggest benefit of this tool is to monitor the stability of the site and its evolution in longer periods as you can easily go back a few weeks or months to see which events were triggered. Also, it is easy to implement – almost a one-liner!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9cbc68dfac58d901f12708b3843f7601/8939e/google-analytics-report.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.603960396039604%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABTUlEQVQoz12Q2XbDIAxE/f+f2DatEydewZjVNtiZSmRp0od7kGA0SCr26xUPQFwJESJqu2LddtAl9n0n/nSZR/7vrcDdiAs3ejyogA/p8cXnGJDupm9cX+P97b4QPqJxKzo6z2ZBYxdMS8q0FJ/0jIsOqJRDTe+1man7BZ27aVuqZR3Hg19RNF2Pn+MR5emMqu4g5AgpJYQQ8M5CGwOlDSZjn4hRoZMKUk0YSMsn30mimJTC4fsH5bHCMAhME4mGIeOcQwgBkszLsoQxGqw/HU/ohcKlblDXNYzWOFdVrinmZYGcHIbJI8aElBK2bcvEGDP8Sd/36GiaZV3zJzORUsRK+UIeXGetQ2GtRdlIfJ4FRuOR4k3EPAwVddW2be6Gczbk7tlknmd4H3I88sjeezRSo+qpC2WfJq8Y2iPvlEfinE1CuJlwd5xzrGnXv7agaGq2wFGkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;google analytics report&quot;
        title=&quot;google analytics report&quot;
        src=&quot;/blog/static/9cbc68dfac58d901f12708b3843f7601/8939e/google-analytics-report.png&quot;
        srcset=&quot;/blog/static/9cbc68dfac58d901f12708b3843f7601/9ec3c/google-analytics-report.png 200w,
/blog/static/9cbc68dfac58d901f12708b3843f7601/c7805/google-analytics-report.png 400w,
/blog/static/9cbc68dfac58d901f12708b3843f7601/8939e/google-analytics-report.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The drawback, at least for Google Analytics, is that this tool is not meant to track bugs. There is no way to add custom data to these events to get more insight about why and how an error happened, it also doesn’t work in real-time, and you obviously want that when you debug.&lt;/p&gt;
&lt;p&gt;So we kept Analytics in place for a long-term view, but took a look at other options for real-time and in-depth tracking.&lt;/p&gt;
&lt;h3&gt;Airbrake&lt;/h3&gt;
&lt;p&gt;In our pursuit of getting more insight, we decided to take a look at &lt;a href=&quot;http://airbrake.io&quot;&gt;Airbrake&lt;/a&gt; because we were already using it to track back-end errors on our main site.&lt;/p&gt;
&lt;p&gt;Our mobile site runs on Node.js, the first thing we did was to integrate an existing &lt;a href=&quot;https://github.com/felixge/node-airbrake&quot;&gt;plugin&lt;/a&gt; for it to handle error tracking on the back-end as well.&lt;/p&gt;
&lt;p&gt;Looking a little further we found a &lt;a href=&quot;http://www.airbrakeapp.com/javascripts/notifier.js&quot;&gt;front-end notifier&lt;/a&gt;, which would catch errors that would fire on window.onerror, but there was no way to report any custom errors.&lt;/p&gt;
&lt;p&gt;We decided to take a day to hack this on our own since their API is public and easy to implement.&lt;/p&gt;
&lt;p&gt;The benefits of Airbrake were instant. We could see what triggered which error, how, why, in which context, which browser, etc… in real-time!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/38c24f5d0a997a90e334c014ae8479e5/8939e/airbrake-report.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.46534653465347%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAAAwElEQVQoz5VRWw7DIAzj/ves1FIeaxnhXY+grX9UWiQrRAq2k4haKzhiTFDaYFca+8gG1tqOF0yvc864rguttZFnENzAoE5oXcBJBQdlvHxGbRd+wT0s/gTuESGEoe69hztP5BQRyCMGQkrpL5RSINZ1HVaZeNs2aK1xHCece3fV7n7iZAYROxE/mJ1dhm/NMdvTI6ELZXxmwmVZoJSClBLGmEHOIE8goluwc86PUmq7l77LfYxsje0ju+khnq78Acg6JJzzSWpsAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;airbrake report&quot;
        title=&quot;airbrake report&quot;
        src=&quot;/blog/static/38c24f5d0a997a90e334c014ae8479e5/8939e/airbrake-report.png&quot;
        srcset=&quot;/blog/static/38c24f5d0a997a90e334c014ae8479e5/9ec3c/airbrake-report.png 200w,
/blog/static/38c24f5d0a997a90e334c014ae8479e5/c7805/airbrake-report.png 400w,
/blog/static/38c24f5d0a997a90e334c014ae8479e5/8939e/airbrake-report.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6adc22b6d275182f0745fe733a9b3d36/8939e/airbrake-report-cont.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.84158415841585%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAAArklEQVQoz5WR2wqFIBRE/f8vDe9aZkk1Z49g0MOBEoZ5UNeMbhWChzUGIQTs+47zPHFd1+1fpbzzMNpgXVeUUrq4cRxHh36VYjPnXBdhKWVwjcQ3TZ9ALw3lydQyz3DWwUtIzhlZ4GyeU4KXc0mcijHeTvE1A6wIstZiWQq2bcM0TdBa96bjG+i89E+PhkxmChM4lFlaEkxQrbXD6K+Hwr9jo9ZaBxEwFg8MfzuUHzAtI5dhUGXiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;airbrake report cont&quot;
        title=&quot;airbrake report cont&quot;
        src=&quot;/blog/static/6adc22b6d275182f0745fe733a9b3d36/8939e/airbrake-report-cont.png&quot;
        srcset=&quot;/blog/static/6adc22b6d275182f0745fe733a9b3d36/9ec3c/airbrake-report-cont.png 200w,
/blog/static/6adc22b6d275182f0745fe733a9b3d36/c7805/airbrake-report-cont.png 400w,
/blog/static/6adc22b6d275182f0745fe733a9b3d36/8939e/airbrake-report-cont.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It also counts errors, which can help you prioritize and include fixes in your roadmap.&lt;/p&gt;
&lt;p&gt;However, the lack of filtering, grouping and custom sorting made it difficult to work with. There was also no sense of time or progress, as everything just gets dumped into a single list ordered by time. We needed something a little better than that.&lt;/p&gt;
&lt;h3&gt;BugSense&lt;/h3&gt;
&lt;p&gt;That’s when our Android team showed us their &lt;a href=&quot;http://www.bugsense.com/&quot;&gt;BugSense&lt;/a&gt; implementation. BugSense seemed to address all of these issues we had with Airbrake: grouping is more effective, searching and filtering is possible, charts of errors are drawn as well.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/9591325255d4ef940a2fc6c20f1793fa/8939e/bugsense-report.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.68316831683168%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABW0lEQVQoz32O3UrDQBCF82Zqm1b6Pl6Ib1DfoILoRZ9C8EJBpSJF+rtttIlJdnd2Z/PTJC21VXHTWghihY+zZ4dzhjFqNbN6aJbMkqZcKZvVilk111okD+wf7J0cH11enDfOGs1ms35aN27b1lPXssbjISG27bi+53oF3Fwdx7FGYzKeXD0MWu3+iAx7vT4hxLh5Zh0LlJRSyACDXcRB6FO8brPBq1BCcgo6bySBCqVEoVAiip1IgYHEJERFqU4qHZZoMArAxT+1IsCE0x8qDPJ1IA3OhH6UVLvAohdoPbb4m0snjp4b+s99Bhq6xd/qj+GCcu1zdX1yfzfpdO1uLz97fYCQDAQDWKtgvKAAukP5xnC9y6PgU+5R3TIcCi8u8CgNk7/BKHYpD6cpqMADEWWzKM1kFHN9NleRzdDzWJhk84+v2erzF+n7SsbTZLGM5wuVZJvhdLHEZP4N3R/+xzN1DIMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bugsense report&quot;
        title=&quot;bugsense report&quot;
        src=&quot;/blog/static/9591325255d4ef940a2fc6c20f1793fa/8939e/bugsense-report.png&quot;
        srcset=&quot;/blog/static/9591325255d4ef940a2fc6c20f1793fa/9ec3c/bugsense-report.png 200w,
/blog/static/9591325255d4ef940a2fc6c20f1793fa/c7805/bugsense-report.png 400w,
/blog/static/9591325255d4ef940a2fc6c20f1793fa/8939e/bugsense-report.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There is one more benefit over Airbrake… JSON. No need to convert objects to XML strings anymore!&lt;/p&gt;
&lt;p&gt;If you are interested in our BugSense notifier you can find the &lt;a href=&quot;https://github.com/soundcloud/Bugsense-js&quot;&gt;source on github&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;There is still a lot of work needed to make front-end JS debugging as easy as it is for regular back-end environments. For example, stack traces today aren’t that useful, because of anonymous objects and minified code, but hopefully browser vendors will tackle these issues soon. Maybe &lt;a href=&quot;http://www.infoq.com/news/2011/08/debug-languages-on-javascript-vm&quot;&gt;Source Maps&lt;/a&gt; could be the first milestone in this quest.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we will continue to use a combination of these tools because of the different strengths outlined above, but there are also other tools we didn’t try out yet like &lt;a href=&quot;http://www.getexceptional.com/&quot;&gt;getexceptional&lt;/a&gt; or &lt;a href=&quot;http://errorception.com/&quot;&gt;errorception&lt;/a&gt;. If you have tried these, or if you have any suggestion on this subject we’d like to get your feedback in the comments below.&lt;/p&gt;
&lt;p&gt;Happy debugging!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Strategy for OmniAuth 1.0]]></title><description><![CDATA[OmniAuth is a great little Rack library that standardizes multi-provider authentication for web applications. As OmniAuth says, “it was created to be powerful, flexible, and do as little as possible.” I use it constantly on hacks that mash up multiple API providers. You can now add SoundCloud to the growing list of strategies OmniAuth 1.0 supports.]]></description><link>https://developers.soundcloud.com/blog/omniauth-soundcloud</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/omniauth-soundcloud</guid><pubDate>Fri, 18 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 522px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6f3da632ced4a793ddcee874c3eda1a0/538d2/omniauth.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.544061302681996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABJUlEQVQY02NggABGKM1gaevIBGMbGJoyGxiYMukbmDCB2AxIAMgHipkwAjGUbwLRJ6ei5uwRGHImIDLmmL6ZhRVY0shMEmgIFwMaABoCNMCUTd/QBMVwfaA4WN7AlIHBPSDkUU559f/Cmob/7oGhJ6Wl5QwNjczagAZWA20NgmBTZyDfC0iHAHExEDcD+dJA2gGIm4C4G2ioNdj04MSMT7PXbv+/cPPe/xGpOVdFhEX8jU0s64EaJgMVHQLiVUAN+4C2LwDSO4H0ZiC9C2jRaqBL1wPlQfzlQNwHNlBBXSs/PCX7a3hK1kdlLd1kqNccgQbqADVJAWlZAwMTLaBBEkBsAhTTBMpLgNQAxfWAtDpQXBUoDnIxPC4UQcEJYqw6eIGBEgAAGV1URiUOstwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;OmniAuth&quot;
        title=&quot;OmniAuth&quot;
        src=&quot;/blog/static/6f3da632ced4a793ddcee874c3eda1a0/538d2/omniauth.png&quot;
        srcset=&quot;/blog/static/6f3da632ced4a793ddcee874c3eda1a0/9ec3c/omniauth.png 200w,
/blog/static/6f3da632ced4a793ddcee874c3eda1a0/c7805/omniauth.png 400w,
/blog/static/6f3da632ced4a793ddcee874c3eda1a0/538d2/omniauth.png 522w&quot;
        sizes=&quot;(max-width: 522px) 100vw, 522px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/intridea/omniauth&quot;&gt;OmniAuth&lt;/a&gt; is a great little Rack library that standardizes multi-provider authentication for web applications. As OmniAuth says, “it was created to be powerful, flexible, and do as little as possible.” I use it constantly on hacks that mash up multiple API providers.&lt;/p&gt;
&lt;p&gt;You can now add &lt;a href=&quot;http://git.io/sc-omniauth&quot;&gt;SoundCloud&lt;/a&gt; to the &lt;a href=&quot;https://github.com/intridea/omniauth/wiki/List-of-Strategies&quot;&gt;growing list&lt;/a&gt; of strategies OmniAuth 1.0 supports.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;Installing&lt;/h3&gt;
&lt;p&gt;Add to your &lt;strong&gt;Gemfile&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &lt;span class=&quot;token string&quot;&gt;&apos;omniauth-soundcloud&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~&gt; 1.0.0&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then &lt;strong&gt;bundle install&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;Basic Usage&lt;/h3&gt;
&lt;p&gt;Add the following piece of Rack middleware.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;use &lt;span class=&quot;token constant&quot;&gt;OmniAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    provider &lt;span class=&quot;token string&quot;&gt;&quot;soundcloud&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SOUNDCLOUD_CLIENT_ID&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SOUNDCLOUD_SECRET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Connect & Record with the new JavaScript SDK]]></title><description><![CDATA[Today we’re excited to announce a big update to our JavaScript SDK.
Aside from improving the existing functionality like authentication, API…]]></description><link>https://developers.soundcloud.com/blog/javascript-sdk</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/javascript-sdk</guid><pubDate>Mon, 14 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 519px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/6c7832499ba160d91a482087ca6c54f3/96300/record-example.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.240847784200387%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsSAAALEgHS3X78AAAA1UlEQVQY03WOTU7DMBCFe82KS1R0gYTYV6xY9BScoAfgGrSy04ak9oxT+beuPQk2AcSGt3iLp/fNm8VUNI7FUnDmbXdDOSdf2SSFYIy1H3LQ5K9jSmn6o0U1otKE7ROslvL5kUaa8Xoxpb7vCnxgkvG+61qntQcZEJw233Ad2Tyo9R2+3BPlXziEIIS8DEqbiBcC5a3RChGEvMVYYco55uwAht2rbQ8xU/55ryw3TcM4O57O/AioTGG899Y6IqrwNQTOmURVHjgD7t/3xtgZdq6Wpn/0CafUGZHyVa+QAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Connect and Record&quot;
        title=&quot;Connect and Record&quot;
        src=&quot;/blog/static/6c7832499ba160d91a482087ca6c54f3/96300/record-example.png&quot;
        srcset=&quot;/blog/static/6c7832499ba160d91a482087ca6c54f3/9ec3c/record-example.png 200w,
/blog/static/6c7832499ba160d91a482087ca6c54f3/c7805/record-example.png 400w,
/blog/static/6c7832499ba160d91a482087ca6c54f3/96300/record-example.png 519w&quot;
        sizes=&quot;(max-width: 519px) 100vw, 519px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Today we’re excited to announce a big update to our JavaScript SDK.
Aside from improving the existing functionality like authentication, API read requests and oEmbed, we’ve added some sweet new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API write support:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create comments, follow users, like sounds and update user profiles&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audio Streaming:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Build your own player powered by SoundCloud&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audio recording and uploading:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let your users record and upload sounds to SoundCloud from your website.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these features can be added to your website with only a few lines of JavaScript saving you the pain
of dealing with tricky things like cross domain communication, dependency loading and cross browser workarounds.&lt;/p&gt;
&lt;p&gt;Want to try it out yourself? Take a look at the &lt;a href=&quot;https://connect.soundcloud.com/examples/basic.html&quot;&gt;examples&lt;/a&gt; and check out the &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot;&gt;docs&lt;/a&gt; to get started.&lt;/p&gt;
&lt;p&gt;We’re excited to see what apps you can build on top of this!
And let us know your thoughts on how we can continue to improve our developer resources.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Competition App - Host a contest, powered by SoundCloud]]></title><description><![CDATA[Competitions are a great way to get your fans involved around a particular theme or release. They encourage original content creation and tend to be extremely viral due to the high level of self-promotion involved with participating. The infrastructure you choose to throw these contests on should be accessible from any device while also providing a nice aesthetic backdrop to apply your branding. Users should be able to submit, vote, and comment no matter where they’re coming from: Web, iPhone, Facebook Tab, etc. Today we’re putting forth an open-source app called Competition which we think is a great solution to the problem.]]></description><link>https://developers.soundcloud.com/blog/competition-app</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/competition-app</guid><pubDate>Mon, 14 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 550px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.63636363636363%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsSAAALEgHS3X78AAACxElEQVQoz1WS2U9TQRTGxwbFB4WgRgKtgQKhpTa0LAlWkaWA2hI1KMr2AOJCIKiIASOQGGJBaUsT/wQiTyqCSwBFEllkUaAL0NICt7d3a3u7Q1keIDjFF/zlm5xJJmfJnA+QyCppRkiLxU4Qbrvd66A9/8u7L6fViqIWkiQpisIwnCCInZ0dYFLKjKp2Q+szo6x5Vd6Kd8qwThmqeGlVySwqGaqSIao2QtWmfSPPyM6O4XJ5AmE8nx9/li9XKAD6sAKprUDqH5hK8qmWuomiy98k58nCzKH8jPH89BWpSCcRGS+l/rwtZbPOBAFwIjj4KIPBAKCyvBzoRDxdhmBenKJOZCG3rkyzj3ffDO+/y2yMOjR5KkgdGzLDC1+ID/uUFNP7okn3qHriSfVkbcXU4/uvmp6DpapSQ0ONsaFmjh+BFUu0WZHGt+LhnsL+vNAVYRQcavleEdVY8+NO8WBVyXStaORqCnI921xZqGxpBjORh3+fBrORQdNHgKlAYmAfU3ckdjdzphMY82GMOc7J2ZiQ+bjQz0lxQ8W5v55GTwhCF3isBXHK64Z6oMlJ/nOBp85MNOQkmZvrlguylkrP9VxLM99IX5Gk6XOTDXmpiFjwXXpx7MM78stH/GuvZ6jfMzosb28HFhSFv+/zevf29vzr64uLehLF3Fbbpt+/EQBG/+7urn5R/76vT29Gnf4N+9q6y78hlysARZIOh2N7exsmwxJjY6N2miYokiBwEsdwLABN0+Pj46MjIy67zQZXjGN2q02pVAIHTcPVO10umLy5uQnvVpsNCrqBpignSdqtVpfbPTk1OTA4gJhMOo1mSa/HzGhHhxzsHWBrawvHA+6hAkai8H0/4QTucDq1Wk0ClxvBZHK4CXGxcUwWq6ysFKwdwOfzuf/hgYLHDXu6XC74BCtKpRI2O1ooFHA40GC8rq6uv28ay81K0QFxAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Competition&quot;
        title=&quot;Competition&quot;
        src=&quot;/blog/static/0cf3edd86f936113ae3447faaf202966/2cf63/competition.png&quot;
        srcset=&quot;/blog/static/0cf3edd86f936113ae3447faaf202966/9ec3c/competition.png 200w,
/blog/static/0cf3edd86f936113ae3447faaf202966/c7805/competition.png 400w,
/blog/static/0cf3edd86f936113ae3447faaf202966/2cf63/competition.png 550w&quot;
        sizes=&quot;(max-width: 550px) 100vw, 550px&quot;
        loading=&quot;lazy&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Competitions are a great way to get your fans involved around a particular theme or release. They encourage original content creation and tend to be extremely viral due to the high level of self-promotion involved with participating.&lt;/p&gt;
&lt;p&gt;The infrastructure you choose to throw these contests on should be accessible from any device while also providing a nice aesthetic backdrop to apply your branding. Users should be able to submit, vote, and comment no matter where they’re coming from: Web, iPhone, Facebook Tab, etc.&lt;/p&gt;
&lt;p&gt;Today we’re putting forth an &lt;em&gt;open-source&lt;/em&gt; app called &lt;a href=&quot;https://github.com/soundcloud/soundcloud-competition&quot;&gt;Competition&lt;/a&gt; which we think is a great solution to the problem.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;While I explain what makes this app unique, it may help to follow along with any of the live examples here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://contest.mastodonrocks.com&quot;&gt;contest.mastodonrocks.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://brandnewunsigned.mtv.co.uk&quot;&gt;brandnewunsigned.mtv.co.uk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://remix.moby.com&quot;&gt;remix.moby.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ll be discussing the main sections and features: Front Page, Track Page, Submit Page, and Mobile&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;http://contest.mastodonrocks.com/&quot;&gt;Front Page&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This well structured page includes the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Call to action block which gives a brief intro into your competition&lt;/li&gt;
&lt;li&gt;Large banner graphic&lt;/li&gt;
&lt;li&gt;Long form explanation about the competition&lt;/li&gt;
&lt;li&gt;Two columns showing both new tracks and those which are most popular&lt;/li&gt;
&lt;li&gt;Section for prizing&lt;/li&gt;
&lt;li&gt;And finally, a separate section to promote your company or artist&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;http://contest.mastodonrocks.com/tracks/12&quot;&gt;Track Page&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SoundCloud powers all voting, commenting, favoriting, and submitting which allows for a sync between your Competition app and SoundCloud. So when users comment on a track on the app, they’re also contributing a comment to the track on SoundCloud which carries socially. Simply login (or sign-up, we have Facebook for login too) with your SoundCloud account to participate. In addition this page includes about, stats, and sharing.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;http://contest.mastodonrocks.com/submit&quot;&gt;Submit Page&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The submission system works simply enough:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign-up for SoundCloud (if you haven’t already)&lt;/li&gt;
&lt;li&gt;Upload your track to SoundCloud&lt;/li&gt;
&lt;li&gt;Return to the app, login, and select your track from the provided drop-down list&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The song will then be instantly added to the app and the submitter will be encouraged to share it with friends.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;http://vimeo.com/26834553&quot;&gt;Mobile&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Competition is built from the group up to play nicely with mobile devices. Here’s how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The player was built with an HTML5 fallback so it will work on as many devices as possible, such as the iPhone or iPad&lt;/li&gt;
&lt;li&gt;The entire layout is elastic so it resizes auto-magically to the screen size of your device. Try resizing your browser down or opening one of the examples up on your iPhone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This additional layer of accessibility will add tons to the engagement. Allowing creators and voters to participate in your competition from anywhere.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://github.com/soundcloud/soundcloud-competition/wiki/How-to-Deploy&quot;&gt;Deploy Instructions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Competition is an open-source &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; application that must be deployed to a server which supports the RoR framework. We highly recommend &lt;a href=&quot;http://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; as the server of choice because it offers a free base server and makes deploying RoR apps much easier. You should also have a basic understanding of &lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; and a local version of RoR running for development purposes.&lt;/p&gt;
&lt;p&gt;If all that sounds good: check out the basic deploy instructions on &lt;a href=&quot;https://github.com/soundcloud/soundcloud-competition/wiki/How-to-Deploy&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If everything I said above went over your head: &lt;strong&gt;don’t sweat!&lt;/strong&gt; Drop me a line at &lt;a href=&quot;mailto:lee@soundcloud.com&quot;&gt;lee@soundcloud.com&lt;/a&gt; about your competition and we’ll let you know when we launch a turn-key version of Competition.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;Thanks&lt;/h3&gt;
&lt;p&gt;If you end up deploying the app yourself or have a feature request, please send me an email &lt;a href=&quot;mailto:lee@soundcloud.com&quot;&gt;lee@soundcloud.com&lt;/a&gt; or contact me on Twitter &lt;a href=&quot;https://twitter.com/leemartin&quot;&gt;@leemartin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you find any bugs, let me know on the Github &lt;a href=&quot;https://github.com/soundcloud/soundcloud-competition/issues&quot;&gt;issues portal&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Music Hack Day Boston 2011]]></title><description><![CDATA[Last weekend was another successful Music Hack Day Boston jam-packed with cool hacks, and hackers alike. We are always excited to get involved, meet new and interesting people, and of course hack!]]></description><link>https://developers.soundcloud.com/blog/music-hack-day-boston-2011</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/music-hack-day-boston-2011</guid><pubDate>Fri, 11 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last weekend was another successful &lt;a href=&quot;http://boston.musichackday.org/2011/&quot;&gt;Music Hack Day Boston&lt;/a&gt; jam-packed with cool hacks, and hackers alike. We are always excited to get involved, meet new and interesting people, and of course hack!&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;img src=&quot;http://farm7.static.flickr.com/6098/6315685375_04f9b28079_z.jpg&quot; alt=&quot;MHD Boston 2011 // Photo by Thomas Bonte&quot;&gt;
&lt;em&gt;Photo by &lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/&quot; target=&quot;_blank&quot;&gt;Thomas Bonte&lt;/a&gt;, see more &lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/sets/72157627935709883/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For those who do not know of Music Hack Day, it’s a great weekend-long event aimed at music and sound hackers. Anyone can attend the event for free, and their goal is to conceptualize and develop innovative hacks of either software or hardware. Below are just a few of the cool hacks that were demoed last weekend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hacks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Soundcloud_for_Rapportive&quot; target=&quot;_blank&quot;&gt;SoundCloud for Rapportive&lt;/a&gt;: A plugin for Rapportive that finds out what your contacts have streaming on Soundcloud, and displays this info while browsing your e-mail, with the ability to play tracks right in your gmail. &lt;em&gt;Currently in development.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=RECordinates&quot; target=&quot;_blank&quot;&gt;RECordinates&lt;/a&gt;: Listen to sounds from around the world, and add your own directly to the map. Streaming, authentication, recording, and uploading is all handled by our new &lt;a href=&quot;https://developers.soundcloud.com/docs/javascript-sdk&quot; target=&quot;_blank&quot;&gt;JS SDK&lt;/a&gt;. Lots more for this app coming soon, via &lt;a href=&quot;https://twitter.com/chriscaselas&quot; target=&quot;_blank&quot;&gt;yours truly&lt;/a&gt;. Check it out in its early stages &lt;a href=&quot;http://recordinates.herokuapp.com/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;!
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Mustachiness&quot; target=&quot;_blank&quot;&gt;Mustachiness&lt;/a&gt;: A visualization hack just in time for Movember. Using the waveform of a track, this hack creates a mustache based off of several factors specific to each song, such as loudness, hotness, danceability, and more. Check out the &lt;a href=&quot;http://mustachiness.ex.fm/artist/foo%20fighters&quot; target=&quot;_blank&quot;&gt;mustachiness&lt;/a&gt; of your favorite songs!
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are just a few. You can see all 56 hacks at the &lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Boston_2011_Hacks&quot; target=&quot;_blank&quot;&gt;Music Hack Day Wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;object width=&quot;100%&quot; height=&quot;450&quot;&gt; &lt;param name=&quot;flashvars&quot; value=&quot;offsite=true&amp;amp;lang=en-us&amp;amp;page_show_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157627935709883%2Fshow%2F&amp;amp;page_show_back_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157627935709883%2F&amp;amp;set_id=72157627935709883&amp;amp;jump_to=&quot;&gt; &lt;param name=&quot;movie&quot; value=&quot;http://www.flickr.com/apps/slideshow/show.swf?v=109615&quot;&gt; &lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;embed type=&quot;application/x-shockwave-flash&quot; src=&quot;http://www.flickr.com/apps/slideshow/show.swf?v=109615&quot; allowfullscreen=&quot;true&quot; flashvars=&quot;offsite=true&amp;amp;lang=en-us&amp;amp;page_show_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157627935709883%2Fshow%2F&amp;amp;page_show_back_url=%2Fphotos%2Fthomasbonte%2Fsets%2F72157627935709883%2F&amp;amp;set_id=72157627935709883&amp;amp;jump_to=&quot; width=&quot;100%&quot; height=&quot;450&quot;&gt;&lt;/object&gt;&lt;/p&gt;
&lt;p&gt;To find out about upcoming events, check &lt;a href=&quot;http://musichackday.org&quot; target=&quot;_blank&quot;&gt;MusicHackDay.org&lt;/a&gt; and follow &lt;a href=&quot;https://twitter.com/musichackday&quot; target=&quot;_blank&quot;&gt;@musichackday&lt;/a&gt; on Twitter. The next planned Music Hack Day is to be held in &lt;a href=&quot;http://london.musichackday.org/2011/&quot; target=&quot;_blank&quot;&gt;London&lt;/a&gt; on December 3rd &amp;#x26; 4th.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud launches the HTML5 Audio Improvement Initiative]]></title><description><![CDATA[We at SoundCloud want to build the best sound player for the web, and we want to do that using the Open Web standards. While working on the…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-launches-the-html5-audio-improvement-initiative</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-launches-the-html5-audio-improvement-initiative</guid><pubDate>Wed, 09 Nov 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We at SoundCloud want to build the best sound player for the web, and we want to do that using the Open Web standards. While working on the native audio features on our mobile site and new widgets, or even as an experiment on the main site, we have discovered that the &lt;a href=&quot;http://dev.w3.org/html5/spec/Overview.html#media-elements&quot;&gt;HTML5 Audio standard&lt;/a&gt; is not equally well implemented across all modern browsers and some decisions can be made that would benefit the web audio users and web developers alike. Soundcloud launches the “&lt;a href=&quot;http://areweplayingyet.org&quot; title=&quot;Are We Playing Yet&quot;&gt;Are We Playing Yet?&lt;/a&gt;” project, which aims to raise the awareness about the state of HTML5 Audio implementations in the web browsers.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 379px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/45a70551b98044234e736cc9106dd3f7/4c799/are-we-playing-yet.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.35092348284961%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAECBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHThYYhh//EABgQAAIDAAAAAAAAAAAAAAAAAAIDABMg/9oACAEBAAEFAiWJyhWf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGRAAAQUAAAAAAAAAAAAAAAAAAAERICIy/9oACAEBAAY/ArI5iP8A/8QAGxAAAgEFAAAAAAAAAAAAAAAAAAEQESFBYaH/2gAIAQEAAT8htSaDU6JJGYR//9oADAMBAAIAAwAAABDAD//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EAB0QAQACAQUBAAAAAAAAAAAAAAEAIREQMUFxkdH/2gAIAQEAAT8QRBCUytPssFVNr+oY4MWsAVpy7n//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;are we playing yet&quot;
        title=&quot;are we playing yet&quot;
        src=&quot;/blog/static/45a70551b98044234e736cc9106dd3f7/4c799/are-we-playing-yet.jpg&quot;
        srcset=&quot;/blog/static/45a70551b98044234e736cc9106dd3f7/f544b/are-we-playing-yet.jpg 200w,
/blog/static/45a70551b98044234e736cc9106dd3f7/4c799/are-we-playing-yet.jpg 379w&quot;
        sizes=&quot;(max-width: 379px) 100vw, 379px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We have decided to help the parties involved and collect the issues in one place, document them, provide the code and add interactive tests that will show the implementation progress. We understand how the software development works, and that a few iterations are needed until something is fully done. We hope ”&lt;a href=&quot;http://areweplayingyet.org&quot; title=&quot;Are We Playing Yet&quot;&gt;Are We Playing Yet?&lt;/a&gt;” can function as a handy development and quality monitoring tool.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 379px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ac26210c0063863e1b7e411ba9c2cd0c/4c799/areweplayingyet.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 69.65699208443272%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMCBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe21IsD/xAAZEAACAwEAAAAAAAAAAAAAAAAQIQABEUH/2gAIAQEAAQUCc0cpD//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABgQAQEBAQEAAAAAAAAAAAAAAAERADHh/9oACAEBAAE/IelZNL640i1Crjm//9oADAMBAAIAAwAAABCgz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/EFn/xAAbEAEAAwEAAwAAAAAAAAAAAAABABEhMVFhkf/aAAgBAQABPxADYRdVdwsWhXohQ3zH6DHoA7hyOwnHdn//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;areweplayingyet&quot;
        title=&quot;areweplayingyet&quot;
        src=&quot;/blog/static/ac26210c0063863e1b7e411ba9c2cd0c/4c799/areweplayingyet.jpg&quot;
        srcset=&quot;/blog/static/ac26210c0063863e1b7e411ba9c2cd0c/f544b/areweplayingyet.jpg 200w,
/blog/static/ac26210c0063863e1b7e411ba9c2cd0c/4c799/areweplayingyet.jpg 379w&quot;
        sizes=&quot;(max-width: 379px) 100vw, 379px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;“Are We Playing Yet?” was started by SoundCloud but it’s open to all companies and developers who care about the state of HTML5 audio and want to build applications based on this &lt;a href=&quot;http://dev.w3.org/html5/spec/Overview.html#media-elements&quot;&gt;Web standard&lt;/a&gt;. You can get the project source on &lt;a href=&quot;https://github.com/soundcloud/areweplayingyet&quot; title=&quot;Are We Playing Yet repository&quot;&gt;GitHub&lt;/a&gt;, contribute tests and fixes via the pull requests or &lt;a href=&quot;https://github.com/soundcloud/areweplayingyet/issues&quot;&gt;Issue Tracker&lt;/a&gt;, and connect to the people involved via &lt;a href=&quot;https://twitter.com/areweplayingyet&quot; title=&quot;&amp;#x22;Are We Playing Yet?&amp;#x22; Twitter account&quot;&gt;@areweplayingyet&lt;/a&gt; on Twitter.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Velocity Europe Birds of a Feather Meetup]]></title><description><![CDATA[With excitement building for the Velocity Conference in Berlin, we are happy to announce a pre-event meet up on Monday, November 7 at the…]]></description><link>https://developers.soundcloud.com/blog/velocity-europe-birds-of-a-feather-meetup</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/velocity-europe-birds-of-a-feather-meetup</guid><pubDate>Thu, 27 Oct 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With excitement building for the &lt;a href=&quot;http://velocityconf.com/velocityeu/&quot;&gt;Velocity Conference&lt;/a&gt; in Berlin, we are happy to announce a pre-event meet up on Monday, November 7 at the Betahaus in Berlin. The event will be a great opportunity for visitors of the Velocity conference to beat the jet lag with a day of networking and relaxed sessions and talks. To get everyone in the Berlin-state-of-mind, the evening will be capped with a party.&lt;/p&gt;
&lt;p&gt;Berlin has quickly established itself as one of Europe’s main technology hubs. There are a lot of web companies in Berlin which are part of a constantly growing and vibrant local web community. The location for the BoF, &lt;a href=&quot;http://betahaus.de/&quot;&gt;Betahaus&lt;/a&gt;, is a shared-office innovation-inducing place that is used by small and large startups as well as established organisations.&lt;/p&gt;
&lt;p&gt;Participation is free for Velocity Europe attendees. Non-Velocity attendees can pay 50 Euros to take part in the BoF event. The evening party is open to all and does not require registration.&lt;/p&gt;
&lt;p&gt;The pre-event will be organized as an open space or unconference with on-spot planning of the sessions. The event is limited to 150 participants who must register in advance. If the event is not sold out in advance, participants can show up at the door.&lt;/p&gt;
&lt;p&gt;At the Betahaus, we have 2 larger rooms and 4 smaller meeting rooms. We will hold presentations in the larger rooms and hold discussions in the meeting rooms. The meeting rooms for the discussion can hold up to 10 people. Anyone interested in speaking at the event can contact Eliot (eliot(at)soundcloud.com to submit a discussion topic.&lt;/p&gt;
&lt;p&gt;The day will close with an open party . Event participants will enjoy a dinner buffet &amp;#x26; free drinks at the after-event party.&lt;/p&gt;
&lt;p&gt;You can register for the event here: &lt;a href=&quot;http://veubof2011.eventbrite.co.uk/&quot;&gt;http://veubof2011.eventbrite.co.uk/&lt;/a&gt; (free for Velocity attendees / 50 Euros at the door for non-Velocity attendees)&lt;/p&gt;
&lt;p&gt;Outline agenda (subject to change):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;13:00: Arrive at Betahaus&lt;/li&gt;
&lt;li&gt;14:00: Event starts with welcoming remarks and the session planning&lt;/li&gt;
&lt;li&gt;15:15 – 16:15: Sessions 1 &amp;#x26; 2, each 30 minutes&lt;/li&gt;
&lt;li&gt;16:15 – 16:45: Coffee break&lt;/li&gt;
&lt;li&gt;16:45 – 17:45: Sessions 3 &amp;#x26; 4, each 30 minutes&lt;/li&gt;
&lt;li&gt;17:45 – 18:15: Wrap up&lt;/li&gt;
&lt;li&gt;18:30: Dinner buffet for event participants&lt;/li&gt;
&lt;li&gt;19:30: Open party&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Event location:
Betahaus
Prinzessinnenstraße 19-20
10969 Berlin
&lt;a href=&quot;http://maps.google.com/maps/place?cid=1623497807966887921&quot;&gt;Betahaus on Google Maps&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing the iOS Sharing Kit]]></title><description><![CDATA[Not long ago we released a sharing kit for Android.
Today, we’re equally excited to announce a similar sharing kit for iOS. With the sharing kit, you can add SoundCloud sharing to your iPhone and iPad apps with only a few lines of code.
We provide everything you need to let your users share their sounds to SoundCloud,
including authentication, UI and connections to other social networks like Facebook and Twitter.]]></description><link>https://developers.soundcloud.com/blog/introducing-the-ios-sharing-kit</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-the-ios-sharing-kit</guid><pubDate>Tue, 25 Oct 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Not long ago we released a &lt;a href=&quot;/blog/blog/introducing-the-android-sharing-kit&quot;&gt;sharing kit for Android&lt;/a&gt;.
Today, we’re equally excited to announce a similar &lt;a href=&quot;https://github.com/soundcloud/CocoaSoundCloudAPI&quot;&gt;sharing kit for iOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the sharing kit, you can add SoundCloud sharing to your iPhone and iPad apps with only a few lines of code.
We provide everything you need to let your users share their sounds to SoundCloud,
including authentication, UI and connections to other social networks like Facebook and Twitter.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;It only takes a couple of minutes to implement.
Yeah, really! This video shows you how to do it:&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 56.36363636363636%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;div style=&quot;width: 100%; text-align: center&quot;&gt; &lt;iframe src=&quot;http://player.vimeo.com/video/28715664?title=0&amp;amp;byline=0&amp;amp;portrait=0&quot; frameborder=&quot;0&quot; webkitallowfullscreen allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;
&lt;p&gt;As you see the kit takes care of the authentication and also provides a nice sharing screen similar to what we have in our own iPhone
application. The whole project is hosted and documented on our &lt;a href=&quot;https://github.com/soundcloud/CocoaSoundCloudAPI&quot;&gt;Github account&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Take a look and let us know what you think in the comments or our &lt;a href=&quot;https://groups.google.com/forum/#!forum/soundcloudapi&quot;&gt;Google Group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy Hacking!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Signs Apache Corporate Contributor License Agreement]]></title><description><![CDATA[We just signed the corporate contributor license agreement (CCLA). SoundCloud always was big on open source – we nearly exclusively use open…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-signs-apache-corporate-contributor-license-agreement</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-signs-apache-corporate-contributor-license-agreement</guid><pubDate>Fri, 14 Oct 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We just signed the corporate contributor license agreement (CCLA). SoundCloud always was big on open source – we nearly exclusively use open source software in our company and use a lot of Apache projects like Hadoop, Solr, Flume, Zookeeper and Cassandra on our large scale production site.&lt;/p&gt;
&lt;p&gt;As SoundCloud is using a lot of Apache projects and started to contribute to project we decided to sign the CCLA and enable all our developers to contribute to Apache projects even during work time if that project is used by SoundCloud.&lt;/p&gt;
&lt;p&gt;The first project we will commit code to is Flume, we hope there are several more coming.&lt;/p&gt;
&lt;p&gt;Apache, keep up the great work and we will support you wherever possible!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mobile: Unit Testing]]></title><description><![CDATA[When we started the Mobile project early 2011, unit testing JavaScript was one of the goals to tackle on the technical side. The history of…]]></description><link>https://developers.soundcloud.com/blog/mobile-unit-testing</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/mobile-unit-testing</guid><pubDate>Mon, 12 Sep 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we started the Mobile project early 2011, unit testing JavaScript was one of the goals to tackle on the technical side. The history of custom JavaScript code at SoundCloud up until then rarely included unit tests, so providing references and the necessary ground research was important for both the project at hand as well as for other projects at SoundCloud.&lt;/p&gt;
&lt;p&gt;This articles aims to provide an overview of the tools we use, what worked well and what we need to improve.&lt;/p&gt;
&lt;h2&gt;Tools&lt;/h2&gt;
&lt;p&gt;When we started the Mobile project, there were just two developers on the team, &lt;a href=&quot;https://soundcloud.com/matas&quot;&gt;Matas&lt;/a&gt; and &lt;a href=&quot;http://bassistance.de&quot;&gt;Jörn&lt;/a&gt;. With Jörn already maintaining and supporting &lt;a href=&quot;http://docs.jquery.com/QUnit&quot;&gt;QUnit&lt;/a&gt; for three years, this particular choice was an easy one. If you haven’t yet heard of it: Among available unit testing frameworks, QUnit is among the most popular ones. There’s a &lt;a href=&quot;http://msdn.microsoft.com/en-us/scriptjunkie/gg749824.aspx&quot;&gt;comprehensive tutorial over at ScriptJunkie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As we were building an API client in the browser, mocking API requests was really important for us. We didn’t want to depend on the API being available, both to be able to work offline and to not depend on data that changes all the time. At the start of the project, jQuery 1.5 and its ajax extension points like custom transports weren’t available yet, so we went with &lt;a href=&quot;https://github.com/appendto/jquery-mockjax&quot;&gt;mockjax&lt;/a&gt;, a library adding mocking on top of jQuery’s ajax module.&lt;/p&gt;
&lt;p&gt;To run tests in continuous integration systems (at SoundCloud, on Jenkins), we looked at quite a lot of options. Jörn has &lt;a href=&quot;http://jquery.bassistance.de/webtesting/presentation.html#slide4&quot;&gt;some slides that give an overview of that research&lt;/a&gt;. Other teams at SoundCloud use &lt;a href=&quot;http://seleniumhq.org/&quot;&gt;Selenium&lt;/a&gt;, which wasn’t an option for us due to the lack of support for Chrome or Safari (which is still a work in progress). In the end we went with &lt;a href=&quot;http://www.phantomjs.org&quot;&gt;PhantomJS&lt;/a&gt;. PhantomJS is built on top of Qt-WebKit, provides a reasonable browser-like environment and enough API to run our unit tests and report back results.&lt;/p&gt;
&lt;p&gt;We considered using TestSwarm to distribute running of our unit tests to regular desktop browsers as well as mobile devices. The lack of a Jenkins-TestSwarm plugin (&lt;a href=&quot;https://github.com/appendTo/Jenkins-TestSwarm&quot;&gt;now actually available&lt;/a&gt;) as well as tools for managing VMs, browsers, simulators and emulators (or even managing mobile devices) was enough of a hurdle that we skipped this. Until we get this in place, we won’t know how many bugs we could have catched earlier with this additional setup.&lt;/p&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;p&gt;QUnit does a pretty good job. The few small issues we encountered were swiftly fixed upstream. We ended up customizing the &lt;a href=&quot;http://docs.jquery.com/QUnit/module&quot;&gt;module-method&lt;/a&gt; quite heavily, mostly to integrate Mockjax. Overall, Mockjax also did a pretty good job, once we figured out a pattern that worked for us. Here’s a typical module-call for testing Backbone Views and Models that fetch their data from the API:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;/users/183/tracks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/fixtures/forss-tracks.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;/users/183/playlists&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/fixtures/forss-playlists.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;/users/183/favorites&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/fixtures/forss-favorites.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;/users/183/groups&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/fixtures/forss-groups.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We still call the module-method with the module-name as the first argument. The second argument can contain setup- and teardown-properties, just like QUnit expects it. In addition, we pass url-mock pairs, which are passed on to $.mockjax. In addition to those, we define a catch-all to make sure that no test ever ends up calling the actual API. And we have a global timeout for each test to ensure a broken async test never prevents the suite from finishing.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; testTimeout&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function-variable function&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mocks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  QUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setup&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          mocks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/setup|teardown/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mockjax&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/_api&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              proxy&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              responseTime&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mockjax&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/_api&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mockjax&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/_api*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        responseTime&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Mockjax caught unmocked API call for url: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modelType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            message &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;, from component &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modelType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      testTimeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test timeout (5s)&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// could involve multiple stop calls, reset&lt;/span&gt;
        QUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;semaphore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;teardown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testTimeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mockjaxClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mocks &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;amp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;amp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; mocks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;teardown&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mocks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;teardown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The problem with this design was the lack of a &lt;code class=&quot;language-text&quot;&gt;$.mockjaxClear(url)&lt;/code&gt; method – you can’t remove an existing handler or replace it (&lt;code class=&quot;language-text&quot;&gt;mockjaxClear(index)&lt;/code&gt; is supported, but didn’t help us). We needed that to test error conditions, for example, when the API returned a 404 when asking if a particular track was a favorite of a user. In some cases, we could just mix it with other mocks.&lt;/p&gt;
&lt;p&gt;In other cases, we grouped these tests into a separate module-call (with the
same name):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;/users/183/playlists&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    responseStatus&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    responseText&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;servererror&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    responseTime&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that, we did the regular tests in one place, the error conditions in the
other.&lt;/p&gt;
&lt;h2&gt;The Bad&lt;/h2&gt;
&lt;p&gt;An interesting QUnit feature, inspired by Kent Beck’s work on &lt;a href=&quot;http://junitmax.com/&quot;&gt;JUnit MAX&lt;/a&gt;, is its built-in reordering. It basically records the results of one test run in &lt;code class=&quot;language-text&quot;&gt;sessionStorage&lt;/code&gt;, then looks at those results during the next run. If a test failed before, its scheduled to run first. All that happens without changing the order of the result output. If it works, you can get the relevant test results much faster then for regular sequential runs, as its likely that tests that failed before will fail again, while passing tests are a lot less likely to start failing.&lt;/p&gt;
&lt;p&gt;The problem with that reordering for us was that with all the asynchronous tests in our suite, sometimes tests would have side effects on other tests. As long as they ran in a fixed order, those effects weren’t noticeable. Instead of addressing the actual side effects, we ended up disabling the reordering. Its on the pile of chores to still address.&lt;/p&gt;
&lt;p&gt;Overall, the unit tests did a good job, though its not quite clear how much value they actually provided. Most bug reports are about visual issues, sometimes small glitches, often enough device specific issues. As a mobile web developer, Android, or Andy as we started to call it, becomes kind of an IE6. It gets updated only with the OS, the OS isn’t updated, so we’re stuck with this browser that was okay a year ago, but is a real pain today. On Android 2.1, you even have the same issue as on IE6: HTML5 elements like ‘header’ or ‘article’ aren’t styled. At least on IE6, there’s a workaround…&lt;/p&gt;
&lt;p&gt;Anyway, the other category of bugs were reported much less frequently, and unit testing didn’t help there either. We learned that client-side error logging is extremely valuable. Tools like &lt;a href=&quot;http://airbrakeapp.com/&quot;&gt;Airbrake&lt;/a&gt; and &lt;a href=&quot;http://bugsense.com/&quot;&gt;Bugsense&lt;/a&gt; still have a long way to go, but writing a single-page web application without logging of client side errors means you never know about the thousands of errors your users get to see. Expect another post on that topic.&lt;/p&gt;
&lt;h2&gt;The Ugly&lt;/h2&gt;
&lt;p&gt;As long as mockjax did its job, we were happy with it. When it didn’t, we had to look at the source, and we weren’t happy anymore. The whole thing is quite a mess and in dire need of some good refactorings. Still, in terms of features, alternatives like jQuery 1.5 custom transports or &lt;a href=&quot;http://sinonjs.org/&quot;&gt;sinon.js&lt;/a&gt; just aren’t on par, so we stuck with mockjax.&lt;/p&gt;
&lt;p&gt;What we now mostly gave up on is PhantomJS. The Jenkins-job that ran our QUnit tests using PhantomJS is currently disabled, as it kept failing for months. We spent overall several days trying to find the source of the one failing test, giving up at the end. We still don’t know why it was failing, and there were several hurdles that made it difficult to debug:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It failed only on our Jenkins server. Running the tests locally, using the same PhantomJS version, worked fine. The difference was the enviroment, with mostly OSX running on developer machines, but Debian Lenny on the Jenkins box. Sure, that’s a problem, but the point of the tool is to provide a browser-like enviroment, it shouldn’t matter what system its running on.&lt;/li&gt;
&lt;li&gt;We were stuck with PhantomJS 1.1, even after 1.2.x was out for several months. While we could adapt to the completely backwards incompatible API changes from 1.1 to 1.2, we didn’t find any way around PhantomJS just crashing on our testsuite, with no useful output. If you’re interested, you can find the debugging process somewhat &lt;a href=&quot;http://groups.google.com/group/phantomjs/browse_thread/thread/f505feeb51eb08&quot;&gt;documented on this Google Groups thread&lt;/a&gt;. Even debugging with &lt;code class=&quot;language-text&quot;&gt;gdb&lt;/code&gt; proved to be a waste of time. The unhelpfulness of PhantomJS when failing to load a page is stunning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So as nice as PhantomJS is, the combination of not being able to upgrade and not being able to fix the existing build forced us to abandon it. TestSwarm is a lot more interesting now with the existing Jenkins plugin. And with Chrome support upcoming in Selenium, that is an attractive short term solution as well.&lt;/p&gt;
&lt;h2&gt;Epilog&lt;/h2&gt;
&lt;p&gt;As you can see, this story isn’t over yet. It seems to share a common theme with other developer tools, be that editors, bug tracking or testing tools: most of them do their job, but we aren’t satisfied with any of them.&lt;/p&gt;
&lt;p&gt;What are your experiences? What tools would you like to see improved, replaced or invented?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing the Android Sharing Kit]]></title><description><![CDATA[Are you an Android developer and want to integrate SoundCloud in your own app?
With the new Android Sharing Kit this has become very easy.]]></description><link>https://developers.soundcloud.com/blog/introducing-the-android-sharing-kit</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-the-android-sharing-kit</guid><pubDate>Wed, 07 Sep 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Are you an Android developer and want to integrate SoundCloud in your own app?
With the new Android Sharing Kit this has become very easy.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The first music app to make use of this is the
&lt;a href=&quot;https://soundcloud.com/apps/rd3-groovebox&quot;&gt;RD3 Groovebox&lt;/a&gt; by fellow Berlin-based company &lt;a href=&quot;http://www.mikrosonic.com/&quot;&gt;Mikrosonic&lt;/a&gt; - create some
loops on the go and share them on SoundCloud immediately afterwards.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 400px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/0e63600ee724b585ba9430793db4d27f/c7805/soundcloud_rd3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAARlAAAEZQAGA43XUAAACWUlEQVQoz3VSy28SYRD/YGHf7AK7++17gXYfDbZFja0xJiZaE62JiV48+gd48tqLVy5VemprrAQq8joQjJ7khhCJtZawqcaE+PhPHOjFmPjL7GT2m5lv5jffIAsTti4qScJUBaxqlmnKsmzbtq7rpmmqqhqPxyORSDQaJYgoQshQ+VvXvLsb/lrBRprMKlICKzKGT5YhWlEUluNommEYlqIZiqLIvwQOsCwuudjURISxaluGbZv5/LkgCKDm6uoKTcbR/0GSlCCmSIpBuoZvb1zQdUwQMZ7nwLd5/fLrg52D3afl/Wcvd7fLe9v1ym6jul+v7DWqz1+92Lm6tuL5QSqZRJah3NlYMy0HmgaSDMs/eng//Ngb9t59eP8GZNB7Ow0//fx6/Ovb8e/v4x/h6OaVgmZYqVQSAUnfzYmiADVhLKATPBssZhxDXXAMEMcAejKWRI6KsSRBEohlaMhiWRZlMhnf9wVBgGHSFBGPRQVB1DRTVnAylYaJpNKSghVBYOHeOEkmOBIahhSY5ywZ5sTz/MwXjcwqi7y3bOm6YVmWYRiSJGFFymV0eI/HDzY/P7m3smDlFj2e4xC48/k8XAEGcAblem7h4vLSHFDB8zzQEOMHwfr51RvrlxZzOQilaRqd7UOpVJpOp2EYjsfjo6OjySScTCaDwaDf78PhzB4OR6PRl5OT8PS0XC5ns9kZZ0h2HGdra6vb7TYajXq9XqvV2u12q9U6PDysVqtndqVSgd9ms9npdIrFouu6HLQtz18IetY0DcOWzQE80+m0NEd6jn9s0LB5fwBUMZ9DL3u8ywAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud+RD3&quot;
        title=&quot;SoundCloud+RD3&quot;
        src=&quot;/blog/static/0e63600ee724b585ba9430793db4d27f/c7805/soundcloud_rd3.png&quot;
        srcset=&quot;/blog/static/0e63600ee724b585ba9430793db4d27f/9ec3c/soundcloud_rd3.png 200w,
/blog/static/0e63600ee724b585ba9430793db4d27f/c7805/soundcloud_rd3.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Since the integration is based on the standard Android intent model you
only need to add a few lines of code to share sounds from your application -
all the hard work is done by our &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.soundcloud.android&quot;&gt;Android app&lt;/a&gt;. Check out the
&lt;a href=&quot;https://github.com/soundcloud/android-intent-sharing/wiki&quot;&gt;Android Sharing Kit&lt;/a&gt; page for more documentation and code samples.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 128px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/627ca2cefc8e46c624bf881c0041f3f2/6b190/android_share_logo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAADZ0lEQVQ4y31U20tUQRxeraCXsoyiB7UIIirNtJ05u6aI0EPQP9BjUBGBD0UQURBEV5Seinpwdc/eXHXVLuZ2UVn3XNb1mlZ0oetDQemLRELs7dc3Z8+ul9UOfMzMmfl98/u+38xYLAs+l8oNpPus2KPZS4y+wrbg/w5ZsW4WY49etVNWrVuXxliWki1sX327n+cMV15rCu1+gX+/ZYXF0c42hfb0t4TLL68Ut2gHtypZEJgv+gg62zl+iNQvZ8ilWcmtSSSrFRT+XE/d40cIxPVmfD7UZFQt3gE/VpvENa0jjPzROno2dSoOqeRSJXIqFRScPB4PjB4md+QAwRKrWC8bpDxXbnas8DttozYQHJhzDJbGMB+DjzG3wmPNg2UxWDHXPm7HJvyWWO/VpPyFGeaZpOuBQaARuILJKNoBt2oLuRUWcqo85NBtIRfG2HBAVtkw5i8BXmAIKMhkZ7Lzmp4P1WLX70BEZOhROaRBatRGbZAd7Csjn87gKaeOcRs85c9B/icwaWRbtZSwun3MJiY+AJp3WCLHkJQI9FWkeh+XpqacJanZhg2p180lqRZdinvFWoX1gvSXJ8LR5zyHsGPCLiY+yRrXAwqj3s69ybF72+nt7SL6en0rzVwopOmLhfSusSjRM1BJLREpCNJpX1QSidhyJKcJ2ZeWISnS83Q/TTRsS07dKKHXV4vp83kQnthI0ycLafZYQWKwq5Sahm1BV5hNtw5Ly0vueGl48VFI9mGRd8Ca9D+rpLanldT5aD89bi8z8KC7POEfMTwUGc74hiShTPqfh7qQAelJp85JAPKoOWoC3vpHDQ+DqPZPj86XJcxU+YfIsFX4orAkQFmEszAyxLpeYK7rVZXoZwkz57BAnDvgJtBuZpsARJvClUyJ1hzHDb9V7kyfWe7D7Vq78k1R+cNA2s+EbJK4cfbE+ZNFpiDsnDQK2JrzwCy4y3lYvMrs3zU8VFksnRHDfWZ/jSuYzjZuVrbBuMsKWzP/2mg85wmCwWvR3wXC6MO3B0XgDQQVoj3q1g3JgrzOE87aZTEfifl0HVpZjnyQPO95Xy0qeNqcKzc9nHNp0qbFRWW5DywCjRZEmZ31/u+1guCcObaLYnjSWRZnrEo/fZJl2W8JYT3wBthnjtcBTwCXX6/NW66g/wCVr7kga+yH3QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud+RD3&quot;
        title=&quot;SoundCloud+RD3&quot;
        src=&quot;/blog/static/627ca2cefc8e46c624bf881c0041f3f2/6b190/android_share_logo.png&quot;
        srcset=&quot;/blog/static/627ca2cefc8e46c624bf881c0041f3f2/6b190/android_share_logo.png 128w&quot;
        sizes=&quot;(max-width: 128px) 100vw, 128px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Happy coding on Android!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hack Your Way to Berlin]]></title><description><![CDATA[Berlin is playing host to the oh-so-sold-out JSConf.eu conference happening October 1-2. Some members of the SoundCloud engineering crew…]]></description><link>https://developers.soundcloud.com/blog/hack-your-way-to-berlin</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/hack-your-way-to-berlin</guid><pubDate>Fri, 02 Sep 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Berlin is playing host to the oh-so-sold-out JSConf.eu conference happening October 1-2. Some members of the SoundCloud engineering crew well be on-site and we thought it would be great if you could come too.&lt;/p&gt;
&lt;p&gt;We have an extra ticket for the JSConf.eu conference that we are going to raffle away in a hack competition. The rules of engagement are simple: use this &lt;a href=&quot;https://soundcloud.com/roman-mars/99-invisible-14-periodic-table&quot;&gt;podcast&lt;/a&gt; on the periodic table from &lt;a href=&quot;http://romanmars.com/&quot;&gt;Roman Mars&lt;/a&gt; and build a page that presents it in an awesome way. Be it with a very creative playback method, aggregating content around the sound or building the nicest design we’ve ever seen. The only constraints are that you use JavaScript and that the page is served from GitHub Pages. We want to see the most interesting and incredible page that you can build to play this track.&lt;/p&gt;
&lt;p&gt;The page with the most creativity and “pop” will win the JSConf.eu ticket. We will get you from where you are in Europe to Berlin for the conference and find a nice place for you to sleep.&lt;/p&gt;
&lt;p&gt;Send your entry to &lt;a href=&quot;mailto:eliot@soundcloud.com&quot;&gt;eliot@soundcloud.com&lt;/a&gt; by 23:59:59 Central European Time on September 18.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://jsconf.eu/2011/&quot;&gt;http://jsconf.eu/2011/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Contest requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You reside in Europe&lt;/li&gt;
&lt;li&gt;You are at least 18 years old&lt;/li&gt;
&lt;li&gt;You are allowed to travel to Germany&lt;/li&gt;
&lt;li&gt;You want to come to the JSConf.eu conference&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;tl;dr:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build awesome page with JavaScript highlighting this &lt;a href=&quot;https://soundcloud.com/roman-mars/99-invisible-14-periodic-table&quot;&gt;track&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Deploy to &lt;a href=&quot;http://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Send a link to your page &amp;#x26; repo to &lt;a href=&quot;mailto:eliot@soundcloud.com&quot;&gt;eliot@soundcloud.com&lt;/a&gt; until September 18&lt;/li&gt;
&lt;li&gt;Win JSConf.eu ticket, flight &amp;#x26; accommodation for 3 nights&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Judging will be done by SoundCloud staff by September 22. Have questions? Send
them to &lt;a href=&quot;mailto:eliot@soundcloud.com&quot;&gt;eliot@soundcloud.com&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Hits the Road]]></title><description><![CDATA[A quick virtual heads-up for developers, hackers and coding enthusiasts in the Berlin-area: SoundCloud is very happy to support the upcoming…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-hits-the-road</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-hits-the-road</guid><pubDate>Sun, 28 Aug 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A quick virtual heads-up for developers, hackers and coding enthusiasts in the Berlin-area: SoundCloud is very happy to support the upcoming Hack and Tell event at the C-base bar on Tuesday, August 30. The Hack and Tell is a monthly meet up where participants have the chance to get on stage for five minutes present their personal or professional projects. For those not presenting, it’s a great evening to mix it up with Berlin-based hackers, meet some members of the SoundCloud development team and eat copious amounts of pizza. You can get a full rundown of the event &lt;a href=&quot;http://www.meetup.com/Berlin-Hack-and-Tell/events/29729371/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Berlin’s too far or you don’t like pizza? Here are a handful of the coming non-pizza-centric events where you can catch SoundCloud engineers offline:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://visionsoundmusic.com/&quot;&gt;Vision Sound Music&lt;/a&gt;
London, September 2-4&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.sfmusictech.com/&quot;&gt;SF MusicTech&lt;/a&gt;
San Francisco, September 12&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://mobiletechcon.de/&quot;&gt;Mobile TechCon&lt;/a&gt;
Mainz, Germany, September 12-14&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://gogaruco.com/&quot;&gt;Golden Gate Ruby Conference&lt;/a&gt;
San Francisco, September 16-17&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://jsconf.eu/2011/&quot;&gt;JSConf.eu&lt;/a&gt;
Berlin, October 1-2&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://gotocon.com/aarhus-2011/&quot;&gt;Goto Aarhus&lt;/a&gt;
Aarhus, Denmark, October 13-14&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.paris-web.fr/&quot;&gt;Paris Web&lt;/a&gt;
Paris, October 13-15&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://velocityconf.com/velocityeu/&quot;&gt;Velocity&lt;/a&gt;
Berlin, November 8-9&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Doing the right thing]]></title><description><![CDATA[The recent outage of SoundCloud was the result of everybody doing the right thing. This totally jives with John Allspaw’s message that…]]></description><link>https://developers.soundcloud.com/blog/doing-the-right-thing</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/doing-the-right-thing</guid><pubDate>Wed, 24 Aug 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The recent &lt;a href=&quot;https://status.soundcloud.com/post/9332254589/outage-update-august-23rd&quot;&gt;outage of SoundCloud&lt;/a&gt; was the result of everybody doing the right thing. This totally jives with &lt;a href=&quot;http://www.slideshare.net/jallspaw/advanced-postmortem-fu-and-human-error-101-velocity-2011&quot;&gt;John Allspaw’s message&lt;/a&gt; that looking for a root cause will lead to you finding smart people simply doing their jobs.&lt;/p&gt;
&lt;p&gt;This is what happened.&lt;/p&gt;
&lt;h3&gt;The technologies at play&lt;/h3&gt;
&lt;p&gt;The number of interactions that escalated to the outage should be interesting for other Rails/MySQL shops out there. We’re using many idiomatic patterns within Rails that ended up having devastating consequences.&lt;/p&gt;
&lt;h3&gt;Cache keys that avoid invalidation&lt;/h3&gt;
&lt;p&gt;It’s best practice to form your cache keys in a way that doesn’t require you to issue cache key deletions, and rather let normal evictions reclaim garbage keys. To do this we use the ‘updated_at’ column on some of our models in the cache key so that if the model updates, we know we’ll get a new key.&lt;/p&gt;
&lt;h3&gt;ActiveRecord::Timestamp#touch&lt;/h3&gt;
&lt;p&gt;There is an innocuous method called ‘touch’ that will simply bump the updated_at and save that record. This is quite convenient to call on containers of has_many things like a forum topic is to its posts. With a recent ‘updated_at’ and consistent way to keep that recent via ‘touch’, there is a clean decoupling of cache strategy from business modeling when you intend to communicate the container’s version is as recent as the most recent addition.&lt;/p&gt;
&lt;h3&gt;ActiveRecord::Associations::BelongsToAssociation :counter_cache&lt;/h3&gt;
&lt;p&gt;In the absence of indexed expressions and multi index merges that some databases have, MySQL and InnoDB leaves it to the application to keep lookups of counts efficient. When dealing with tables of multiple millions of rows, a simple query like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; things&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Could take tens of seconds as InnoDB actually needs to traverse all primary keys and literally count the rows that exist which are visible in the current transaction.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Foo&lt;/span&gt; belongs_to &lt;span class=&quot;token symbol&quot;&gt;:things&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:counter_cache&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is a simple and convenient workaround to avoid the ‘count(*)’ overhead where when a new Foo is created on a thing, that thing’s ‘foos_count’ would get a database increment by one. When it’s removed, the ‘foos_count’ would be decremented by one.&lt;/p&gt;
&lt;h3&gt;ActiveRecord::Associations::Association :dependent =&gt; :destroy&lt;/h3&gt;
&lt;p&gt;What better way to maintain all the business rules on deletion than to make sure your model’s callbacks fire when a container is destroyed. When your business consistency is maintained in the ORM, this is also the best place to ensure proper business rules on removal.&lt;/p&gt;
&lt;p&gt;This simply does the following for every association:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;thing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;foos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:destroy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;thing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destroy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s best to have the behavior declared where the association is declared and let the framework make sure it’s not forgotten when actually performing the destruction.&lt;/p&gt;
&lt;h3&gt;More than 140 characters&lt;/h3&gt;
&lt;p&gt;Some of the features of SoundCloud deserve more than 140 characters to do them justice. Even more than 255 characters. The tools that Rails gives you out of the box when you need more than 255 characters on a string in your data layer are limited; you’re left with the ‘text’ type in your schema definition. In MySQL this translates to a “TEXT” column.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;A TEXT column with a maximum length of 65,535 (216 - 1) characters.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Well, in most cases we wouldn’t need more than 1000 characters for much on our site, but in the spirit of “deployed or it didn’t happen”, our early database schemas were mostly driven by the simplest option that ActiveRecord::Schema offered. (We also have way too many NULLable columns)&lt;/p&gt;
&lt;h3&gt;Trade space for time or time for space&lt;/h3&gt;
&lt;p&gt;The early days, SoundCloud ran on 3 servers. CPU was precious so for some of the HTML conversion tasks we traded space for time and are storing a cached version of the HTML for some of the longer text fields in the same row as the record. This was the right choice at the time for the request load and available CPU.&lt;/p&gt;
&lt;p&gt;(time is usually easier to scale than space)&lt;/p&gt;
&lt;h3&gt;Tune your DBs for your workload&lt;/h3&gt;
&lt;p&gt;We separate reads from writes on our data layer, and we also have slightly different tunings on the DB slaves that accept reads. We also have experienced statement-based asynchronous slave replication breaking the replication thread due to these different tunings on different hardware.&lt;/p&gt;
&lt;p&gt;We use row-based (actually mixed) replication between our masters and slaves because it’s as close as you’ll get to the storage engines speaking directly to each other, minimizing the risk of differences in hardware/tuning interfering with the replication thread.&lt;/p&gt;
&lt;h3&gt;Alert on disk space thresholds&lt;/h3&gt;
&lt;p&gt;We have a massive amount of Nagios checks on all our systems, including host-based partition %free checks. When any partition on a host reaches a threshold of free space, an alert is sent.&lt;/p&gt;
&lt;h3&gt;Separate data and write ahead log physical devices&lt;/h3&gt;
&lt;p&gt;Most OLTP databases have data that is bound by random read/writes, whereas binary logs are fundamentally sequential writes. You want these workloads on different spindles when using rotating disks because a transaction cannot complete without first being written to the binary log. If you need to move the drive head for your binlog, you’ve just added milliseconds to all your transactions.&lt;/p&gt;
&lt;h3&gt;Clean up after yourself&lt;/h3&gt;
&lt;p&gt;Periodically there are administrative tasks that need to be performed on the site like mass takedowns of inappropriate content. The Rails console is amazing when you need to work directly with your business domain. Fire it up, get it done. For one-off maintenance tasks this is a life saver.&lt;/p&gt;
&lt;h1&gt;Add Spam, Mix, Bake and we’ve been Served&lt;/h1&gt;
&lt;p&gt;This all adds up, reviewing the good parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abstract away bookkeeping in your domain model&lt;/li&gt;
&lt;li&gt;Leverage existing patterns to get the job done quickly&lt;/li&gt;
&lt;li&gt;Tune and monitor your DBs&lt;/li&gt;
&lt;li&gt;Hand administer your site via your domain model&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you haven’t noticed yet, there have been some incorrigible entrepreneurs using some groups to advertise their pharmaceuticals distribution businesses. They have very thorough descriptions (5-50KB worth), and unprecedented activity with tens to thousands of posts in their own groups.&lt;/p&gt;
&lt;p&gt;At 1:00pm yesterday, we were working through our cleanups, and cranked open a console with a large list of confirmed groups to remove. With Rails this is as simple as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;Group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ids&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:destroy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looks innocent enough.&lt;/p&gt;
&lt;h3&gt;What the database sees&lt;/h3&gt;
&lt;p&gt;From the database perspective all of the automated bookkeeping and business domain extraction of individual destroys creates statements, this ended up looking something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; post
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; posts_count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; posts_count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; updated_at &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; x &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; track_group_contribution
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; tracks_count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tracks_count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; updated_at &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; x &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; user_group_membership
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; members_count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; members_count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; updated_at &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; x &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So we’re seeing 2N+1 number of updates on a group where N is the sum of associated objects.&lt;/p&gt;
&lt;h3&gt;What replication sees&lt;/h3&gt;
&lt;p&gt;When using row-based replication, any change to a row gets that entire row added to the binary log. Some of these groups had over 100k worth of text columns and hundreds of associated posts. When parsing a given binlog, these group updates were taking over 90% of the replication events being sent to the slaves.&lt;/p&gt;
&lt;h3&gt;What the binlog partition sees&lt;/h3&gt;
&lt;p&gt;This is what finally brought us down. We were producing over 3GB/min of binlogs to be replicated to our slaves from these many group updates. Our binlog partition filled up from 10GB to 100GB in a matter of 30 minutes.&lt;/p&gt;
&lt;p&gt;The MySQL docs are clear about what happens with a full data partition. When data cannot be written, MySQL just waits. The behavior around the binlog partition wasn’t as clear. That last binlog event had a partial write. When the disk filled, the binlog corrupted. When that last event in the last binlog was attempted to be sent to the slaves, it failed and the slaves stopped replicating.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Got fatal error 1236 from master when reading data from binary log: &amp;#39;log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our max_allowed_packet is big enough for any of our rows.&lt;/p&gt;
&lt;h1&gt;How we recovered&lt;/h1&gt;
&lt;p&gt;We had a master with live queries that were not coming back from a ‘killed’ state. We scaled the binlog LVM partition so that it could accommodate new writes now, but the DB was not budging. We had no idea how to get it to start writing again so we began a failover process.&lt;/p&gt;
&lt;p&gt;All our slaves were at the same position just before the corrupted event, so we grabbed one, confirmed the tuning was good and then promoted it. We went through the many other slaves and reconfigured them and we were good, consistent to the last event. All we lost were the few processes that were waiting to write to the full partition.&lt;/p&gt;
&lt;p&gt;Ironically, 2 of our team members are currently in the datacenter recabling some of our racks. We also have 4 swanky new DB class machines powered on, but we were a day or two away from getting them networked and integrated. We were just short of having that excess capacity to accept the spike in load from return visitors after the tweet “We’re back!”.&lt;/p&gt;
&lt;h1&gt;Forensics&lt;/h1&gt;
&lt;p&gt;We tried to understand the cause of the sudden binlog growth so we could safely enable the site without a replay of what just had happened. We expanded out some of the logs with ‘mysqlbinlog –verbose’ to show that it was filled with group spam. To confirm that it was group activity, we compared the replication data volume per table of a well sized binlog file with an abnormally large with the following awk program:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; mysql-bin.03980&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  mysqlbinlog &lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;
    /Table_map/ { name = &lt;span class=&quot;token variable&quot;&gt;$9&lt;/span&gt; }
    /BINLOG/ { bytes = 0; col = 1 }
    { if (col) bytes += length(&lt;span class=&quot;token variable&quot;&gt;$0&lt;/span&gt;) }
    /\*!\*/ { if (col) sizes[name] += bytes; col = 0 }
    END { for (i in sizes) print sizes[i], i }
  &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt; -n &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;tee&lt;/span&gt; /tmp/&lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;.sizes &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This created a list of byte sizes of base64 encoded row data to table name. Groups took 820MB compared to the next largest table at 30MB.&lt;/p&gt;
&lt;p&gt;Put this script in your toolbox, it’s also great to use for getting an idea of which tables are your hottest under normal operations.&lt;/p&gt;
&lt;p&gt;We also used our recently finished HDFS-based log aggregation system to run map reduce jobs over our web and app logs to identify any possible abuse vectors around groups that were coming from the outside.&lt;/p&gt;
&lt;h1&gt;What we learned&lt;/h1&gt;
&lt;p&gt;When maintenance around abusive usage is also a part of your business, think about the data and impact on your running system for all maintenance work.&lt;/p&gt;
&lt;p&gt;Cut your losses early and resist the temptation to find the “root cause” during an outage incident. Failforward. Save what you can for forensics after you’re back up.&lt;/p&gt;
&lt;p&gt;Uncleared acknowledged alerts are alertable offenses. A big question during the incident was, “where were the alerts?”. It turned out that the host-based disk space check was previously acknowledged because we had an unrelated partition fill on the same host within expectation. This acknowledgment wasn’t cleared before the binlog partition filled so we didn’t get the 20 minutes of lead time we could have had.&lt;/p&gt;
&lt;p&gt;In the heat of the moment, put your heads together, make a plan for the next X minutes, execute, and repeat until you’re back up. All the engineers were at battle stations during this outage. This incident blindsided us and all kinds of theories were thrown around. When we focused on do X within Y minutes we got down to time boxing research to be able to take our next action. This worked very well.&lt;/p&gt;
&lt;p&gt;Pay off your technical debt. In the past, we took a loan on the future for trading space for time. Paying off these kinds of debts is easy to defer, until the collector comes to visit. Keep a list of the debts chosen, and the debts discovered. Even without an estimated cost to fix, your debt is a learning tool for others for where and when measured choices towards the road of delivery are made.&lt;/p&gt;
&lt;p&gt;Be as specific as you can with your expected data types. We never expected group descriptions to be larger than 2KB. We should have encoded that expectation loud and clear in the data and business layers.&lt;/p&gt;
&lt;h1&gt;Doing the right thing&lt;/h1&gt;
&lt;p&gt;We are all working with the best practices in mind, yet the combination of all that we were doing correctly ended up with this outage nobody expected. Yesterday was an incredible learning experience for everyone at SoundCloud, and the entire team joined together with a positive spirit and heartfelt passion to restore service as quickly as possible. I’m quite proud to work with everyone here.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud mobile – Proxies]]></title><description><![CDATA[The Problem The mobile version of SoundCloud is a consumer of our own API dog food. That decision was made with the intention to deploy a…]]></description><link>https://developers.soundcloud.com/blog/soundcloud-mobile-proxies</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-mobile-proxies</guid><pubDate>Mon, 22 Aug 2011 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;The mobile version of SoundCloud is a consumer of our own API dog food. That decision was made with the intention to deploy a self-sufficient client application that depends only on a static provider. Our early experiements showed that the attempt we made had some downsides. For example, the implementation of &lt;a href=&quot;http://code.google.com/p/chromium/issues/detail?id=70257&quot;&gt;redirects in CORS is not behaving properly&lt;/a&gt; and therefore can’t be used with many of the endpoints in our API where we rely on the correct handling. Also classic XHR communication with the API is not an option due to the &lt;a href=&quot;http://en.wikipedia.org/wiki/Same_origin_policy&quot;&gt;same origin policy&lt;/a&gt; implications that apply even on subdomains.&lt;/p&gt;
&lt;h3&gt;The Idea&lt;/h3&gt;
&lt;p&gt;In another internal project we handled that problem with an iFrame hack for all non GET/HEAD HTTP methods. This makes sense when you’re not directly dependent on an immediate response, but it is not a feasible way to handle direct actions like commenting or favoriting. At this point we looked into different approaches to make the API accessible to the mobile app, without reimplementing any application logic from the mothership. The one we agreed on was a paradigm that we called mountable proxy. Basically, it means that we reserve a namespace in our routes that is exclusive to our API communication and can be treated like api.soundcloud.com. In our case we reserved /_api. Our frontend application is hitting that endpoint with a well-formed request that would originally go to the API subdomain. To get the correct response the proxy is applying basic rewrite rules and proxies the request to the API and returns the reponse again with some basic rewrite rules to the frontend. Thus the frontend code can be written in a way that it not has to deal with extra layers like CORS or any rewrite logic. Normal rules for XHR requests behaviors for example the handling of redirects still apply.&lt;/p&gt;
&lt;h3&gt;The Nginx Solution&lt;/h3&gt;
&lt;p&gt;With Nginx being one of the most important tiers in our http stack, it was natural to lean in that direction first. Nginx serves most of our subdomains and also the static content of the mobile site. The software itself ships with a good amount of powerful modules one of it is the proxy module. It allows one to redirect and alter some of the important bits like Location header. The following snippet is what we had in production when we first went live with m.soundcloud.com:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Only apply redirects to a specific route
location /_api/ {
  # Clean the prefix
  rewrite ^/_api(.*)$ $1 break;

  # Override the default we want the host of the proxy here
  proxy_set_header Host $proxy_host;

  # the actucal redirect
  proxy_pass https://api.soundcloud.com/v1/;

  # Rewrite Location header
  proxy_redirect https://api.soundcloud.com http://$host/_api/;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can find all in-depth documentation for the used directives in the &lt;a href=&quot;http://wiki.nginx.org/HttpProxyModule#proxy_pass&quot;&gt;proxy module&lt;/a&gt; wiki page.&lt;/p&gt;
&lt;h3&gt;The Node Solution&lt;/h3&gt;
&lt;p&gt;The second iteration of the mobile app included features that are exclusive to authenticated users like the Dashboard, own Tracks, and Favorites. It also brings some more requirements like traffic over HTTPS and storage of credentials. We handle authentication against our API with &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth2&lt;/a&gt;. To see in detail how it works, check out the developers documentation for authentication.&lt;/p&gt;
&lt;p&gt;In our case we wanted to avoid exposing the client secret used to get the access token for authenticated communication with the API. For that reason we decided we want to add that secret inside of the proxy for the token endpoint. Really early in the development we started to use a server written in &lt;a href=&quot;http://nodejs.org/&quot;&gt;nodejs&lt;/a&gt; to provide an environment comparable to our live setup. It enabled all developers to equally contribute to the component that drives the local development. Because of that fact we looked into possible options to extend this server code to match the new requirements. The nginx part changed to use a pool of servers combined in one &lt;a href=&quot;http://wiki.nginx.org/HttpUpstreamModule&quot;&gt;upstream directive&lt;/a&gt; and looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;upstream mobile {
  server xx.xx.xx.xx:xxxx weight-10 fails-5 fail_timeout-10s;
}

location / {
  # Avoid double encoding for requests coming from the API
  proxy_set_header Accept-Encoding  &amp;quot;&amp;quot;;

  proxy_pass http://mobile$request_uri;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re using &lt;a href=&quot;http://senchalabs.github.com/connect/&quot;&gt;Connect&lt;/a&gt; for a standard set of server functionalities and mounting the &lt;a href=&quot;https://gist.github.com/1162848&quot;&gt;proxy&lt;/a&gt; into the /_api endpoint like we did before with nginx. The recently introduced &lt;a href=&quot;http://nodejs.org/docs/v0.4.8/api/streams.html#stream.pipe&quot;&gt;pipe method&lt;/a&gt; on &lt;a href=&quot;http://nodejs.org/docs/v0.4.8/api/streams.html&quot;&gt;Streams&lt;/a&gt; helps to keep the code really small and readable. All modifications on top of that was moved to the app layer.&lt;/p&gt;
&lt;p&gt;Through our external monitoring we observed partial downtimes after the move to the node backed cluster. Why they happened wasn’t fully clear as Nginx lacks good introspection about the up and down states of its upstream servers. As a follow-up we mirrored what we already had for our other multi-instance endpoints and put &lt;a href=&quot;http://haproxy.1wt.eu/&quot;&gt;HAProxy&lt;/a&gt; in front of the node processes. This right away got us way better metrics and visibility. Nginx still is part of the stack as it fronts the HAProxy tier.&lt;/p&gt;
&lt;p&gt;During that time Github released &lt;a href=&quot;https://github.com/blog/900-nodeload2-downloads-reloaded&quot;&gt;a blog post&lt;/a&gt; regarding how their nodeload component got optimized. One important fact is the way how nginx handles bodies and only supports HTTP/1.0. We also turned off &lt;a href=&quot;http://wiki.nginx.org/HttpProxyModule#proxy_buffering&quot;&gt;proxy_buffering&lt;/a&gt; in nginx to let data being passed synchronously to the node processes.&lt;/p&gt;
&lt;p&gt;Afer this fundamental setup change we saw a new problem. When communicating to the proxy endpoint over HTTPS and getting a 304 response from the API the socket hang up unexpectedly and HAProxy returned a 502 for this request. We synthesized the problem and isolated it to another finding from the Github blog article about the event handling of end/close on Streams and modules that inherent from Stream. As we use pipe to get the body unaltered from the response coming from the API to the response going to the client we saw that end() was called prematurely. This led to what HAProxy understands as protocol violation and we saw a lot of 502s with the reason SH, that is explained in the &lt;a href=&quot;http://haproxy.1wt.eu/download/1.4/doc/configuration.txt#8.5.&quot;&gt;HAProxy documentation&lt;/a&gt;. This behavior is not necessarily a bug and is not yet changed in node itself. We applied a small workaround mentioned in this &lt;a href=&quot;https://github.com/joyent/node/issues/728&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The Conclusion&lt;/h3&gt;
&lt;p&gt;Even though the decision to mount the API seems like a violation of the idea to have a shippable independent client-side application, it helped to move fast and focus on the actual implementation of the authenticated areas instead of handling hacks and work around them. Both solutions — nginx and node — have worked quite well so far. With both technologies following the same paradigms in implementation they both fit well when it comes to proxy traffic with simple alterations.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building the SoundCloud mobile site using backbone.js]]></title><description><![CDATA[Until early this year, there was a gap. A gap between the desktop-targeted main SoundCloud site, what we call the ‘mothership’, and the…]]></description><link>https://developers.soundcloud.com/blog/building-the-soundcloud-mobile-site-using-backbone.js</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/building-the-soundcloud-mobile-site-using-backbone.js</guid><pubDate>Tue, 02 Aug 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://img.skitch.com/20110722-qf2wmir75es6ag338k2xdkpksg.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Until early this year, there was a gap. A gap between the desktop-targeted main SoundCloud site, what we call the ‘mothership’, and the native iOS (iPhone, iPod touch) and Android applications. A common and frustrating use-case was mobile Twitter: Someone would share a new &lt;a href=&quot;https://twitter.com/#!/ericw/status/93440751215718400&quot;&gt;favorite&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/#!/ianwhooper/status/89517272762286080&quot;&gt;upload&lt;/a&gt; on Twitter, you tap on it, and it tried to load the regular site on your tiny smartphone screen. Pushing the whole desktop site over a mobile connection would be a waste of precious bandwidth, if you only want to check out a track. Alternatively we could try to redirect to our native apps, but there’s no guarantee that the user has it installed and the mobile vendors don’t offer any APIs for verifying that in advance.&lt;/p&gt;
&lt;p&gt;With that in mind, back in December 2010, we set off to build &lt;a href=&quot;https://m.soundcloud.com&quot;&gt;SoundCloud Mobile&lt;/a&gt;, targeting the mobile browsers of iOS and Android. The analytics of the existing site told us that these two platforms make up the overwhelming majority of our users, so we started there. As a mid-term goal, we decided to expand our support to devices, as long as they have a browser capable of streaming audio.&lt;/p&gt;
&lt;p&gt;For the architecture of the site we decided to make it a &lt;a href=&quot;https://developers.soundcloud.com/docs&quot;&gt;SoundCloud API&lt;/a&gt; client, eating our own dogfood just like the native iOS and Android apps already do. With that in mind, we considered the option of building a single-page web application (vs classic serverside rendered pages). To figure out how viable that option is, we spent a week building a prototype based on &lt;a href=&quot;http://jquerymobile.com/&quot;&gt;jQuery Mobile&lt;/a&gt;. The prototype included a start page with hot tracks, a basic search, people and track pages and basic audio streaming. The lists used the theme provided by jQuery Mobile, everything else was barely styled HTML. This prototype helped a lot in making several important decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building a single-page app was feasible, with the client side application as the direct API client. Later we had to back away a bit from that, introducing a proxy to decorate the API (and work around WebKit &lt;a href=&quot;http://code.google.com/p/chromium/issues/detail?id=70257&quot;&gt;bugs&lt;/a&gt;), but overall most of the action is still happening on the client.&lt;/li&gt;
&lt;li&gt;jQuery Mobile works great for a fixed number of preloaded and infinite number of server-generated pages, but not for our usecase of generating all pages on the fly based on API results. We needed much more flexible routing with HTML5 &lt;a href=&quot;https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history&quot;&gt;history.pushState&lt;/a&gt; support, so that we could support the theme URL sets as the main site.&lt;/li&gt;
&lt;li&gt;On a similar note, jQuery Mobile’s theming system allowed us to build a pretty prototype in no time, but wasn’t a good fit for the completely customized UI that we wanted.&lt;/li&gt;
&lt;li&gt;Audio streaming on mobile is still very immature. Even with support for only iOS and Android, plenty of workarounds are required for a somewhat consistent experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After throwing away the first prototype, we moved on to create our own basic framework. It described the domain classes like ‘track’ and ‘user’ as global singleton objects. Our ‘router’ object was responsible of passing on the model data onto the responsible controller method. Soon we could see that the approach wouldn’t scale that well, especially when simultaneous instances of a class were required on the same page.&lt;/p&gt;
&lt;p&gt;After dismissing a &lt;a href=&quot;http://www.sencha.com/products/touch/&quot;&gt;few&lt;/a&gt; &lt;a href=&quot;http://javascriptmvc.com/&quot;&gt;bigger&lt;/a&gt; client-side &lt;a href=&quot;http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&quot;&gt;MVC&lt;/a&gt; frameworks, we’ve stumbled upon &lt;a href=&quot;http://documentcloud.github.com/backbone/&quot;&gt;Backbone.js&lt;/a&gt;, which was compact, easily extendable and depended only on &lt;a href=&quot;http://documentcloud.github.com/underscore/&quot;&gt;Underscore.js&lt;/a&gt;. Backbone sets up only the application structure plus it offers a multitude of convenient methods that can be used while building your app. It doesn’t dictate how the application UX works nor describes how the templates have to be structured. While that still left a lot of open questions for us to answer, it also didn’t impose too much unwanted structure.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://documentcloud.github.com/backbone/&quot;&gt;Backbone.js&lt;/a&gt; let’s you choose your own templating engine, and we went with the &lt;a href=&quot;http://api.jquery.com/jquery.tmpl/&quot;&gt;jquery-tmpl&lt;/a&gt; plugin. We restricted our template usage to output and iteration within the template, both to give us the option of switching to another template engine (e.g. &lt;a href=&quot;http://www.handlebarsjs.com/&quot;&gt;handlebars.js&lt;/a&gt;) and to keep our sanity. To implement the remaining presentation logic, we used the route suggested by Backbone.js, preparing the data for output in the Model’s &lt;a href=&quot;http://documentcloud.github.com/backbone/#Model-toJSON&quot;&gt;toJSON&lt;/a&gt; method. This also has the advantage of keeping the model itself clean, making it easy to update the model and send it back to the server. In addition to that we added a decoration step, modifying the template output before inserting it into the DOM. This includes adding additional classes or removing empty nodes.&lt;/p&gt;
&lt;p&gt;When we started using Backbone.js, it supported only hash-based history (what Twitter does today when it redirects twitter.com/ericw to twitter.com/#!/ericw). We wanted support for &lt;a href=&quot;http://dev.w3.org/html5/spec-author-view/history.html&quot;&gt;history.pushState&lt;/a&gt; to map URLs from soundcloud.com to m.soundcloud.com by only prepending the ‘m.’. We extended Backbone.history for that, while also triggering a custom event. The latter can be used by the Google Analytics tracker or any other component that has to get an update on the current page state.&lt;/p&gt;
&lt;p&gt;We also extended regular Backbone.sync method, used by all Models and Collections to exchange data with the server, to add a client side cache, backed by the HTML5 &lt;a href=&quot;http://dev.w3.org/html5/webstorage/&quot;&gt;sessionStorage&lt;/a&gt;. That way we didn’t have to keep any pages in memory, but can instead rerender them from scratch in milliseconds, as the underlying data is still available in the cache.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.skitch.com/20110722-1r2ynrxuyeqbm47rbmgh6bwce5.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;With those components in place, a click (or rather, tap) on any internal link
caused the following actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handling the click/tap event, preventing the default browser action, and using history.pushState instead to update the current address. At some point telling the Backbone.router that the page changed.&lt;/li&gt;
&lt;li&gt;Backbone.router maps the URL to a controller method, which creates the model for that URL, e.g. initializing the User model with the username parsed from the URL. It then creates the view and passes the model to that view.&lt;/li&gt;
&lt;li&gt;The view tells the model to fetch its data. Once done, with data loaded from the server or from the client side cache, it passes the model to a template, decorates the result and inserts it into the DOM.&lt;/li&gt;
&lt;li&gt;The view also initializes event handlers (via event delegation) to handle all interactions within that view, e.g. a click event on the ‘Play’ button to start streaming audio.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This turned out to be a very solid application architecture which we continued to fine-tune after the first public launch of the mobile site in March, when we redirected iOS and Android traffic from the &lt;a href=&quot;https://soundcloud.com/&quot;&gt;main site&lt;/a&gt;. Since then we continued to add features and improve the site, watching the traffic almost doubling every month.&lt;/p&gt;
&lt;p&gt;Along with this new client side architecture we also experimented with alternatives for development and production. The &lt;a href=&quot;http://nodejs.org&quot;&gt;node.js&lt;/a&gt;-based development and production server, including the API-proxy is &lt;a href=&quot;https://backstage.soundcloud.com/2011/08/soundcloud-mobile-proxies/&quot; title=&quot;SoundCloud mobile – Proxies&quot;&gt;covered in detail&lt;/a&gt; by our node ninja &lt;a href=&quot;https://twitter.com/goldjunge&quot;&gt;Alexander Simmerl&lt;/a&gt;. In the upcoming post we’ll also talk about our approach to testing with &lt;a href=&quot;http://docs.jquery.com/Qunit&quot;&gt;QUnit&lt;/a&gt; and &lt;a href=&quot;http://www.phantomjs.org&quot;&gt;PhantomJS&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Velocity Conference 2011 – Europe]]></title><description><![CDATA[The venerable O’Reilly Velocity Conference is coming to Berlin on the 8th and 9th of November. SoundCloud is doing what we can to help…]]></description><link>https://developers.soundcloud.com/blog/velocity-conference-2011-europe</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/velocity-conference-2011-europe</guid><pubDate>Fri, 29 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The venerable O’Reilly &lt;a href=&quot;http://velocityconf.com/velocityeu&quot; title=&quot;Velocity Conference - EU&quot;&gt;&lt;strong&gt;Velocity Conference&lt;/strong&gt;&lt;/a&gt; is coming to Berlin on the 8th and 9th of November. SoundCloud is doing what we can to help organize the speaker program and want YOU to speak.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have you turned an operations challenge into a opportunity?&lt;/li&gt;
&lt;li&gt;Does web performance matter to you, and you’ve done something to prove it?&lt;/li&gt;
&lt;li&gt;Do you transform your running systems raw data into meaty information?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is an amazing opportunity to share your world class experience with European peers!&lt;/p&gt;
&lt;p&gt;Achtung! The call for proposals ends &lt;strong&gt;August 9th&lt;/strong&gt; so hurry to &lt;a href=&quot;http://velocityconf.com/velocityeu/public/cfp/177&quot; title=&quot;Velocity call for proposals&quot;&gt;send your proposals in to the official website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://farm1.static.flickr.com/40/85477841_b3f1452719_z_d.jpg&quot; alt=&quot;http://www.flickr.com/photos/givingkittensaway/85477841/&quot; title=&quot;Some rights reserved cc-by-sa by Ben Cumming&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Sharing to Facebook, Twitter etc]]></title><description><![CDATA[It is now possible to share all public tracks and playlists to the connections of the authenticated user. So if a user is already connected e.g. with Facebook on SoundCloud, this connection can be used from your app to share a track.]]></description><link>https://developers.soundcloud.com/blog/sharing-to-facebook-twitter-etc</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/sharing-to-facebook-twitter-etc</guid><pubDate>Tue, 26 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It is now possible to share all public &lt;a href=&quot;/blog/docs/api/tracks#social-sharing&quot;&gt;tracks&lt;/a&gt; and &lt;a href=&quot;/blog/docs/api/playlists#social-sharing&quot;&gt;playlists&lt;/a&gt; to the connections of the authenticated user. So if a user is already connected e.g. with Facebook on SoundCloud, this connection can be used from your app to share a track.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The general flow for sharing looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get the connections of the user&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ curl -H &amp;quot;Authorization: OAuth {access_token}&amp;quot; &amp;quot;https://api.soundcloud.com/me/connections&amp;quot;

[
  {
    &amp;quot;id&amp;quot;: 1234,
    &amp;quot;created_at&amp;quot;: &amp;quot;2010/12/14 22:02:07 +0000&amp;quot;,
    &amp;quot;display_name&amp;quot;: &amp;quot;My Facebook Name&amp;quot;,
    ...
    &amp;quot;service&amp;quot;: &amp;quot;facebook_profile&amp;quot;,
  },
]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose connections&lt;/p&gt;
&lt;p&gt;Display the connections to the user and let her choose to which social networks she wants to post to. Remember the ids of the connections for the next step. You can also enable the user to connect to new social networks. This is explained further in the &lt;a href=&quot;/blog/docs/api/me-connections&quot;&gt;connections&lt;/a&gt; documentation. Furthermore the user can enter a custom sharing message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Post to connections&lt;/p&gt;
&lt;p&gt;In this example we will post the track with the id 321 with the message “I love this track” to the Facebook profile 1234:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ curl -H &amp;quot;Authorization: OAuth {access_token}&amp;quot; &amp;quot;https://api.soundcloud.com/tracks/321/shared-to/connections&amp;quot; \
  -d connections[][id]=1234 \
  -d sharing_note=I+love+this+track&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can both post your own and other peoples track via this resource. If you want to know more, read the documentation for track &lt;a href=&quot;/blog/docs/api/tracks/#social-sharing&quot;&gt;here&lt;/a&gt; and for playlists &lt;a href=&quot;/blog/docs/api/playlists#social-sharing&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Promoting your app in the SoundCloud App Gallery]]></title><description><![CDATA[UPDATE: We made some changes to how we manage the App Gallery. Read about them here: The Next App Gallery Update So you’ve built a fantastic app that you would like to get showcased in the SoundCloud App Gallery?   Here’s what you can do to improve your chances of getting your app featured and increase the likelihood of SoundCloud users trying out your app.]]></description><link>https://developers.soundcloud.com/blog/promoting-your-app</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/promoting-your-app</guid><pubDate>Mon, 25 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;UPDATE: We made some changes to how we manage the App Gallery. Read about them here: &lt;a href=&quot;/blog/blog/the-next-app-gallery-update&quot;&gt;The Next App Gallery Update&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So you’ve built a fantastic app that you would like to get showcased in the SoundCloud App Gallery?   Here’s what you can do to improve your chances of getting your app featured and increase the likelihood of SoundCloud users trying out your app.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Add your App Gallery profile &lt;a href=&quot;https://soundcloud.com/you/apps&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt; - Sell it - let people know what makes your app one of a kind,  why should they download it? As silly as this may sound, don’t forget to explain what it does!  Make use of simple HTML formatting to give the text the presentation it deserves.&lt;/p&gt;
&lt;p&gt;Example:  &lt;a href=&quot;https://soundcloud.com/apps/deadmau5&quot;&gt;Deadmau5 Remix&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sound&lt;/strong&gt; - if your app creates audio,  let SoundCloud users what kinds of sounds they can expect to create!  If not,  why not record a short description of what the app can do that users can listen to?&lt;/p&gt;
&lt;p&gt;Example: &lt;a href=&quot;https://soundcloud.com/apps/nanostudio&quot;&gt;NanoStudio&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Screenshots&lt;/strong&gt; - the more the merrier. Remember that SoundCloud users need to understand not only what your app does, but how they can connect with SoundCloud.   Adding carefully selected screenshots will tell more of a story than just a short paragraph.  You can add as many screenshots as you like!&lt;/p&gt;
&lt;p&gt;Example: &lt;a href=&quot;https://soundcloud.com/apps/fire2&quot;&gt;Fire2 - Field Recorder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Group&lt;/strong&gt; - create a group so that users of the app can submit tracks to it, the last five tracks from the group will show on your app page.  Currently only SoundCloud admins can add a group URL, get in contact with “api [at] soundcloud.com” to get your app’s group added to your page!&lt;/p&gt;
&lt;p&gt;Example: &lt;a href=&quot;https://soundcloud.com/apps/freestyle-app&quot;&gt;Freestyle App&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sound and Vision&lt;/strong&gt; - Add a tutorial video.   There are plenty of tools available for making short screencasts available, like &lt;a href=&quot;http://www.techsmith.com/jing/&quot;&gt;Jing&lt;/a&gt; for example.&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 69.8%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;http://www.youtube.com/embed/Cks_1miFLPM&quot; frameborder=&quot;0&quot; allowfullscreen style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After you have added your app, the SoundCloud admin team will check your app for adherence to SoundCloud guidelines and completeness.
Once approved, your app will go live in the SoundCloud App Gallery and might get featured.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New Activities]]></title><description><![CDATA[Today we announce new activities available over the API. As a reminder: Activites are the items on your Dashboard.]]></description><link>https://developers.soundcloud.com/blog/new-activities</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new-activities</guid><pubDate>Fri, 22 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today we announce new activities available over the API. As a reminder: Activites are the items on your &lt;a href=&quot;https://soundcloud.com/dashboard&quot;&gt;Dashboard&lt;/a&gt;.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The activities resource now supports favoritings and comments. They look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2011/07/21 10:07:50 +0000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123456&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;I like this track a lot&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    ...
    &lt;span class=&quot;token property&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ...
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;track&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123456&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ...
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;affiliated&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;favoriting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2011/07/22 06:50:39 +0000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;track&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123456&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;My new nice track&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ...
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;affiliated&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also, you are now able to filter recent comments and favoritings that happened on your own tracks via&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://api.soundcloud.com/me/activities/all/own?oauth_token=A_VALID_TOKEN&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The documentation can be found &lt;a href=&quot;https://developers.soundcloud.com/docs/api/me-activities&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New Sharing Interface]]></title><description><![CDATA[Today we announce a better way to share sounds in SoundCloud via the API. The new interface resembles the way sharing happens on the website. You can retrieve, create, update and delete sharings to other users on SoundCloud based on user_id or email address for your tracks and playlists.]]></description><link>https://developers.soundcloud.com/blog/new-sharing-interface</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/new-sharing-interface</guid><pubDate>Wed, 20 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today we announce a better way to share sounds in SoundCloud via the API.&lt;/p&gt;
&lt;p&gt;The new interface resembles the way sharing happens on the website. You can retrieve, create, update and delete sharings to other users on SoundCloud based on user_id or email address for your tracks and playlists.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;The following new resource are available:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;GET                    /tracks/{id}/shared-to
GET, POST, PUT, DELETE /tracks/{id}/shared-to/users
GET, POST, PUT, DELETE /tracks/{id}/shared-to/emails

GET                    /playlists/{id}/shared-to
GET, POST, PUT, DELETE /playlists/{id}/shared-to/users
GET, POST, PUT, DELETE /playlists/{id}/shared-to/emails&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also you can set the sharing state when you create or update a track/playlist.&lt;/p&gt;
&lt;p&gt;Find the docs here for &amp;#x3C;%= link&lt;em&gt;to(‘tracks’, ’&lt;a href=&quot;https://developers.soundcloud.com/docs/api/tracks#sharing&amp;#x27;&quot;&gt;https://developers.soundcloud.com/docs/api/tracks#sharing’&lt;/a&gt;) %&gt; and &amp;#x3C;%= link&lt;/em&gt;to(‘playlists’, ’&lt;a href=&quot;https://developers.soundcloud.com/docs/api/playlists#sharing&amp;#x27;&quot;&gt;https://developers.soundcloud.com/docs/api/playlists#sharing’&lt;/a&gt;) %&gt;.&lt;/p&gt;
&lt;p&gt;This change deprecates the &lt;code class=&quot;language-text&quot;&gt;/tracks/{id}/permissions&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;POST /tracks/{id}/shared-to&lt;/code&gt; resources. Both still work, but are not recommended anymore.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[MySQL for Statistics – Old Faithful]]></title><description><![CDATA[MySQL turns out to be a good Swiss Army Knife for persistence, if used wisely. Understanding disk access patterns driven by your storage…]]></description><link>https://developers.soundcloud.com/blog/mysql-stats-old-faithful</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/mysql-stats-old-faithful</guid><pubDate>Tue, 05 Jul 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MySQL turns out to be a good Swiss Army Knife for persistence, if used wisely. Understanding disk access patterns driven by your storage engine is key. Choosing a read or write optimized disk layout will get you very far. We chose a read-optimized disk layout using InnoDB and MySQL for statistics.&lt;/p&gt;
&lt;p&gt;While our wheels were spinning trying to find out why our statistics storage patterns were &lt;a href=&quot;https://developer.soundcloud.com/2011/04/failing-with-mongodb/&quot;&gt;causing MongoDB to thrash our disks&lt;/a&gt;, we started looking for an emergency alternative with the technology that we already had: MySQL+InnoDB.&lt;/p&gt;
&lt;p&gt;What we knew:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to persist a log entry for each play&lt;/li&gt;
&lt;li&gt;The play events are coming in sequentially ordered by time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Counts for 5 different dimensions of the plays: by referrer, listener, 3rd party application, and country, and the total count.&lt;/li&gt;
&lt;li&gt;Counts for 3 different time ranges – all time, currently displayed period and previous period to calculate the percentage changed.&lt;/li&gt;
&lt;li&gt;A time series of the totals for a single dimension over the displayed period&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we had so far (don’t do this at home!):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single log table called &lt;em&gt;plays&lt;/em&gt; in our online database with the below schema&lt;/li&gt;
&lt;li&gt;A secondary index on each dimension used by the statistics pages to build aggregates using the &lt;em&gt;count&lt;/em&gt; aggregate function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We posed the question, “what is the minimum we could do with our current InnoDB log table so that it can service our read load”.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; plays &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at &lt;span class=&quot;token keyword&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  track_id &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  referer &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COLLATE&lt;/span&gt; utf8_unicode_ci&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  country &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COLLATE&lt;/span&gt; utf8_unicode_ci &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  api_consumer_key &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COLLATE&lt;/span&gt; utf8_unicode_ci &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; index_plays_on_track_id &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; index_plays_on_user_id &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; index_plays_on_created_at &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;created_at&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; index_plays_on_referer &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;referer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ENGINE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;InnoDB&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHARSET&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;utf8 &lt;span class=&quot;token keyword&quot;&gt;COLLATE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;utf8_unicode_ci&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The impending doom of letting this table continue to grow was also approaching fast. This table was ticking along at multiple billions of rows, consuming hundreds of gigabytes of data, had no clear shard key and the indexes added another 80% on top of the data and filling too much storage space on our OLTP databases.&lt;/p&gt;
&lt;p&gt;To answer the question “what can we do to serve stats within a second”, we should also find the root cause of “why aren’t we currently servicing our current read load?”&lt;/p&gt;
&lt;p&gt;Still bruised from getting whacked by the reality stick from our previous experiences, the first step was to trace the entire operation of a typical query from request to response, paying close attention to where the IO operations &lt;em&gt;could&lt;/em&gt; be occurring and take the assumption that they will always occur. We no longer assumed that our dataset would reside in memory.&lt;/p&gt;
&lt;p&gt;Our typical query looked something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; total
  &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; plays
 &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; track_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; created_at &lt;span class=&quot;token operator&quot;&gt;between&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; country&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was performed over date intervals of week, last week and all time. For
the other dimensions, we’d group by the additional column like &lt;em&gt;country&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The first thing that gets hit is the query planner. An &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/explain-output.html&quot;&gt;explain&lt;/a&gt; on query for a 1 row table reveals this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; select_type: SIMPLE
        table: plays
         type: ref
possible_keys: index_plays_on_track_id,index_plays_on_created_at
          key: index_plays_on_track_id
      key_len: 5
          ref: const
         rows: 1
        Extra: Using where; Using temporary; Using filesort&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ah, nice and simple, traverse the &lt;em&gt;track_id&lt;/em&gt; index then load the row, fill out a temporary table with the aggregate fields &lt;em&gt;track_id&lt;/em&gt; and &lt;em&gt;country&lt;/em&gt;, then split up that temporary table into chunked buffers sorting each buffer, potentially writing it to disk, then perform a merge sort from all the temporary buffers.&lt;/p&gt;
&lt;p&gt;It looks so obvious now because we have spent the effort to read the MySQL source, read the documentation and walked through every step and the tuning variables that affect each step. We currently don’t have a DBA on staff to ask, so thankfully we could teach ourselves from the online manual and source code.&lt;/p&gt;
&lt;p&gt;Taking a little detour, it’s good to explain how we’re interpreting the Extra column one at a time. In reverse:&lt;/p&gt;
&lt;h2&gt;Using filesort&lt;/h2&gt;
&lt;p&gt;A filesort doesn’t necessarily mean it goes to files. This &lt;a href=&quot;http://s.petrunia.net/blog/?p=24&quot;&gt;article by Sergey Petrunia&lt;/a&gt; gives a very good overview of what a filesort actually does, putting in clear terms what is suggested for &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html&quot;&gt;optimizing MySQL order by&lt;/a&gt; clauses.&lt;/p&gt;
&lt;p&gt;We were obviously not hitting any &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/group-by-optimization.html&quot;&gt;group by optimizations&lt;/a&gt; with this query.&lt;/p&gt;
&lt;h2&gt;Using temporary&lt;/h2&gt;
&lt;p&gt;This is a byproduct of &lt;em&gt;order by&lt;/em&gt;, &lt;em&gt;group by&lt;/em&gt; or &lt;em&gt;select distinct&lt;/em&gt; as described
by &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html&quot;&gt;how MySQL uses internal temporary tables&lt;/a&gt;. MySQL must use a temporary
table to satisfy these kinds of query. If you have a lot of temporary tables
created going to disk by monitoring the &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/server-status-variables.html#statvar_Created_tmp_disk_tables&quot;&gt;Created_tmp_disk_tables&lt;/a&gt; status
variable, you should increase your &lt;em&gt;tmp_table_size&lt;/em&gt; and &lt;em&gt;max_heap_table_size&lt;/em&gt;
variables to prevent “swapping” on your temporary partition.&lt;/p&gt;
&lt;h2&gt;Using where&lt;/h2&gt;
&lt;p&gt;This means that the condition of the query cannot be fulfilled solely from the index. The row data must be fetched so that the condition “created_at between ? and ?” can be tested. The number of rows in the &lt;em&gt;rows&lt;/em&gt; column will indicate how many rows MySQL guesses it needs to pull out from the storage engine.&lt;/p&gt;
&lt;p&gt;How the rows are fetched is actually specific to the storage engine and this is where we learned our most important lesson about InnoDB: Everything is a BTree. The InnoDB table itself is a &lt;a href=&quot;http://en.wikipedia.org/wiki/Index_(database)#Clustered&quot;&gt;clustered index&lt;/a&gt; on the primary key with the leaves containing the row data. The secondary indexes (the ones you add to your table) are organized by indexed columns with their leaves  containing the primary key of the row. References: [Xarb][xarb], &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/innodb-table-and-index.html&quot;&gt;MySQL Indexes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you see “using where”, you are making 2 index lookups. One on the secondary index to find the primary key, and one on the clustered index (table) to find the row data.&lt;/p&gt;
&lt;p&gt;What this means for the &lt;em&gt;plays&lt;/em&gt; table is a classic example of how easy it is to optimize for writes or reads but not both. The &lt;em&gt;plays&lt;/em&gt; table is optimized for writes as plays come in, the surrogate ID gets incremented and ends up settling cosy next to the warm data on the right side of the BTree. A read comes in and performs a quick traversal down a secondary index. What it gets is a list of primary keys that can range from 0 to max(id). The lookup of those primary keys turn a sequential secondary index traversal into effectively multiple random lookups on the clustered index. Traverse primary key – load database page, traverse next primary key – load database page, seek to next primary key – load page, etc…&lt;/p&gt;
&lt;p&gt;In the worst-case, we incur 1 seek for every statistic. At about 10ms per seek… and some tracks with many millions of plays… we effectively try to load almost all pages from the table. Now we have the answer why we’re not able to perform.&lt;/p&gt;
&lt;h2&gt;How to fix&lt;/h2&gt;
&lt;p&gt;What we now know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have effectively random reads on the full plays table using secondary indexes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sequential reads by not reading the rows in our AUTO_INCREMENT primary key order&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Covering indexes&lt;/h2&gt;
&lt;p&gt;This is one very good option to avoid the 2nd index lookup on the primary key
in the table. A &lt;a href=&quot;http://dev.mysql.com/doc/innodb/1.1/en/glossary.html#glos_covering_index&quot;&gt;Covering Index&lt;/a&gt; contains all columns that will satisfy
the SELECT, WHERE, ORDER BY and GROUP BY clauses in a composite index so after
you traverse the secondary index, you already have all the data you need and
do not need to go to the table storage. This will show up in the EXPLAIN as
“Using index” and is a sign that you’ll perform only a single BTree traversal
instead of one for the index and one for each of rows that index points to.&lt;/p&gt;
&lt;p&gt;Designing a covering index for our plays table requires careful thought to
the ordering of the indexed columns. We need to order our indexed columns so
that the composite index that composes the covering index can be reused for
multiple queries. We also need to be thrifty what we’re storing here, because
a covering index will make a partial copy of column data in the index.&lt;/p&gt;
&lt;p&gt;For the &lt;em&gt;country&lt;/em&gt; counts, we could do something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; covering_country_index &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; plays &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; created_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the above select would explain like this:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Using where; Using index; Using temporary; Using filesort&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Almost – we are using columns from the index but because the index is ordered
by &lt;em&gt;track_id&lt;/em&gt;, &lt;em&gt;created_at&lt;/em&gt; and the GROUP BY is on &lt;em&gt;track_id&lt;/em&gt;, &lt;em&gt;country&lt;/em&gt;, the
GROUP BY will need to create the temporary table from the row data to perform
the aggregation. If we reorder the columns in the index:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; covering_country_index &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; plays &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; created_at&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The query explains a bit better:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Using where; Using index&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Using where?! Where is this coming from? We have a covering index that has
the first two columns used by the GROUP BY, all columns in the WHERE and all
columns in the SELECT.&lt;/p&gt;
&lt;p&gt;Ah, always &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/explain-output.html&quot;&gt;read the fine print&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Even if you are using an index for all parts of a WHERE clause, you may see
Using where if the column can be NULL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This table is old… like SoundCloud early days prototype to get it out the door
old. It was created using the ActiveRecord schema language which didn’t encourage
decent table schema design. ALL of the columns can be NULL… shit.&lt;/p&gt;
&lt;h2&gt;So you want to ALTER TABLE eh?&lt;/h2&gt;
&lt;p&gt;No worries, we can just convert the column type to be able to create our covering
index. Being in production, the table should still be readable while it’s being
migrated to the new “NOT NULL” columns. Rehearsing the necessary operations
cost on production dataset took over 1 day before looking at the temporary
table size to realize it was only 20% completed.&lt;/p&gt;
&lt;p&gt;The index creation would also lock the table due to a &lt;a href=&quot;http://bugs.mysql.com/bug.php?id=33650&quot;&gt;known bug&lt;/a&gt; to perform
&lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/alter-table.html#alter-table-online-limitations&quot;&gt;online table alterations&lt;/a&gt; on columns that are in the UTF8 charset. UTF8
column alterations will take out a read lock.&lt;/p&gt;
&lt;p&gt;Alter table on &lt;em&gt;plays&lt;/em&gt; to change the charset, and add copies of all of our
data into covering indexes causing up to a day of unavailability to achieve
this wasn’t worth it. If we’re going to fix this, we may as well fully separate
our write-optimized log from our read-optimized index.&lt;/p&gt;
&lt;h2&gt;Aha moment&lt;/h2&gt;
&lt;p&gt;What we now know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Covering indexes can remove the filesort and surrogate key lookup from the clustered index.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A table and index format that does not cause random I/O reads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We were looking at duplicating the dataset of the logging table &lt;em&gt;plays&lt;/em&gt; in
the indexes. Only if we had tighter control of the indexes we could tune them
to meet the requirements of sequential disk lookups and in-memory aggregation…&lt;/p&gt;
&lt;h2&gt;Use the clustered index… (luke).&lt;/h2&gt;
&lt;p&gt;InnoDB requires a primary key because this is the clustered index key. For
almost all cases, a surrogate AUTO_INCREMENT integer key is the best because
it will have good insertion performance and small overhead for the leaf nodes
of the secondary indexes. But, you can specify an alternative key, even a composite
key as long as it follows some constraints. Here’s the money quote, &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.1/en/innodb-index-types.html&quot;&gt;again
from the excellent documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;If you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index.&lt;/li&gt;
&lt;li&gt;If you do not define a PRIMARY KEY for your table, MySQL picks the first UNIQUE index that has only NOT NULL columns as the primary key and InnoDB uses it as the clustered index.&lt;/li&gt;
&lt;li&gt;If the table has no PRIMARY KEY or suitable UNIQUE index, InnoDB internally generates a hidden clustered index on a synthetic column containing row ID values. The rows are ordered by the ID that InnoDB assigns to the rows in such a table. The row ID is a 6-byte field that increases monotonically as new rows are inserted. Thus, the rows ordered by the row ID are physically in insertion order.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Accessing a row through the clustered index is fast because the row data is
on the same page where the index search leads. If a table is large, the clustered
index architecture often saves a disk I/O operation when compared to storage
organizations that store row data using a different page from the index record.
(For example, MyISAM uses one file for data rows and another for index records.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The primary key must contain unique, non-null columns. This doesn’t work very
well for the raw plays table because we could have 2 plays on the same track
in the same second from the same country and would like to count both. Not
to mention an increase in the key size would explode the size of our secondary
indexes.&lt;/p&gt;
&lt;p&gt;A new table schema could take advantage of the clustered index that InnoDB
provides. The trade-off is bloated secondary indexes due to the secondary index’s
leaf nodes containing the primary key of the table. If we don’t need secondary
indexes, then lets look at how to shape a clustered index.&lt;/p&gt;
&lt;h2&gt;Introducing aggregate tables&lt;/h2&gt;
&lt;p&gt;To support the time series and totals query for both per-user and per-track
using unique primary keys that help shape our clustered index leveraged the
“bucket” model from our prior attempts at the stats. We looked at the minimum
granularity we wanted for the statistics and found that hourly and daily were
sufficient for all aggregations. Given that, we could create a schema that
looked something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; plays_by_hour &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  owner_id &lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  track_id &lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  count &lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;owner_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;hour&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our time series per track query could be as simple as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; owner_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;hour&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count
  &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; plays_by_hour
 &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; owner_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; track_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;between&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This explains as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  select_type: SIMPLE
        table: plays_by_hour
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 12
          ref: NULL
         rows: 1
        Extra: Using where&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The “Using where” in this explain is effectively the same thing as “Using index”
because all columns in the where make up the branches in the clustered index
and since we only need the count, that value stored on the leaves is already
loaded from disk from the index traversal.&lt;/p&gt;
&lt;p&gt;The composition of coarser grained time series isn’t so bad now because we
have loaded adjacent pages during the primary key choice.&lt;/p&gt;
&lt;p&gt;To get the total for that time series on day groupings, all that is needed
is to sum the counts:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; total
  &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; plays_by_hour
 &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; owner_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; track_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;between&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; owner_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track_id&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Back of the hand math is if we have 64 bytes per row, and even if we’re looking
at 5 years of counts, our upper bound is no more than 3 MB to pull off of disk.
When the tables are optimized, this is a sequential read at ~50MB/s, keeping
well under 100ms which is acceptable for this application.&lt;/p&gt;
&lt;p&gt;By including the owner_id in the primary key, we get a degree of locality and
a handy partitioning key for our queries as well.&lt;/p&gt;
&lt;p&gt;For the remaining metrics around countries and apps, we just added one more
column to the clustered index.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; plays_by_country_day &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  track_id &lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  country &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  count &lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Don’t forget to pick the simplest character encoding to support your dataset
like &lt;em&gt;latin1&lt;/em&gt; if you have any character columns).&lt;/p&gt;
&lt;h2&gt;Reasoning about I/O&lt;/h2&gt;
&lt;p&gt;In our &lt;a href=&quot;https://developer.soundcloud.com/2011/04/failing-with-mongodb/&quot;&gt;MongoDB experience&lt;/a&gt; we spent a long time running experiments to
identify the worst case performance. With MySQL and InnoDB, we have very clear
documentation on the on-disk layout of the tables and indexes. We can reason
very well about the worst case performance of our non-optimized plays table
and index layout. Now we can reason very well about the worst case performance
of this new table layout. The explain output is somewhat limited. We have one
signal out of MySQL “Using where” and copious documentation to give that meaning.&lt;/p&gt;
&lt;p&gt;We chose this approach because we could reason about I/O. We also can reason
about space. By using a partial aggregation on &lt;em&gt;hour&lt;/em&gt;, we know that upper bound
for storage is one row for every hour since the creation of the track. By treating
the data as the (clustered) index, we can reason both about the index and data
size, because they are the same! We can calculate storage requirements by correlating
track growth to these aggregate tables by hour, not by popularity.&lt;/p&gt;
&lt;p&gt;What we also get is the desired sequential disk access on a cold cache because
the row data (the count) is on the same page that gets traversed. We also get
data locality of the time series for balanced BTrees. However, the persistence
of the BTree can produce page splits where pages are not physically located
next to each other, but due to the separation of these read-optimized tables
from the source-of-truth, write-optimized event storage means we can run OPTIMIZE
TABLE weekly which will reorganize the physical pages of the aggregate tables
so that they are mostly sequential.&lt;/p&gt;
&lt;p&gt;The catch is, by using aggregate tables in read-optimized form, there is no
way we can sustain the writes directly from our raw stream of events. We must
also use an &lt;a href=&quot;http://en.wikipedia.org/wiki/Extract,_transform,_load&quot;&gt;ETL&lt;/a&gt; process that batches the updates per hour and per day
from the log table. We started running ours every 15 minutes, but found that
we must tune that back to every hour. Not so realtime. To get the realtime
behavior another in-memory system would be the best bet to lay over the persistence
for the most recent hour/day of counts.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;This is not the end of our stats story. We want realtime stats for ourselves,
and want you to have them too! This solution won’t last forever, but from start
to finish, leveraging all the knowledge gained from an incomplete Cassandra
implementation, and incomplete MongoDB implementation plus our in-house experience
with MySQL, it took 3 months to research, design and deploy. With about 2 weeks
purely on the data migration.&lt;/p&gt;
&lt;p&gt;This is not a story about how NoSQL fails, how MySQL wins, or how to code like
a &lt;a href=&quot;http://lolcat.biz/&quot;&gt;lolcat&lt;/a&gt;. It’s a story of what we’re doing to get by with the core physical
limitations of CPU/Memory/Network/Disk and operational resources, one order
of magnitude of user growth at a time.&lt;/p&gt;
&lt;p&gt;Speaking with others about this solution has also led to some insights. SSDs
will likely make the data locality of using a clustered index a non-issue.
And we could generalize the table layout for other metrics as well, instead
of saying “plays_by_country_day” it would be “daily_stats_by_country” and include
a 2 byte “metric_id” that would indicate this was a ‘play’ rather than ‘download’.&lt;/p&gt;
&lt;p&gt;Understanding the lower and upper bounds of your I/O profile is critical when
designing for consistent performance, data-driven systems with any technology.
Good documentation and understanding of your storage engine’s implementation
is the key to achieve that.&lt;/p&gt;
&lt;h2&gt;Curious&lt;/h2&gt;
&lt;p&gt;If you’re storing and serving hundreds of GB or TB of stats for your customers,
how have you designed your system?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hack Ideas]]></title><description><![CDATA[If you are looking for some inspiration for your next weekend hack project,
here is a list of ideas that could be built using the SoundCloud platform.
Feel free to add your own ideas in the comments!]]></description><link>https://developers.soundcloud.com/blog/hack-ideas</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/hack-ideas</guid><pubDate>Sun, 29 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are looking for some inspiration for your next weekend hack project,
here is a list of ideas that could be built using the SoundCloud platform.
Feel free to add your own ideas in the comments!&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h3&gt;Web&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Moodboards&lt;/strong&gt;: blend available ambient sounds and music from SoundCloud to make ‘moodboards’, e.g. moodboard.com/fire or moodboard.com/forest&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Street musicians of the world&lt;/strong&gt;: world map of all the recorded street music on SoundCloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send a sound to a Facebook friend&lt;/strong&gt;: simple way to share sounds and recordings to Facebook friends&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Current events&lt;/strong&gt;:                    audio commentary pages of world current events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commentary Overlay&lt;/strong&gt;: Artist’s audio commentary or audio listener feedback&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moodline&lt;/strong&gt;:                         record users who move their mouses if they like a part of a track or not. create composites of moodlines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Last.fm + SoundCloud&lt;/strong&gt;: Enter your last.fm username and get suggestions on who to follow on SoundCloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Record a telephone call&lt;/strong&gt;: e.g. a prank call recordings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moodboards #2&lt;/strong&gt;:  — layering sounds, innovative interface for blending SoundCloud sounds together in the browser using various HTML5 technology&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crowdsourced lyrics for sounds&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crowdsourced book reading&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rap battle hiphop&lt;/strong&gt;: a la &lt;a href=&quot;http://takesquestions.com&quot;&gt;http://takesquestions.com&lt;/a&gt; but for rappers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“SoundCards”&lt;/strong&gt;: Moocards+SoundCloud. Secret track links, invite-codes, waveform-displays and comments on the cards.&lt;/li&gt;
&lt;li&gt;Moodbar analyzer for tracks.&lt;/li&gt;
&lt;li&gt;A standard JS picker UI for SoundCloud tracks from your profile or from the whole site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ingestion-app for SoundCloud&lt;/strong&gt;: Uses SoundCloud Connect to bulk add 100s of files over SFTP+manifest xml files (standard ingestion scheme).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WeFollow tweak&lt;/strong&gt; for popular SoundCloud users etc.&lt;/li&gt;
&lt;li&gt;Use the new Mozilla recording API to record and share on SoundCloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Download limit monitor&lt;/strong&gt;: Check if a track has been uploaded more than expected, if so, disable download&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapportive plugin&lt;/strong&gt; for GMail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discography&lt;/strong&gt;: Turn sets into a discography page with artwork, buy link, cat no etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slideshare Mashup&lt;/strong&gt;: Sync presentation audio recording with the slides. Leverage &lt;a href=&quot;http://popcornjs.org/&quot;&gt;http://popcornjs.org/&lt;/a&gt; ?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Birthday messages&lt;/strong&gt;: Facebook based app to track your friends birthdays and remind you to share an audio greeting with them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instagram for Sound&lt;/strong&gt;: &lt;a href=&quot;http://madebymany.com/blog/what-would-instagram-sound-like&quot;&gt;http://madebymany.com/blog/what-would-instagram-sound-like&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Mobile&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audiogram&lt;/strong&gt;: an Instagram style app to record sounds, applying audio filters and share on SoundCloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal audio diary&lt;/strong&gt;: inspired by the memento/everyday iphone apps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Desktop&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A file system plugin (fuse?) that lets you mount your SoundCloud account and add files to it by dragging and dropping.&lt;/li&gt;
&lt;li&gt;Audio visualizer using timed comments for textual-based artistic visualization at the right point in the sound.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Java API Wrapper]]></title><description><![CDATA[Alongside with the SoundCloud Android app we’ve created a new
Java Wrapper for the SoundCloud API
and today we can finally release it to the public! It is simple to use, supports OAuth2 and requires a minimum of external dependencies so it should be easily embeddable
in both desktop and mobile applications.]]></description><link>https://developers.soundcloud.com/blog/java-api-wrapper</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/java-api-wrapper</guid><pubDate>Thu, 19 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Alongside with the &lt;a href=&quot;https://soundcloud.com/apps/android&quot;&gt;SoundCloud Android app&lt;/a&gt; we’ve created a new
&lt;a href=&quot;https://github.com/soundcloud/java-api-wrapper&quot;&gt;Java Wrapper&lt;/a&gt; for the SoundCloud API
and today we can finally release it to the public!&lt;/p&gt;
&lt;p&gt;It is simple to use, supports OAuth2 and requires a minimum of external dependencies so it should be easily embeddable
in both desktop and mobile applications.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Here is a quick demonstration how easy it is to use it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a wrapper instance&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ApiWrapper&lt;/span&gt; wrapper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;client_secret&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LIVE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Obtain a token&lt;/span&gt;
wrapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Execute a request&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HttpResponse&lt;/span&gt; resp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wrapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/me&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Update a resource&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HttpResponse&lt;/span&gt; resp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
      wrapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/me&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user[full_name]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Che Flute&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;token string&quot;&gt;&quot;user[website]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token string&quot;&gt;&quot;http://cheflute.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user[avatar_data]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flute.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll find the wrapper on &lt;a href=&quot;https://github.com/soundcloud/java-api-wrapper&quot;&gt;GitHub&lt;/a&gt; and more infos and examples in the &lt;a href=&quot;https://github.com/soundcloud/java-api-wrapper/blob/master/README.md&quot;&gt;Readme&lt;/a&gt;.
Let us know what you think!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Experiment 02: Destroying SoundCloud & Instagram]]></title><description><![CDATA[I was fortunate enough to be given the opportunity to help Moby premiere his new record via SoundCloud. I didn’t know what to expect from…]]></description><link>https://developers.soundcloud.com/blog/experiment-02-destroying-soundcloud-instagram</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/experiment-02-destroying-soundcloud-instagram</guid><pubDate>Wed, 11 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/1b03927c03fc6c6558164d1b0bad3897/8939e/destroyed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.04950495049505%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAADLUlEQVQ4y5VTW09TWRQ+/6NieIDwICHhEghQoIVCIW2hQGkLLUgyLZcXH9RJjPPizzBRo8Y7QVuMwQdKKy3USSbRiENb4EFoixRlWsNgUqBnfa69iagxMXEnX9bZ6/Jlr7W+o8TjcTWRSJBAKpWira0tadPp9G9hY2ODOVZJyeVyfNlEJpPB3t4e+I79/X387snnD7C7uwtlaWmJfH4/ZmefUzgSplAoRIFAgO0LikQiEsFgkMLhMM3Pz9PCwgJFo1Fp5+bmTnBcEyKlt7eXBgcH4Rx0ksPhgM1mg31gAHa7Xdqenh60tbWB86DX62GxWGRMp9Ohlf1GYyeamprQ0NCA9vYOKENDQ+R0OtHX10eCbIBJrFxsE9ZqlclGo1Givr4eLS0taG1tRV1dHRobG5moka0WXV1dcLncUEwmE4mEy39dIr//MWb8PoSDQYQCAfh9T3D37m1cu34VkcUIoktRcOvgMWBxcVF+//3yJaamptDRYeQH9ELhdkin0+PW7Rt0/8FNaLUNuOIdwTmXHd1WC6an7+HNm1e/XEgymYR7eBjDIyNQDAYDiRn5fE/o9et/8OjRHTx7Oo3Zxw/x/Jkfb/9dxubmJg4ODnB4eCiRTqXx/v229ImzsrICt9sNh8P5jXDm6Qxls/+xfLawth7D2loMOzvbSKWSbHdYSp+Ry2aRSqYYafanwZrF/3v7WF5+K5dmMpmPCfU8Q7F6ob/V1Ti2t9P48DHDL3snyTKM78/R0REKhQKIju/r6+u8EBeESo4JWQ5CR4lEHLHYCj59yjJyiMVjMlm8Mp/PS6KvZIXC0UnL/LdJQoGTlodcLhIOj8cDr9eDsbExtl542Xo8f2BychITExPSPz4+LiG+z5+/gIsX/5QzlIRiy4KwubmZBLRa7Q8Qvs7OTurvt5HZYiGdXk8sYhljHRLrksxmM/VYreRwOkmpqqoiBqqrqyVqamp+ghgJk0pxGwzt0LLYa2trZUzUiPrKykqWnBaKRnOqUFFRoZ4pP6OWlpaqJSUlqkajkSgqOq32u86qDtewOjo6qnZ3d8t4WVkZx4pkTnl5uSrqhb+4uFj9AvWbLalJwr7bAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;destroyed&quot;
        title=&quot;destroyed&quot;
        src=&quot;/blog/static/1b03927c03fc6c6558164d1b0bad3897/8939e/destroyed.png&quot;
        srcset=&quot;/blog/static/1b03927c03fc6c6558164d1b0bad3897/9ec3c/destroyed.png 200w,
/blog/static/1b03927c03fc6c6558164d1b0bad3897/c7805/destroyed.png 400w,
/blog/static/1b03927c03fc6c6558164d1b0bad3897/8939e/destroyed.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I was fortunate enough to be given the opportunity to help Moby premiere his new record via SoundCloud. I didn’t know what to expect from &lt;a href=&quot;https://twitter.com/thelittleidiot&quot;&gt;@TheLittleIdiot&lt;/a&gt;‘s latest piece of work. However, I soon learned he had created a phenomenal album with a perfectly crafted and inspiring theme: &lt;a href=&quot;http://destroyed.moby.com&quot;&gt;Destroyed&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;i don’t sleep very well when i travel. and as a result, i tend to be awake in cities when everyone else is asleep. that’s where this album, and the pictures that accompany it come from. it was primarily written late at night in cities when i felt like i was the only person awake (or alive), a soundtrack for empty cities at 2 a.m, at least that’s how i hear it. the pictures were taken on tour while i was writing the album. i wanted to show a different side of touring and traveling. a side that is often mundane, disconcerting, and occasionally beautiful.&lt;/blockquote&gt;
&lt;p&gt;ME == PUMPED&lt;/p&gt;
&lt;p&gt;I immediately started on a very simple idea: Adding Moby’s photos to a map which users could explore while experiencing the record. A good digital marketing theme cannot stand on it’s own without a firm technical foundation, so I combined three of my favorites: &lt;a href=&quot;http://polymaps.org/&quot;&gt;Polymaps&lt;/a&gt;, &lt;a href=&quot;http://instagr.am/&quot;&gt;Instagram&lt;/a&gt;, and of course &lt;a href=&quot;https://soundcloud.com/&quot;&gt;SoundCloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Rails&lt;/a&gt; as a base, I developed a very simple and clean map/player interface using &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery
&lt;/a&gt;, CSS3, and PolyMaps. A photo model was then created and &lt;a href=&quot;https://twitter.com/tomdef&quot;&gt;Tom Packer&lt;/a&gt; &amp;#x26; I added each of Moby’s photos including his captions. Then I built a basic custom SoundCloud player into the header area which pulls the Destroyed set right from Moby’s account via &lt;a href=&quot;https://developers.soundcloud.com/&quot;&gt;our api&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, we added some interaction by allowing fans to submit their own photos via Instagram. New photos tagged #destroyed are pulled in hourly via the &lt;a href=&quot;http://instagram.com/developer/&quot;&gt;Instagram API&lt;/a&gt;. And this layer of content creation is interesting because users are more likely to revisit their past photos than say a status update, constantly reminding them of the record.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Post Mortem on API Outage May 5th 2011]]></title><description><![CDATA[On Thursday May 5th 2011 we had an API outage from ~9:00 UTC to ~14:00 UTC. This was caused by a deploy that included a migration with…]]></description><link>https://developers.soundcloud.com/blog/post-mortem-on-api-outage-may-5th-2011</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/post-mortem-on-api-outage-may-5th-2011</guid><pubDate>Wed, 11 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On Thursday May 5th 2011 we had an API outage from ~9:00 UTC to ~14:00 UTC. This was caused by a deploy that included a migration with unexpected behavior and a bug in the application code. The migration ran on tables related to API applications and made these tables unavailable for the time of the migration. When the deploy was finished, a bug causing huge memory consumption brought our app servers down. We recovered by rolling back the faulty code and migrating the database down.&lt;/p&gt;
&lt;p&gt;After the API was running again, we discovered that there was an inconsistency in the OAuth 2 token data. This caused all tokens to be invalid. It took us until ~00:00 UTC to restore the correct state of the tokens. During this time it was possible to create new valid tokens, so even though some users where confused that they had to log-in new in apps, the API was fully functional. No data was lost during these events.&lt;/p&gt;
&lt;p&gt;We apologize for the outage. In the future we will test migrations more thoroughly and take an extra effort to make sure that they do not conflict with API operations.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Introducing the Large Hadron Migrator]]></title><description><![CDATA[Rails style database migrations are a useful way to evolve your data schema in an agile manner. Most Rails projects start like this, and at…]]></description><link>https://developers.soundcloud.com/blog/introducing-the-large-hadron-migrator-3</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/introducing-the-large-hadron-migrator-3</guid><pubDate>Wed, 04 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rails style database migrations are a useful way to evolve your data schema in an agile manner. Most Rails projects start like this, and at first, making changes is fast and easy.&lt;/p&gt;
&lt;p&gt;That is until your tables grow to millions of records. At this point, the locking nature of &lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt; may take your site down for an hour our more while critical tables are migrated. In order to avoid this, developers begin to design around the problem by introducing join tables or moving the data into another layer. Development gets less and less agile as tables grow and grow. To make the problem worse, adding or changing indices to optimize data access becomes just as difficult.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side effects may include black holes and universe implosion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are few things that can be done at the server or engine level. It is possible to change default values in an &lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt; without locking the table. The InnoDB Plugin provides facilities for online index creation, which is great if you are using this engine, but only solves half the problem.&lt;/p&gt;
&lt;p&gt;At SoundCloud we started having migration pains quite a while ago, and after looking around for third party solutions [0] &lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;2&lt;/a&gt;, we decided to create our own. We called it &lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;Large Hadron Migrator&lt;/a&gt;, and it is a gem for online ActiveRecord migrations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3093/2844971993_17f2ddf2a8_z.jpg&quot; alt=&quot;LHC&quot;&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Large_Hadron_Collider&quot;&gt;The Large Hadron collider at CERN&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The idea&lt;/h2&gt;
&lt;p&gt;The basic idea is to perform the migration online while the system is live, without locking the table. Similar to OAK (online alter table) &lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;2&lt;/a&gt; and the facebook tool [0], we use a copy table, triggers and a journal table.&lt;/p&gt;
&lt;p&gt;We copy successive ranges of data from the original table to a copy table and then rename both at the end. Since UPDATE, DELETE and CREATE statements continue to hit the original table while doing this, we add triggers to capture these changes into a journal table.&lt;/p&gt;
&lt;p&gt;At the end of the copying process, the journal table is replayed so that none of these intervening mutations are lost.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;Large Hadron&lt;/a&gt; is a test driven Ruby solution which can easily be dropped into an ActiveRecord migration. It presumes a single auto incremented numerical primary key called id as per the Rails convention. Unlike the twitter solution [1], it does not require the presence of an indexed updated_at column.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;Large Hadron Migrator&lt;/a&gt; is currently implemented as a Rails ActiveRecord Migration.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AddIndexToEmails&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LargeHadronMigration&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;up&lt;/span&gt;&lt;/span&gt;
    large_hadron_migrate &lt;span class=&quot;token symbol&quot;&gt;:emails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:wait&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;table_name&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      execute &lt;span class=&quot;token string&quot;&gt;%Q{
        alter table %s
          add index index_emails_on_hashed_address (hashed_address)
      }&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; table_name
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Migration phases&lt;/h2&gt;
&lt;p&gt;LHM runs through the following phases during a migration.&lt;/p&gt;
&lt;h3&gt;1. Get the maximum primary key value for the table&lt;/h3&gt;
&lt;p&gt;When starting the migration, we remember the last insert id on the original table. When the original table is copied into the new table, we stop at this id. The rest of the records will be found in the journal table – see below.&lt;/p&gt;
&lt;h3&gt;2. Create new table and journal table&lt;/h3&gt;
&lt;p&gt;The two tables are cloned using &lt;code class=&quot;language-text&quot;&gt;SHOW CREATE TABLE&lt;/code&gt;. The journal table has an extra action field (update, delete, insert).&lt;/p&gt;
&lt;h3&gt;3. Activate journalling with triggers&lt;/h3&gt;
&lt;p&gt;Triggers are created for each of the action types ‘create’, ‘update’ and ‘delete’. Triggers are responsible for filling the journal table.&lt;/p&gt;
&lt;p&gt;Because the journal table has the same primary key as the original table, there can only ever be one version of the record in the journal table.&lt;/p&gt;
&lt;p&gt;If the journalling trigger hits an already persisted record, it will be replaced with the latest data and action. &lt;code class=&quot;language-text&quot;&gt;ON DUPLICATE KEY&lt;/code&gt; comes in handy here. This insures that all journal records will be consistent with the original table.&lt;/p&gt;
&lt;h3&gt;4. Perform alter statement on new table&lt;/h3&gt;
&lt;p&gt;The user supplied &lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt; statement(s) or index changes are applied to the new table. Our tests using InnodDB showed this to be faster than adding the indexes at the end of the copying process.&lt;/p&gt;
&lt;h3&gt;5. Copy in chunks up to max primary key value to new table&lt;/h3&gt;
&lt;p&gt;Currently InnoDB aquires a read lock on the source rows in &lt;code class=&quot;language-text&quot;&gt;INSERT INTO... SELECT&lt;/code&gt;. LHM reads 35K ranges and pauses for a specified number of milliseconds so that contention can be minimized.&lt;/p&gt;
&lt;h3&gt;6. Switch new and original table names and remove triggers&lt;/h3&gt;
&lt;p&gt;The orignal and copy table are now atomically switched with &lt;code class=&quot;language-text&quot;&gt;RENAME TABLE original&lt;/code&gt; TO &lt;code class=&quot;language-text&quot;&gt;archive_original&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;copy_table&lt;/code&gt; TO &lt;code class=&quot;language-text&quot;&gt;original&lt;/code&gt;. The triggers are removed so that journalling stops and all mutations and reads now go against the original table.&lt;/p&gt;
&lt;h3&gt;7. Replay journal: insert, update, deletes&lt;/h3&gt;
&lt;p&gt;Because the chunked copy stops at the intial maximum id, we can simply replay all inserts in the journal table without worrying about collisions.&lt;/p&gt;
&lt;p&gt;Updates and deletes are then replayed.&lt;/p&gt;
&lt;h2&gt;Potential issues&lt;/h2&gt;
&lt;p&gt;Locks could be avoided during the copy phase by loading records into an outfile and then reading them back into the copy table. The facebook solution does this and reads in 500000 rows and is faster for this reason. We plan to add this optimization to LHM.&lt;/p&gt;
&lt;p&gt;Data is eventually consistent while replaying the journal, so there may be delays while the journal is replayed. The journal is replayed in a single pass, so this will be quite short compared to the copy phase. The inconsistency during replay is similar in effect to a slave which is slightly behind master.&lt;/p&gt;
&lt;p&gt;There is also caveat with the current journalling scheme; stale journal ‘update’ entries are still replayed. Imagine an update to the a record in the migrated table while the journal is replaying. The journal may already contain an update for this record, which becomes stale now. When it is replayed, the second change will be lost. So if a record is updated twice, once before and once during the replay window, the second update will be lost.&lt;/p&gt;
&lt;p&gt;There are several ways this edge case could be resolved. One way would be to add an UPDATE trigger to the main table, and delete corresponding records from the journal while replaying. This would ensure that the journal does not contain stale update entries.&lt;/p&gt;
&lt;h2&gt;Near disaster at the collider&lt;/h2&gt;
&lt;p&gt;Having scratched our itch, we went ahead and got ready to roll schema and index changes that would impact hundreds of millions of records across many tables. There was a backlog of changes we rolled out in one go.&lt;/p&gt;
&lt;p&gt;At the time, our MySQL slaves were regularly struggling with their replication thread. They were often far behind master. Some of the changes were designed to relieve this situation. Because of the slave lag, we configured the LHM to add a bit more wait time between chunks, which made the total migration time quite long. After running some rehersals, we agreed on the settings and rolled out to live, expecting 5 – 7 hours to complete the migrations.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e8a883f8e52683da109209f5341d150c/48a11/958035425_abb70e79b1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAIDBQH/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAcu+U9TPWjkiqxH/xAAbEAACAwADAAAAAAAAAAAAAAABAgADERASMf/aAAgBAQABBQJE7FqcGSlgCiLSD6JvH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABsQAAIBBQAAAAAAAAAAAAAAAAERAAIQEiAi/9oACAEBAAY/AowjerrJ6//EABkQAQEBAQEBAAAAAAAAAAAAAAERACExEP/aAAgBAQABPyFZBkVIcY2Z63Z+OQfLAkyMWV8//9oADAMBAAIAAwAAABC88H//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAWEQADAAAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8QI//EABsQAQEBAQEAAwAAAAAAAAAAAAERACExQVFh/9oACAEBAAE/ECly+YwKuFT6Z5lIm9zginpeY+WIkAtr+5iTMPHIOuq/O//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;958035425 abb70e79b1&quot;
        title=&quot;958035425 abb70e79b1&quot;
        src=&quot;/blog/static/e8a883f8e52683da109209f5341d150c/48a11/958035425_abb70e79b1.jpg&quot;
        srcset=&quot;/blog/static/e8a883f8e52683da109209f5341d150c/f544b/958035425_abb70e79b1.jpg 200w,
/blog/static/e8a883f8e52683da109209f5341d150c/41689/958035425_abb70e79b1.jpg 400w,
/blog/static/e8a883f8e52683da109209f5341d150c/48a11/958035425_abb70e79b1.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Several hours into the migration, a critical fix had to be deployed to the site. We rolled out the fix and restarted the app servers in mid migration. This was not a good idea.&lt;/p&gt;
&lt;p&gt;The lesson: never restart during migrations when removing columns with large hadron. You can restart while adding migrations as long as active record reads column definitions from the slave.&lt;/p&gt;
&lt;p&gt;The information below is only relevant if you want to restart your app servers while migrating in a master slave setup.&lt;/p&gt;
&lt;p&gt;If you anticipate the need to restart your application servers while migrating, then check the comments in &lt;code class=&quot;language-text&quot;&gt;large_hadron_migrator.rb&lt;/code&gt; for detailed instructions on how to do this.&lt;/p&gt;
&lt;h2&gt;Future work&lt;/h2&gt;
&lt;p&gt;The following improvements should be made at some point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Loading data into outfile instead of &lt;code class=&quot;language-text&quot;&gt;INSERT INTO... SELECT&lt;/code&gt;. Avoid contention and increase speed.&lt;/li&gt;
&lt;li&gt;Handle invalidation of ‘update’ entries in journal while replaying. Avoid stale update replays.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some other optimizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deletions create gaps in the primary key id integer column. LHM has no problems with this, but the chunked copy could be sped up by factoring this in. Currently a copy range may be completely empty, but there will still be a &lt;code class=&quot;language-text&quot;&gt;INSERT INTO... SELECT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Records inserted after the last insert id is retrieved and before the triggers are created are currently lost. The table should be briefly locked while id is read and triggers are applied.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Footnotes
[0] &lt;a href=&quot;http://www.facebook.com/note.php?note_id=430801045932&quot;&gt;Facebook Online Schema Change&lt;/a&gt;
[1] &lt;a href=&quot;https://github.com/freels/table/_migrator&quot;&gt;Twitter Table Migrator&lt;/a&gt;
&lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;2&lt;/a&gt; &lt;a href=&quot;http://openarkkit.googlecode.com/svn/trunk/openarkkit/doc/html/oak-online-alter-table.html&quot;&gt;OAK online alter table&lt;/a&gt;
&lt;a href=&quot;https://github.com/soundcloud/large-hadron-migrator&quot;&gt;The Large Hadron Migrator on Github&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Web Scale Statistics – Failing with MongoDB]]></title><description><![CDATA[As SoundCloud rapidly grows our initial systems need an overhaul. Our scaling strategy has been very realistic, design for 10x our current…]]></description><link>https://developers.soundcloud.com/blog/web-scale-statistics-failing-with-mongodb</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/web-scale-statistics-failing-with-mongodb</guid><pubDate>Thu, 28 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As SoundCloud rapidly grows our initial systems need an overhaul. Our scaling strategy has been very realistic, design for 10x our current usage. Our initial statistics system found under &lt;a href=&quot;https://soundcloud.com/you/stats&quot;&gt;https://soundcloud.com/you/stats&lt;/a&gt; was made when we were 100k users, living long past its expiration date.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;About a year ago we started off with the goal of redesigning the statistics pages to support 500 playbacks a second. We knew that this would be a write-heavy workload and that to sustain bursts of writes, we’d need decent partitioning. Coming from a successful experience moving the Dashboard feature to &lt;a href=&quot;http://cassandra.apache.org/&quot;&gt;Cassandra 0.6&lt;/a&gt; we started out prototyping a design that would be easily partitioned.&lt;/p&gt;
&lt;p&gt;The write side of this story went very well, Cassandra could keep up with everything we threw at it, however to naively pull out all the aggregates we were collecting took hundreds of queries to the cluster. Cassandra didn’t have atomic counters at the time, so we had a lot of individual counts that needed to be summed on the client. (This is changing with the much anticipated &lt;a href=&quot;http://wiki.apache.org/cassandra/Counters&quot;&gt;upcoming 0.8 release&lt;/a&gt;!)&lt;/p&gt;
&lt;p&gt;In a one-night experiment, we re-implemented the Cassandra based prototype to be backed by &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt;. Not only could this quick prototype consume events as fast as Cassandra, there were some server side features in MongoDB that we could use to simplify a few of the queries that we had for the stats like atomic inplace insert/updates (&lt;a href=&quot;http://www.mongodb.org/display/DOCS/Updating&quot;&gt;upserts&lt;/a&gt;) to use fewer documents and &lt;a href=&quot;http://www.mongodb.org/display/DOCS/Indexes&quot;&gt;secondary indexes&lt;/a&gt; to build the time series. &lt;em&gt;Plus&lt;/em&gt; it was &lt;a href=&quot;http://en.wikipedia.org/wiki//dev/null&quot;&gt;web scale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To answer the partitioning problem for MongoDB, we decided to lean on the &lt;a href=&quot;http://www.mongodb.org/display/DOCS/Sharding+Introduction&quot;&gt;automatic sharding&lt;/a&gt; router that was under development. This was built to automate the rebalancing of data between replication pairs and keep a cluster nice and healthy without much data administration.&lt;/p&gt;
&lt;p&gt;Away we went, implementing a correct, distributed key-value oriented MongoDB based statistics backend. We even deployed it into a cluster of Amazon EC2 instances and hooked it up to the website to start tracking our statistics alongside our existing solution.&lt;/p&gt;
&lt;p&gt;What we observed was disconcerting. On a 36GB RAM machine with a working set under 100 GB, the system performed better than needed. A single node could process thousands of plays per second. Once the working set approached 300 GB, the throughput dropped down to between 100 and 200 plays per second. The disk utilization of one of the shards in the cluster stayed at 100% and we were seeing a IO service latency of around 5ms and MongoDB latency spiking upwards to 15 seconds. This all pointed to becoming bound on disk seeks.&lt;/p&gt;
&lt;h2&gt;Seeking the answer&lt;/h2&gt;
&lt;p&gt;Being bound on IO was something that we anticipated as we would not need to keep the entire working set resident, but not this bad. Whatever that MongoDB’s sharding was doing was causing a single node in the cluster to bottleneck. Could it be those poor disk heads bouncing back and forth to support our write load?&lt;/p&gt;
&lt;p&gt;For any workload, a disk seek is the worst thing one could be spending time on. In &lt;a href=&quot;http://norvig.com/21-days.html#answers&quot;&gt;“Numbers Everyone Should Know”&lt;/a&gt; it’s obvious we can do better:&lt;/p&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;execute typical instruction&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1/1,000,000,000 sec = 1 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetch from L1 cache memory&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;0.5 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;branch misprediction&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetch from L2 cache memory&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;7 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutex lock/unlock&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;25 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetch from main memory&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;100 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;send 2K bytes over 1Gbps network&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;20,000 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;read 1MB sequentially from memory&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;250,000 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetch from new disk location (seek)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;8,000,000 nanosec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;read 1MB sequentially from disk&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;20,000,000 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;send packet US to Europe and back&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;150 milliseconds = 150,000,000 nanosec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The stress to get the stats released was mounting. Our existing solution was unusable for almost all of our active users and was actually causing a ripple effect in the databases that caused site performance degradation when anyone went to visit their stats page. And we had overshot our release goal already by 2 months.&lt;/p&gt;
&lt;p&gt;We struggled hard to identify the root cause for why we had a hotspot in the MongoDB cluster. If we could just distribute the seek-bound workload, we could at least release this iteration of the stats while we dug into the root cause of being seek bound in the first place.&lt;/p&gt;
&lt;p&gt;To identify the root cause, we tried confirming various theories about the workload we were introducing and the behavior of MongoDB. Each experiment was time consuming but we built up an understanding of how the system was (mis)behaving.&lt;/p&gt;
&lt;h2&gt;Death by design&lt;/h2&gt;
&lt;p&gt;The design of this system was fairly simple – create a document for each time aggregate that we needed to display in a time series. Use varying time spans to reduce the number of aggregates we would need to fetch and sum in the client. We used &lt;em&gt;year&lt;/em&gt;, &lt;em&gt;month&lt;/em&gt;, &lt;em&gt;day&lt;/em&gt; and &lt;em&gt;hour&lt;/em&gt; aggregation documents for each dimension of data we wished to track. This includes the total play counts, by user, country and source URL. Send these aggregates to a [mongos][mongo-mongos] node that would then shard the documents and distribute them to one of 4 backing replication pairs.&lt;/p&gt;
&lt;p&gt;The last theory is that when a track is first recorded, the 4 aggregate documents (year, month, day, hour) are written to the end of each collection. All 4 documents would end up on the right side of the tree and the right side on disk, because our understanding of the MongoDB disk layout goes as far as being a memory mapped BTree of the actual &lt;a href=&quot;http://bsonspec.org/&quot;&gt;BSON&lt;/a&gt; documents. As time passes, the longer duration documents, year and month, would start to become relatively shifted to the left of the disk storage, where the newer hours and days would end up on the right. Since MongoDB’s persistence was essentially a bunch of memory mapped files, this could have caused all the page faults loading up cold pages from the middle/left of the tree when attempting to update all aggregation buckets.&lt;/p&gt;
&lt;p&gt;We are still uncertain about how the documents were layed out in memory and on disk, how it grew, how it was mapped to disk and how the indexes resolved to documents. If the on-disk layout was by the random ID then we were screwed on writes and reads because they would both be random. If it was by time, we were screwed on reads as we’d get sparse reads over the time series, if it was on our bucket organization we were screwed in the future as our buckets locality would get sparser over time.&lt;/p&gt;
&lt;h2&gt;Coming to terms&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 631px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/8d5ebb59a8a6d9f3dc083e3a76aca8a9/eef7a/IO-doing-it-wrong.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.10776545166404%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAADHklEQVQ4y12T329TZRjHT9dfazfaWfvz9LQ9PWs7zmy7jpXNbsAWYGNsUEbphiIbRombQUTcwsgujHFChASCPwJy4ZXohdGgIWpMTLz1ygvv/Vs+PuecljgvvnmfvHmfz/t9nvd9FLc7gMcTwOfrx+ffh9fnyOPt76jPWWWvx9OH4gqg9ARx94bwBsIEggP4Jc/hBFGswAKUiwWmKiVmaibz42XOTI3Snj7Iq8cbrM452r3c5Okn6/xw603unJrgkpFhKprAVLN4/SEbKsBecRaiVsozN6KzND7ExekK6yfqXFlscP3sEd5tTnLlZJ3f774Bfz/kn/sXeHQgxc2SxpftBsujJRSvVCWs58CXDJ1GKU1VT1JMR6nkEjTrJVanqyxPDtN+2eSrGyv89fUOz+5s8OPuBr99+j7fb7eYyaWlBeG9wPKgzpAWJRYJE4+EiL2wT8AxVqYqnKoPsXCgyJmGyZOPN/jps23+/OY2Pz/cYfetJmOS65T8H6CZ1xnOJkhHByhqcfZnk8QFemhYl1ZIf/fnaJg6T+9d44/H2zz5aJ1fPt/i2w/XpLICbushuz206JWCway4mJHklekakwLS4hEaAjpk5qjmUjTKBg+ur7B2tMLj7VVuXV7g7ZM1Roz8Xod+qb+YzdA6XOW7u5s82Hqd09K3I9VBuT3LwYJGOZNgTOIb5w9z/50lfv1ik/dak3xw6YTkZu0qHWCPAxzMSFIuydXzi6y3jjM3VrKd1WRvxFaCfCwsF6hcnKtzdWGCnWNlrs2U0aJxegMDWKznwJK8cklc6MkX7f6ZGUcVPcWooVIfTDOsxTCkx5H+fhbSEW6PZXl2rsLauCnfJtR9ZfnYMg3xWIpUQkVNqiTjnbijrJqxlZZYS3b2RGUtzYShUVRVe4rsR7HGxYK6XH4URSSryyU3KV6RS6Rwc3OL1165YMeK4hH57HP2+Y4smLs7eha0K683SI+0YSAcJa8XSCY02kstZo/OShUquaxBQGbYqSyIT+S1TFn5XYd7gX22Cz1XYPnsORvUXDzN/Oy8rbbsJeQSy+X/cy39Cz5on54Pw/RuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;IO doing it wrong&quot;
        title=&quot;IO doing it wrong&quot;
        src=&quot;/blog/static/8d5ebb59a8a6d9f3dc083e3a76aca8a9/eef7a/IO-doing-it-wrong.png&quot;
        srcset=&quot;/blog/static/8d5ebb59a8a6d9f3dc083e3a76aca8a9/9ec3c/IO-doing-it-wrong.png 200w,
/blog/static/8d5ebb59a8a6d9f3dc083e3a76aca8a9/c7805/IO-doing-it-wrong.png 400w,
/blog/static/8d5ebb59a8a6d9f3dc083e3a76aca8a9/eef7a/IO-doing-it-wrong.png 631w&quot;
        sizes=&quot;(max-width: 631px) 100vw, 631px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Not reasoning hard about this system’s IO patterns was a very late, and very obvious oversight. Even if we could partition the writes, we had designed a system that would require close to 100% in-memory residence to be performant over time. This is a very expensive proposal for mostly stale and very large data.&lt;/p&gt;
&lt;p&gt;Lessons learned, we faced and made the difficult decision to cancel this project as it could not meet our long-term goals under real workload.&lt;/p&gt;
&lt;p&gt;Yet the stress is still there, the SoundCloud statistics must work for all users, especially the people with millions of plays.&lt;/p&gt;
&lt;p&gt;Up soon – what we made instead (for now).&lt;/p&gt;
&lt;p&gt;Not reasoning hard about this system’s IO patterns was a very late, and very obvious oversight. Even if we could partition the writes, we had designed a system that would require close to 100% in-memory residence to be performant over time. This is a very expensive proposal for mostly stale and very large data.&lt;/p&gt;
&lt;p&gt;Lessons learned, we faced and made the difficult decision to cancel this project as it could not meet our long-term goals under real workload.&lt;/p&gt;
&lt;p&gt;Yet the stress is still there, the SoundCloud statistics must work for all users, especially the people with millions of plays.&lt;/p&gt;
&lt;p&gt;Up soon – what we made instead (for now).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Marbleo.us]]></title><description><![CDATA[Greetings! I’m Robb and this is my first SoundCloud Backstage blog post. During the day I’m a developer working on the Mac App here in the…]]></description><link>https://developers.soundcloud.com/blog/marbleo.us</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/marbleo.us</guid><pubDate>Thu, 28 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Greetings! I’m Robb and this is my first SoundCloud Backstage blog post. During the day I’m a developer working on the Mac App here in the SoundCloud office, but I’m also a university student. It was through Uni that I found out about and entered the annual informatiCup competition with my friend &lt;a href=&quot;http://simon-hohberg.de&quot;&gt;Simon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/a854ce086a2793cd85a8e2bfb8d11604/8939e/marbleous-screenshot1-505x481.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 95.24752475247524%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAAD7UlEQVQ4y5WTbVMbVRTH8y2c8Tv4ym/Q1ZfOdHzRyFORiBFaRgfbamlFHKt2lMxoS6EWGqpimQKlJaWhkDQPUEhICoSEPBFCkm4CIc8QQh42u/fvZpNgcBynnpn/nN0z9/7uOeeeK5IP33tL+Wz255dmc+/a2lqvy+WSmUwmmVqtPpZKpTrWnGpOZtIsy2zP12Um/YpMbbH0aq22n9R6/Y2x0dG3RUqlssnn98PPKxKJ4vDwUFAymUQqleJ9AolEXPhP8Eon0zDFXmIiqoDBr0V8ZgaRGQUO6FcYn1K0i7xe75kSgKbpQiQSYfK5HAPgP2XMLjNDRyPMwo6SyU9MMOkHI/lieBeTan2LaGNjQ4yycbxI6YMQAsJxQpC2zsJnGCkv4Ag4wqLWSHmPEBx/NC0RORwOcQXC8UYEWEUlszzugebGaaRCdlQPY3lokRTBcixIscifwgrAiSnl38BSAqRsJ4AbMz9Ce0sM41ADQub7lUx5UEnldccZvhbQOv0D5n+tx+JgA7bnvvtnuf8DyJV7ZXt6HfqBD2CUNyFu6kfuYA+B+T5kaCNP414DyF9GFVaydcU1aAfqsDRYj5j5DqJeAzS/vAfn/UYUYo7qvfwLkEeRap28ZWI+hBZvY2O0FebfJDDJGxEz9iHiWYLudgMflyAXXi8DKxmUgXb7ibFh00Fs6fqh7a+H489GvJo6B8/DNugGSpCPsTX9JeYHP4R9tAW5PVu1lTVAt1tc6kVh08llUyGyeq8eq0PvwzzcAIO8GVuT7bD80YKFO00IKM7DPS6Fpu8MbL/XIRu2gGMIyTssNXO46RHzcwRWN8tx2QyhbSpsq68jaLiLF3IpFofOwvu4AysjUj7bNoQNt+BbeYItZQ8KmV2QWIxwL56XMxSAtSWT8kvhKp30LAxDe/M0NsckMN5tgG+8CQnrg0rLK6PDssclj9W+lOot184ZxzII2WYRczxFcO0RDrw6sEzu5BzW9PCZSiMR2StArpgVLiV/mCRFJk+YfIbks4fCHqaQFzzHFgVfOErxawqEsHycLRwDV9csEtGmNyAmhX2ktzUckw4R45ObZJ9eJZsmBQlapsmOe4kEVyZIcsdJ3Pphkj8IEs/yQ3IUdRLn0iTZdelINGBhs4kA7C4vX7LTVZfLZkBvWbEX3oHPY0NsLwiXYx2RHR8C/m2E/A4cpOKIBl3IpBPYo93IpmMIB72I7/oQDdMoZPdhXlltFbV9In2j49POd5qlnaeaW89R0o6LVKOE9+cvUI0fdVCt7Z9RkrZO6uIXXdTVnu+pC5cuU1/x/vNLXVT3N9eoK93fUl1Xvz51+Ur3u83NZ9/8C767Ci3cu6WjAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marbleous screenshot1 505x481&quot;
        title=&quot;marbleous screenshot1 505x481&quot;
        src=&quot;/blog/static/a854ce086a2793cd85a8e2bfb8d11604/8939e/marbleous-screenshot1-505x481.png&quot;
        srcset=&quot;/blog/static/a854ce086a2793cd85a8e2bfb8d11604/9ec3c/marbleous-screenshot1-505x481.png 200w,
/blog/static/a854ce086a2793cd85a8e2bfb8d11604/c7805/marbleous-screenshot1-505x481.png 400w,
/blog/static/a854ce086a2793cd85a8e2bfb8d11604/8939e/marbleous-screenshot1-505x481.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Although we didn’t make it into the final around, I consider the project we made – a web-based marble run simulator called Marbleo.us – to be a success. &lt;a href=&quot;http://marbleo.us/#GQIAAP8AAAYvAgAA_wAAFDwAAAA_AAAABwICAP8AAAQAAAAAAAAAABgCAgD_AAALAQEAAP8AAAYpAQAAGgcIAf8AAAU_AAAAPwEAAP8AAAU_AAAAPwEAAP8AAAU_AAAAKwAAAB4CBQD_AAAEAAAAAAAAAAAwBwgA_wAABAAAAAAAAAAAAAAAACwHCQD_AAADAQEAAP8AAAYpAQAA_wAAIgAAAAAAAAAAAAAAAAABAAD_AAADKQEAAP8AAAYpAQAA_wAAKQEBAAD_AAAGKQEAAP8AACkBAQAAFAAAARUAAAAVAAAAFQAAABUAAAAFBwkAKQEAABYAAAEVAAAAFQAAABUAAAAFBwkA_wAAARUAAAAVAAAAFQAAABUAAAAVAAAABQIGAP8AAA8AAAAAAAAAAAAAAAAAAAAAFAEAAP8AAAIAAAAAAAAAAAAAAAAAAAAAMAIAAP8AAAIFAgAA_wAABjECAAAA=&quot;&gt;Here’s a fun example map to try it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So let me give you a quick overview of what we did, how we did it and what I’ve learnt building Marbleo.us.&lt;/p&gt;
&lt;h2&gt;No data store is faster than no data store.&lt;/h2&gt;
&lt;p&gt;Early on, we decided to give users the ability to share their creation with their friends. However, learning from the popular sandbox game Minecraft, we wanted to have a counter measure against what is called griefing, malicious players destroying your creation.&lt;/p&gt;
&lt;p&gt;After considering different data stores and ways to handle both read-only and read-write links to the maps, we came up with a far more elegant solution: encoding the whole map into the fragment of the URL.&lt;/p&gt;
&lt;p&gt;Writing the whole application in client side Javascript also alleviated the need for a server-side persistence layer, so all we needed was a way to serve a bunch of static files.&lt;/p&gt;
&lt;h2&gt;Standing on the shoulders of github&lt;/h2&gt;
&lt;p&gt;We are using git, which is unfortunately not used often in our academic circles, but it meant the natural choice for hosting our repository was &lt;a href=&quot;https://github.com/robb/Marbleo.us&quot;&gt;github&lt;/a&gt;. Using the awesome github-pages feature, we can also serve &lt;a href=&quot;http://marbleo.us&quot;&gt;http://marbleo.us&lt;/a&gt; from their servers at no additional cost and with great response times.&lt;/p&gt;
&lt;h2&gt;I like my sugar with coffee and cream …script&lt;/h2&gt;
&lt;p&gt;Only a short time before the start of this project came I across &lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript&lt;/a&gt; – thanks to fellow SoundCloud developer &lt;a href=&quot;http://dmytri.info/&quot;&gt;Dmytri&lt;/a&gt; for tipping me off.&lt;/p&gt;
&lt;p&gt;CoffeeScript is a lean, mean language that compiles to easy to read Javascript.
More than just syntactic sugar, it is a really beautiful way to express your intentions and you can tell that its creator, &lt;a href=&quot;https://github.com/jashkenas&quot;&gt;Jeremy Ashkenas&lt;/a&gt; is a firm believer in code as literature.&lt;/p&gt;
&lt;p&gt;Jeremy also wrote backbone.js which, amongst other things, powers the
&lt;a href=&quot;https://m.soundcloud.com/&quot;&gt;SoundCloud mobile site&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Drawing the marble run&lt;/h2&gt;
&lt;p&gt;After briefly investigating CSS-3 transformations like in &lt;a href=&quot;http://cssatoms.com/miscellaneous/create-a-3d-cube-in-pure-css3/&quot;&gt;this article&lt;/a&gt; for composing the map, I decided to go with a more straightforward approach by drawing the maps onto a canvas.&lt;/p&gt;
&lt;p&gt;A single block is then stitched together from multiple components, cached, and placed on the canvas at its correct position in space. The individual components are all stored in &lt;a href=&quot;https://github.com/robb/Marbleo.us/blob/master/src/img/textures.png&quot;&gt;one file&lt;/a&gt; to minimize requests.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/2fede6763a7fefa0614863d0b3328cdd/8939e/marbleous-compositing1-505x101.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAA50lEQVQY022PTUvDQBCG81P9Ef4OPYmIHrzpqV68FAIak5uKEKEfSbWUUrXSbNNmk6bd3TzuRtBL5zDzMh/PzHhN02CMQWuN0/vsP9+g8zeUSK00f/XP9BHxkbTac87B6rpuE7KsWMxGxPdXrMWcZb6ilNLGNYtRSPZ0howvKCYh5UbxlYTcHB1wd3mIKqZ47jppB4QQFrppAe9xQNA5IZv2CB4iBv0ehV00ebll6B+TPZ8jBl0L3BF1r/E7p+Spj5wP8ZRS7XUOXFXV3pfdB87UtuI7CViNI8zut3dblyxnr9RijNGKH7lHKyakpndsAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marbleous compositing1 505x101&quot;
        title=&quot;marbleous compositing1 505x101&quot;
        src=&quot;/blog/static/2fede6763a7fefa0614863d0b3328cdd/8939e/marbleous-compositing1-505x101.png&quot;
        srcset=&quot;/blog/static/2fede6763a7fefa0614863d0b3328cdd/9ec3c/marbleous-compositing1-505x101.png 200w,
/blog/static/2fede6763a7fefa0614863d0b3328cdd/c7805/marbleous-compositing1-505x101.png 400w,
/blog/static/2fede6763a7fefa0614863d0b3328cdd/8939e/marbleous-compositing1-505x101.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Additionally, a second, smaller canvas is used to allow the use to drag a stack of blocks freely across the browser window without doing costly redraws at every mouse position.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/421ecb6f70272299e453ac722c2b48ae/8939e/marbleous-canvases1-505x456.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.2970297029703%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAADgElEQVQ4y42U60+aZxTA+d/2ZVmydI2xRaygrQsm26dl/bZs3T4s2ZJdTGtriVJ7WfGCdNrWau1qvXCpSrHqKFJEURQBgRfk4svtt+d9oToXt+4kJzzPec/ze85zOOdogskg+7l9otkYsSONEi9IJOUU+4dpsY//7duxKmeUs5upTQqlAopoFMNpUpS2mR/4ml33UM1Q5V8lLS7NF/M1oHKTIpVKhUpVaLmk7oPz/Tz76QNe3dVTKWZV3pHPOxV7RZL55ClA4VCtVoVTWd2H3DZcvVoe/HCWlZFvqCaXRZQl1eedKmdqwAR5Ofc+4DAvuj7BeUvPy95zOK9/RNQ7VvMtK+DKEVASLyhUq/8HeBZ7rx672YDjZgORpcET6VRx8TgJzySZV1aIhN4PdJj1zPW0EBhqIesfVb/JmSjFzE5t7XGw0N3CxnAjObflv4BWpq+LCOtAn0XHQWCMQibGsqUd34CBUn4P+c0SzhvNrFn1ZFx9J4EKrFyuAbeWRnh+9QyOWwYVuGZpIhccJ7m7xpx4ftDawmHST3FlEZcCFC84eHnnGFiul4Ai2fAivpHLzJt1TJuaaxHe11LYmiIdD2E3nWfT1oYsBZBX3bi6L+AXEarAaB2oSCzgwDv6lTjchK//AmsDbfiGLql5XLe2sue8RlT4OHqaRM4MyKm3FL0e5q5p8fY3CuBtNIl8jGL0NYGJK0z8/DEzolSCtna2H3YQfmRkXaztPc1MdjYIqAD/bhSlZBBAPQVpA6IJNp6aCf9xlfLGKhrPs+94a9Gy0NPI/O1WFu9dZL5PRDd0kchYB9NdjUzdOM/yb3oi45/jF/ZZk5adRx3k/uyDUo5MMcdhqdZhGsfEjzhFBKv3dYRHWpntbuBFt1bkTdgsBgLDl3CaRdk8MLL72EjoyWUW+r9Qiz00eIYDr5X9Qk50ykF9OBxKZPbW8T/vRFp7QnhlHNeddmZMOhbvthJ+/JkobpF0m5Gdh+3EnL8iyzLBBRvb9i4qmQipw4zo5XrrRQ+iR39Kud4Cr0e/ZfKXD3HfayM00s7szXMqcNumIzLzPaXKyWlTGw7/HF9KL9aB+f0Qb5524hERxqau4Bn8koD1UyS3iVJq63icVU8ZXxtiOKYLafWWZD6l/qZFoiXxhHDYQywZYi+xxa5YS3KWlLBLhRR+YV+JB/AlNnFHVsnVgX8BEV3ljB6cXicAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marbleous canvases1 505x456&quot;
        title=&quot;marbleous canvases1 505x456&quot;
        src=&quot;/blog/static/421ecb6f70272299e453ac722c2b48ae/8939e/marbleous-canvases1-505x456.png&quot;
        srcset=&quot;/blog/static/421ecb6f70272299e453ac722c2b48ae/9ec3c/marbleous-canvases1-505x456.png 200w,
/blog/static/421ecb6f70272299e453ac722c2b48ae/c7805/marbleous-canvases1-505x456.png 400w,
/blog/static/421ecb6f70272299e453ac722c2b48ae/8939e/marbleous-canvases1-505x456.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We also save yet another representation of the marble run, to quickly determine face of a block is under the current mouse position.
This also allows us to draw a cute little hand cursor whenever the user places the mouse over a block with pixel precision.&lt;/p&gt;
&lt;p&gt;We simply compile a map using a special, color-coded sprite and then quickly look up what color (if any) is stored at the mouse position.
&lt;em&gt;Blue&lt;/em&gt; for the top, &lt;em&gt;red&lt;/em&gt; and &lt;em&gt;green&lt;/em&gt; of the block’s sides and &lt;em&gt;black&lt;/em&gt; for the floor.&lt;/p&gt;
&lt;p&gt;Now we have the run ready, here comes the marble!&lt;/p&gt;
&lt;h2&gt;Animating the marble&lt;/h2&gt;
&lt;p&gt;We use a mixed approach to move the marble over the run by discriminating between two different types of movement: free movement in space and movement on the tracks of the marble run.&lt;/p&gt;
&lt;h3&gt;Movement in space&lt;/h3&gt;
&lt;p&gt;This part of the physics engine engine was simple, by using a basic collision detection we check if the marble collided with a block and then invert the appropriate velocity components. If the marble is rolling over the top of a block, we also test if it crosses a track which leads us to the next point:&lt;/p&gt;
&lt;h3&gt;Movement on the track&lt;/h3&gt;
&lt;p&gt;For every block with its different grooves and tunnels, we defined small, directed graphs that we then join together to form the large, overall track graph.
It’s important that the nodes of this graph have a degree of at most two, so that there is no ambiguity in which node to pick.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/d68ef1999313b6614dde305e8e7e0738/8939e/marbleous-tracks1-505x487.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.43564356435644%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAAES0lEQVQ4y42Te0xTdxTH7207Y0xcovtDZ7Kn0xizYOYWxjZnpsmyzeyVzUjAPTIHCFRyb1voAwrlkYDDKckeosBgBTtugQoVKFJ5DzKEPixYWoEyilBoJSzihlBu79nv3pZuEhP9/fP75Z7z+9zv73vOwbB169mnt2KKqF04ex7RiOL7zx6fdGgIlR+uCoo+i+Qlxx7kYnw+D3usdShqD3YkdDZpUtwuTTK49Gk0AGxkvw2qkwURkXu5OI+HPxp44LVd+Kdv7ggCtaKbruZ06C6MoW0X4n4Et/qFtbzDh/Y9nsJ3kMLPX93Knc0U6XA2yqG/+AQ9qRHCTJPM723PzGdjaYefwyP2PPNo4NtI4Sf7g0CLVuQYaUiDG7pU2q5OWnFWJsFAeaIHPZ8N4y2FxwUtxXH8l57fzuULHubrG/t34h9EbOHMMWtJx4heCghMo+cH3NeyYMqodDiCwPDq+SGab9UrHl6lg5G7sQ/3bcHCwAYpWGsQsJqgPR0qmO/JGWVjExfTNo9TZMLC9YK31u62VyT9VyU81ApRr+zEyjI+wt8/GoNbKNLJKayV0INI5QwC+rqyXWy+Qy/TLHYqYVyTTC/05moXLWeCpccR6Ql+ECyNPoB3lZwUrP3EXCu2j1xOhaFLp2iLOpn2tGWFgSaKMI42K8Dya+IyOM/CfG9eaVhdkTIabz8fHwYh0/nTLRnfDlUJ79mrCbBUCpkbpfH0bKsSvN3ZY6GCGUab5GCuES/d/SMf5jpVP3OXbXp5GPSXPn/D7DXVNxP14ps+gxRmm2XgqiMZB0WAuZoIzHTmwJ2e3NlQSzWMGxRg1oru/zNQAN6u7PPY/xQJfH0FX9suxo3YimJhrkEMHoNsdVKfyqAd7nSpGFudhHHWiANTOhKmm2Q2e4PUPaSTgKVGtBICFnOwe9ZzX3oapUPTtQTYfksBe5VwFY0bPU2lMHc7MsFrTGeGS+OYoeIT0FdwFCYbpYEl02lwomIhdTQCrj6gcP73HJhrVYBLm7I8cVnsH64VB6x1EtpaJYTb6kTGVy8C209fgbUsgZlsVjC+7hzwdmavugxyGEYFQ17eX+xnPQwpHNAQxSateMmD1CybT8NYsxzs9SgRPdGqJWFcRzJzrelg0aXCuCGdQVUOLPTmwa0rsjoTJRpDO4D9HAssC3vYpyZ223Sy8imjKnNQI4o0a8VXh5E/gxRJ3zJkoMrmMGaKYFyGdEAK/X9fzwd3W+57GBbBM2sleW6jqsLdonyRg9UXHuOvn5a+S8S7qA3ARJHMaJPCj1Sx5gMC+hEwwCr6szUrZv29EsXHOLZh4yasIi+WV3vmC35febAXO345td1SIykZrCaWfT25rOE0auRV9qkIvuJpU5XZ6+Xb2Nx+dYLAShF844WTD87yk5s3cfv3oiPheeytJPc6rmR8d9uoiuosT3l9tElZiGAvr8XVuce43B3bngpz/gX0rYsPGNc8ZwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marbleous tracks1 505x487&quot;
        title=&quot;marbleous tracks1 505x487&quot;
        src=&quot;/blog/static/d68ef1999313b6614dde305e8e7e0738/8939e/marbleous-tracks1-505x487.png&quot;
        srcset=&quot;/blog/static/d68ef1999313b6614dde305e8e7e0738/9ec3c/marbleous-tracks1-505x487.png 200w,
/blog/static/d68ef1999313b6614dde305e8e7e0738/c7805/marbleous-tracks1-505x487.png 400w,
/blog/static/d68ef1999313b6614dde305e8e7e0738/8939e/marbleous-tracks1-505x487.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We then move the marble along these paths from node to node, until we encounter a node that has no neighbor we haven’t visited before. It is then that we leave the track and switch back to the previous mode of movement.&lt;/p&gt;
&lt;h2&gt;Drawing the marble&lt;/h2&gt;
&lt;p&gt;Now that we have the animation of the marble in place, we of course need to draw it, but to do that, we first have to figure out where the marble is visible.&lt;/p&gt;
&lt;p&gt;First we draw the the marble onto a blank, offscreen canvas.&lt;/p&gt;
&lt;p&gt;Then we iterate over all blocks in map that could potentially obstruct our view to the marble, drawing only the parts that are actually in the way onto another offscreen canvas.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/e7b93b68b9bf85ceb90c94d677359908/8939e/marbleous-visibility1-505x487.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.43564356435644%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAAESklEQVQ4y42Uf0xTVxTH36OdcUyXoFnELRuJTOJwykaIskzJzDIXF7dplpHoJmObblUmbSm0RSp90AUCGHRxG06wYIPQlkIp68CV34wKoS1gafnRFjeKUqaEbWzIj76+s/setARiovefd3PPuZ/7Pd977sOwNeOlrZuw+LhXcHo+oBUntPx0ymHTpom97tKnJCf240nH45gYixWEPdE4ELsDOxS+NDdXct0julRw6sU+ANd6eq2nMpkdHbWdiQcF4Y8H7ovZjh+JeY6ZW5Q8m7NeAj9/l0iays8WgFsR5s97e9/OJ1P4FlL4UcxmP3BopE4EnWWnfSO6NLhryFicbCNy6FjK0Z347h0vPh64Hyn8MHoTM+9V8YfstWnQrxGQVk3KglXDh46yMxMAQIdxpJzdUHSStS0slMlnP8rXN6LD8UO7QxhzLCre0KBOCL1qPonU+tyGTHAbJEO2JWBgtBd9wuqvFT36luL2RGCHo0KwALBWCH3qFNKs5JETLQRMdWQ56JjNcPFpZMcX0105b/r3Nss5K7eEL7dC7OvhWEnG+/jBYwk4Ag7TCv3Ae81SuN9GuOj8wTqRYrojE4a1Kb5po0w1Y8mPXALhK7XzEw7grfIktv8Qi4pvH0Qe9lWtAo4yLaXkNjp/SQdzxTdzMFwIU52y4oC6S+c+xpuKOQEQMp013ij9sl/F/ddakwoIRpkq+eR4UxZMthLO5Qurd+jFyN+UhzNdOWhd+gOz2VojDIDmHeVsT2vW56N6oW2yWQL3GjNgpDaNsiGoXXPGN2MUwF+3JJ7lltK66tMBVTE325MLf7YRP2IrijzsB8acz0wVyQNdZRwEOkfDvHf0ImqqVYTKyaTU8osUkdHisyoKYLxRbLLXCses1QK6AxaWgUUM7D/zhROoYa1/1IugX82D29UC74hOQI41pFN/93wLXZpLlPq8iFK8lgTPHPwNdjUk+uYtBAzrxGBW8UkE9K5SONVBwAQqz6ETzLv0okVrdarPohaQg1Wn4TInn9qwFyDqZDHACxgllGPUBgiG6SbCeweVO4CsQF7OzXTTHi4r7LnBLUInPaShC5ZccCGj0d8FzDU8Cl4OBW7iLgrzbgQIw4DzPUatgxDfbJsMUA9qTEqek36aYC+kgSUBD40KbsTtapHcbZCeN1Xw91iU/Ju9dUJ4IDlC1l3eCnl3j1L/8A5TRWURcM2ZsEjeyoPRZuJdDIsMsqgEsjGDtHSsQbKNgWnz41lrX4uxnPfOZIsUurVp1Gi9bHGuMRd6UIOP38xenG3O8cFgIfz+a+axtfuupn+AY+vWB2OlsuNBVQWfsozyU0wLtVxLCu1VC66abyTP328nwNOWSVoqkr1TndngaZcuTDRJS+xa8RY6t/v6V+w+JZdluPL16rf87MZg5nuB/17gPXYqeJFDdRl54wZpbKv87F6HXpKPYK/649ez45nc57dsDnD+B2KxfjLoAaNfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marbleous visibility1 505x487&quot;
        title=&quot;marbleous visibility1 505x487&quot;
        src=&quot;/blog/static/e7b93b68b9bf85ceb90c94d677359908/8939e/marbleous-visibility1-505x487.png&quot;
        srcset=&quot;/blog/static/e7b93b68b9bf85ceb90c94d677359908/9ec3c/marbleous-visibility1-505x487.png 200w,
/blog/static/e7b93b68b9bf85ceb90c94d677359908/c7805/marbleous-visibility1-505x487.png 400w,
/blog/static/e7b93b68b9bf85ceb90c94d677359908/8939e/marbleous-visibility1-505x487.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As a last step, we join our two canvases by drawing the canvas that contains the obstructing parts onto the marble using the blend mode &lt;code class=&quot;language-text&quot;&gt;destination-out&lt;/code&gt;, this removes the marble everywhere there is a block and leaves us with a nice precisely drawn marble.&lt;/p&gt;
&lt;p&gt;I hope you enjoy marbleo.us and found this write-up useful. If you have any more questions or comments, feel free to drop me a line at robb@soundcloud.com&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Custom Players]]></title><description><![CDATA[UPDATE: Check out our guide on Playing Sounds for the most up to date information. We think the default audio widget from SoundCloud already is one of the sexiest on the web. But maybe you need something a bit simpler or complex and want to tailor the player to your needs. This is where the idea of “custom players” comes into play.]]></description><link>https://developers.soundcloud.com/blog/custom-players</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/custom-players</guid><pubDate>Wed, 06 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;UPDATE: Check out our guide on &lt;a href=&quot;/blog/docs/api/guide#playing&quot;&gt;Playing Sounds&lt;/a&gt; for the most up to date information.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We think the default audio widget from SoundCloud already is one of the sexiest on the web. But maybe you need something a bit simpler or complex and want to tailor the player to your needs. This is where the idea of “custom players” comes into play.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Here’s a few examples of custom players in the wild:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://stream.bandofhorses.com/&quot;&gt;Band of Horses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stratus.heroku.com/&quot;&gt;Stratus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://whitelies.heroku.com/&quot;&gt;White Lies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://thebeatles.com/core/love/album/&quot;&gt;The Beatles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://simplemath.heroku.com/&quot;&gt;Manchester Orchestra&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve provided a few javascript libraries to get you started:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Widget-JS-API&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The official &lt;a href=&quot;/blog/docs/html5-widget&quot;&gt;SoundCloud Widget Javascript API&lt;/a&gt; allows you to interact with our standard widgets via JavaScript. You can load tracks and sets, control playback and trigger events when something changes in the player.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;SoundCloud-Custom-Player&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/docs/custom-player&quot;&gt;SoundCloud-Custom-Player&lt;/a&gt; is a jQuery plugin that allows you to easily create customizable, HTML/CSS/JS based audio players. It even includes automagic support for the &amp;#x3C;audio&gt; tag and therefore also works on iOS, Android and Palm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;SoundManager2&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another great way to create custom players is to use any javascript audio library like &lt;a href=&quot;http://www.schillmania.com/projects/soundmanager2/&quot;&gt;SoundManager2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pumped? Yeh, us too. Let us know what you create, we’ll be waiting api@soundcloud.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Stream and Download]]></title><description><![CDATA[UPDATE: Check out our guide on Playing Sounds for the most up to date information. This is an introduction to how you can power your app or website with sounds hosted on SoundCloud.
Via our API, you get access to millions of different sounds, and in addition to streaming and downloading tracks, you can access meta data and social features like comments and followings.]]></description><link>https://developers.soundcloud.com/blog/stream-and-download</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/stream-and-download</guid><pubDate>Wed, 06 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;UPDATE: Check out our guide on &lt;a href=&quot;/blog/docs/api/guide#playing&quot;&gt;Playing Sounds&lt;/a&gt; for the most up to date information.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is an introduction to how you can power your app or website with sounds hosted on SoundCloud.
Via our API, you get access to millions of different sounds, and in addition to streaming and downloading tracks, you can access meta data and social features like comments and followings.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;These are some typical ways apps are using SoundCloud for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let users search, browse and stream tracks in an app, e.g. &lt;a href=&quot;https://soundcloud.com/apps/soundcloud-desktop&quot;&gt;SoundCloud for Mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Let users connect to SoundCloud and make their tracks available for streaming on a personalized page, e.g. &lt;a href=&quot;https://soundcloud.com/apps/bandpage&quot;&gt;Rootmusic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Filter out tracks uploaded by a certain app, and make them available for other users in the same app, e.g. &lt;a href=&quot;https://soundcloud.com/apps/ielectribe&quot;&gt;iELECTRIBE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Build a website for music exploration, e.g. &lt;a href=&quot;https://soundcloud.com/apps/citysounds-fm&quot;&gt;CitySounds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make it possible to import sounds and samples for music creation, e.g. &lt;a href=&quot;https://soundcloud.com/apps/myna&quot;&gt;Aviary Myna&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Streams &amp;#x26; Downloads using the API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For all uploaded files, we provide a 128 kbps MP3 stream version alongside with the original audio file.
The access to these files can be restricted depending on the track settings for streamable and downloadable.
Have a look at the &lt;a href=&quot;/blog/docs/api&quot;&gt;API Introduction&lt;/a&gt; and the &lt;a href=&quot;/blog/docs/api/tracks&quot;&gt;/tracks documentation&lt;/a&gt; to find out how to access the stream and download for a track.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playing Streams in websites&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want to play SoundCloud tracks in a website have a look at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/blog/docs/oembed&quot;&gt;oEmbed&lt;/a&gt; to get the embed code of our standard player widgets.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/docs/html5-widget&quot;&gt;Widget JS API&lt;/a&gt; to control and customize our player widget using JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/docs/custom-player&quot;&gt;JS Custom Player&lt;/a&gt; to build your own HTML5 audio player.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Sharing Sounds]]></title><description><![CDATA[UPDATE: Check out our guides on Authentication and Sharing Sounds for the most recent information. If you build an app or web service that generates any type of sound, it’s easy to connect it to SoundCloud and enable your users to share their creations across the web. Allowing users to share what they create to their existing social networks and the SoundCloud community brings great value in a variety of use cases. If you have an app for beat making, you can let your users post their creations to a dedicated group on SoundCloud. For aspiring music makers, it’s valuable and fun to get feedback from friends on Facebook. And for someone recording an interview with their phone, it’s super handy to be able to post the audio directly to a blog. In addition, a sharing feature is obviously practical for sending files to collaborators or moving audio between devices.]]></description><link>https://developers.soundcloud.com/blog/sharing-sounds</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/sharing-sounds</guid><pubDate>Tue, 05 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;UPDATE: Check out our guides on &lt;a href=&quot;/blog/docs/api/guide#authentication&quot;&gt;Authentication&lt;/a&gt; and &lt;a href=&quot;/blog/docs/api/guide#sharing-sounds&quot;&gt;Sharing Sounds&lt;/a&gt; for the most recent information.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you build an app or web service that generates any type of sound, it’s easy to connect it to SoundCloud and enable your users to share their creations across the web. Allowing users to share what they create to their existing social networks and the SoundCloud community brings great value in a variety of use cases. If you have an app for beat making, you can let your users post their creations to a dedicated group on SoundCloud. For aspiring music makers, it’s valuable and fun to get feedback from friends on Facebook. And for someone recording an interview with their phone, it’s super handy to be able to post the audio directly to a blog. In addition, a sharing feature is obviously practical for sending files to collaborators or moving audio between devices.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Letting users share tracks is also a great way of virally promoting your app. Uploaded tracks will automatically be tagged as uploaded with [your app], so when a user shares a track on Facebook, their friends will see what app the track was created with.&lt;/p&gt;
&lt;p&gt;Some examples of apps using SoundCloud for sharing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/apps/voicejam&quot;&gt;VoiceJam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/apps/ujam&quot;&gt;UJAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/apps/wave-editor&quot;&gt;Wave Editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’ll find more examples in our &lt;a href=&quot;https://soundcloud.com/apps&quot;&gt;App Gallery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To be able to upload tracks, the user must connect the app to their SoundCloud account. For this connection, we use OAuth; “An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://oauth.net/images/oauth-2-sm.png&quot; alt=&quot;OAuth2&quot;&gt;&lt;/p&gt;
&lt;p&gt;The idea behind OAuth is to make it possible for the user to hand out a “key” to their account, without giving away their email and password to the third-party app. In our case, an app with a key can access the user’s tracks, delete tracks, upload new tracks, make comments on tracks, see who the user is following, etc. In other words, the key gives full access to the user’s account, except the email address, credit card information, and other sensitive data.&lt;/p&gt;
&lt;p&gt;This is how the connection flow looks for the user…&lt;/p&gt;
&lt;p&gt;The app can store the key so that the user doesn’t have to log in the next time they want to upload a track. The key will work until the user revokes the access in their SoundCloud account settings.&lt;/p&gt;
&lt;p&gt;To try the connection flow, check out the following apps:
Connect with SoundCloud demo on the web
SoundCloud for iPhone
SoundCloud for Android&lt;/p&gt;
&lt;p&gt;To implement Connect with SoundCloud:
Read the API documentation about authentication with OAuth 2.0
See if there’s an SDK for your framework which will simplify the implementation&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Uploading Audio&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once the app is connected you can upload with a POST request to /tracks. The only meta data required when uploading an audio file is the track title. You can specify other meta data as well, or edit the data once the track is uploaded. If you don’t set the track to public, it is set to private by default. When the file upload is finished, you get back a track object including the track id and URL. Once you have the track ID, you can read and access all the meta data. The full list of meta data is available here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sharing a Track on SoundCloud&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tracks can be shared to other SoundCloud users. You can either share to specific users by id or to email addresses. If the email address is not yet registered with SoundCloud, they will receive an email invitation to the track.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sharing to Social Networks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An uploaded track can also be shared to Facebook, Twitter, MySpace and Foursquare. Instead of connecting your app to the different networks, we handle the connections and share the track once it’s uploaded and finished processing. Users can enable automatic sharing of new tracks to social networks in their SoundCloud settings. If you want, you can make these options available in the app as well.&lt;/p&gt;
&lt;p&gt;This is how the sharing screen looks in our iPhone app:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 300px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/4b4afa2ba8edef2b8a25de084981fd7b/135ae/iphone-sharing.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 150%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFsElEQVRIx3VWWW9TRxS+AgmFovCAEAQSG4W0Wb1f32vHjh3vjrPgOBtZGgUlJQ6Q3U6i5LUqT/0nrVSkLuoLUiWqUiFQRYsQioSgFWrZ2j9QZr6eM7kXhaUPn86dmTPfnG3OXO36g39e/fDrI/HTrdvi59t3xPW7f4ob95+JJ89fiMdPX4qHT56Ka99+L7649rX449nf4tFfzwkvxO+0/nD3pbj7y2Nx4+Zt8d2db+RXN7/8V7u0uiWurFawvLKKxeUVrKxvoLq1gyUaX15cRnVzS83PXSzjytIKltfWaW2N1pawRrrrlU2s0Nzi2jKW1peF5nTUC0dDPRwNDdLpcMhTdXVyZGRYzl+8KMdGR+XoyIj8ZG5OTkyM0/yIml8ol+UorTmdDtnQUC8dtM/pcMLZ4HylffDBEVF79Chqa2slgSVOnz6NM2fOKJw8efL1uL6eDnY44HQ60dzcjMOHD9uQlhRad3e3mJ+fx9TUlBwfH0e5XMbQ0BDa29vhdrvh8/ng8XjU2OVyoaOjQ6GtrQ1er9eGZB3SFVoulxNra2tYWFiQs7OzWF1dxczMDHRdRyQSQTgchmmaMAzjHQSDQRsyFovx3B7heqVKAV6XqxTc6sYmZi5cgD+gI5PNoafQi2wuT8omzQUQeA/ocDk4OMhSaNFIpyj29eBcb14SUOwvIJ9OwAx4YFgwA16FYFDfb9UbFrJHJIXm9QdFky+OJn+3JJCM4yzJD/U0mo0MWs0MWgitRgpur5/i6VYxfQsqhn6/X2jNkaLQFn6Ddvm+1Mr3ULf1AJ5Pd+H/bBc1y/egzdPapfvQ5m5BTw4iGYsgHu8mxPdDslSErmBMnMhUcCK7IQk4ld/ARwObaC1uoaGwiZO5TdTlt1CXXYfRlUIsGkE02kWI7odkqesBoYVDpkhEgoiHAzJm+hAL+dEV1tFp+BHW3TC8bTAJhq8dAb+PrXgfJEtKkNA6OyMilc4gmUpLAkuV1UQyhQhZwtkOBA0E9KAqpaD+ZmJ4jrNsSaExa4AGVBKSi9guZD5R19/Nqo/m7YJmXat0pKUvNJ+rQ5SuVFEYm5SjQyVMT08rpNPp16R7m3Q1NswQusn6LkpMQDcwNjbGkBa50MgFETSp6g1DhkNhdTO6urrQ2dkJKy528YI94dhSvBE1/Sq+QeVqUBEqC3ViTWbP0ak5GSJiJrKvG189zh6PdXLX0H1ojI7BWaBK6K/gVHoJbj+FweclQstlj8clZpeuYnp2SQ4WBzAxMQlqTeCrxM2CmoaSOiXG8LbjSP/nOLH9EMGru9Bmf0Q42YtMKiG5lFQd8oX2udvhdbskB5xN54D7reCzu6FQaK8h0NVzh5JoCvXirNGDZjNHFWAiZJrSSprQ/F6PyI5cQPrciEwnE0hRMrLZLFKplJLsOpMGreREwyZ60t3oy6cIaWQyaY655CQqwgC5HJ+6hHhpQhayGQyWSspl6s4KHD87KWx1hGI6WBpCiXrm1MfTKst0sCwUCnuEhmkKV2sz3B1t0kUN1W6gDK5HuxZtyaS8xg13n57kZqwIqQtTxy5jYnJS9vf3Y3h4GMViUSWFY7e/bJiMs87rrHf+/HmUyKNMJqP2KkJu2729vcjn8yoOvMDjnp4elQibzC5wPoSasoovE7Or1K1lIpHYI6SgC6uA1X20rx/PcUJsMBFL230mt/XsvYqQgi6sjEo+ma0kF8AnsqX8nUwmlVX8zevkjbKQvxmkK62KEBqdLHiSNqkmyY8NkzEJvYjqe79kUr5NfD1Zx4Lqh+rVo5gJK25yYGBAxY4t+D/wOuux9QzrW/b19XH8hUYni2q1CoJ6Rjn9ra2t6t19Gy0tLSpu/Hbv7OygUqlgcXER29vbkp9i8lRox44dE/wXQMqysbERhw4dwoEDB3Dw4MF3wPM1NTVoampSB/A+/iYp+bDjx4+L/wA33yYGx6OxDgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;SoundCloud iPhone Sharing&quot;
        title=&quot;SoundCloud iPhone Sharing&quot;
        src=&quot;/blog/static/4b4afa2ba8edef2b8a25de084981fd7b/135ae/iphone-sharing.png&quot;
        srcset=&quot;/blog/static/4b4afa2ba8edef2b8a25de084981fd7b/9ec3c/iphone-sharing.png 200w,
/blog/static/4b4afa2ba8edef2b8a25de084981fd7b/135ae/iphone-sharing.png 300w&quot;
        sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For Cocoa apps, there is a specific guide &lt;a href=&quot;/blog/blog/ios-sharing&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have further questions about how your app can be integrated with SoundCloud, please contact &lt;a href=&quot;mailto:api@soundcloud.com&quot;&gt;api@soundcloud.com&lt;/a&gt; and we’re happy to help.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hello developers!]]></title><description><![CDATA[We are happy to introduce you to our new developer portal developers.soundcloud.com.
  Here we are bundling up all the SoundCloud platform…]]></description><link>https://developers.soundcloud.com/blog/developers-soundcloud-com</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/developers-soundcloud-com</guid><pubDate>Mon, 04 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;  We are happy to introduce you to our new developer portal developers.soundcloud.com.
Here we are bundling up all the SoundCloud platform and developer related products and resources.
It will give you the best starting point to dig into our API and all things related!
Let us know what you think about it!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Experiment 01: Puzzle To Unlock]]></title><description><![CDATA[This “Puzzle to Unlock” concept came to me straight from Manchester Orchestra’s wonderful label/management team, and we were able to pull it…]]></description><link>https://developers.soundcloud.com/blog/marbleo.us</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/marbleo.us</guid><pubDate>Tue, 01 Mar 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 505px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/561d0e6c1e3cb8af10b5fa9b61d34944/8939e/original3-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.04950495049505%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9klEQVQ4y32U227iQBBE+f9/gweEwOaOAdvcbAxCCdJ6eus0HrTJKnkoeZjprq6+0VssFu1iPg+z2SzkeREOh4O+eSjL8kdg8y+Ox2PY7/dhuVyG3nA4DIPBwEajkaVJYmma2mq1Mhna6XSy8/lsl8tF34ufuROBidgU2Ha7nWXZxpIktX6/b73JZBISEaFwvV4HGUhF4VFFEKqqCnVdh+v16mfuYhZZlrkqiQgdT+iNx2MnVOoYeFTUAZSJzERmTdOYCP0OhaAoCsNHpJ6ZuF4KpY40UedEOFYiKoryTR4Bib8L/MZHmZGhietFOJ/PbbPZkIbXCEWAOkU1gDeAahRzxke+BocTkrfSRXogOoW/3W6OsmsMQA0kHx+fCtb4O7YxbTgo3S+E9/8IH4+HPZ9PJ7zf7z8Tfk8ZJU1ze49OHBUaAumvCmNTGBkNpztSo7q+uvH3ppxO53fnscXnSw0hnE6n3uXtduuNIDJdfA32pRuXbmR0V1W1n7HFh0WAwwnjHCpCIBIRUQIpKkiNegHO3PGGTVQX0/U5hJAD087U0xz2kp2N2yI1ry3RV6r9DZu4KVLnmwKXp4zUbv1cPlEZWNKhUbmU5DrvM5DZTr/jhsSBjoDwjxa7lUKHDFr+gUT8BWtBBO1KUAPdRspa+bdS1kqQ4y9bbk3P7ejZHQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;original3 1&quot;
        title=&quot;original3 1&quot;
        src=&quot;/blog/static/561d0e6c1e3cb8af10b5fa9b61d34944/8939e/original3-1.png&quot;
        srcset=&quot;/blog/static/561d0e6c1e3cb8af10b5fa9b61d34944/9ec3c/original3-1.png 200w,
/blog/static/561d0e6c1e3cb8af10b5fa9b61d34944/c7805/original3-1.png 400w,
/blog/static/561d0e6c1e3cb8af10b5fa9b61d34944/8939e/original3-1.png 505w&quot;
        sizes=&quot;(max-width: 505px) 100vw, 505px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This “Puzzle to Unlock” concept came to me straight from Manchester Orchestra’s wonderful label/management team, and we were able to pull it together very quickly with &lt;a href=&quot;https://soundcloud.com/developers&quot;&gt;SoundCloud&lt;/a&gt;, &lt;a href=&quot;http://jquery.com&quot;&gt;jQuery&lt;/a&gt;, and &lt;a href=&quot;http://www.schillmania.com/projects/soundmanager2/&quot;&gt;SoundManager2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The band wanted to build a bit of excitement around the premiere of their new single “Simple Math.” So we developed a way to tease the song with dialogue from the artist and actual clips of audio released as “pieces” to a puzzle that will &lt;em&gt;unlock&lt;/em&gt; both the album cover and track. Cool, right?&lt;/p&gt;
&lt;p&gt;Interaction was created using these basic jQuery commands: draggable and droppable.&lt;/p&gt;
&lt;p&gt;Every time you drag the puzzle piece from it’s container at the bottom a hidden droppable div moves to the right pixel location where the piece will fit. If you find that location, the piece will glow and dropping it will lock the piece into place.&lt;/p&gt;
&lt;p&gt;A successful drop will queue up a SoundManager2 powered / SoundCloud hosted track and morph the piece’s container into a nice little play/pause button for replaying purposes.&lt;/p&gt;
&lt;p&gt;Once all of these pieces are released and placed correctly, you’ll be able to hear the full version of “Simple Math,” and trust me – it’s worth building a puzzle for. Enjoy!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[We ♥ OAuth 2]]></title><description><![CDATA[From today on, we advise developers to use OAuth 2 as preferred way of API authentication. Our implementation of the Draft 10 OAuth…]]></description><link>https://developers.soundcloud.com/blog/we-love-oauth-2</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/we-love-oauth-2</guid><pubDate>Wed, 19 Jan 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 124px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/360a07bb16fbdc974215f53bb3cbd201/03fde/oauth-2-logo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 99.19354838709678%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAEgklEQVQ4y01UaSimXxR/zCQNIikJkX37k33f18bYsydrdlJkyYtCypKSKJR6bfmC7CkfUJSy1DAaI1knJXvy4X3fnnv+51zzTnPrdp/73HN+55zfWYSqqiphY2NDoDU6OvpJ+LOODg81pqenvw4MDEiGhoak3d3d0qysrMbg4OBv+KyplDMwMOA6c3NzHz9QgZ/9/f2f6Wxvb1e9ubmpPzs7uzw5OYH393dYW1uD9fV1/o3y0NjYeB0UFCRBcTXS8fT05LpLS0sfoHV1dfxHZWWlxcLCwsHj4yPs7OxAW1ubqFAo5JOTkwraACBHQPH5+Rnu7++hpqbmO6pZk66TkxPHENLT07nLjo6OFrOzs3cYGqAB2fX1tbi6uso6OzpYdnY2JCcnQ35+PhscHGQILEokEhlSAT09PQ+obkMYOjo6fylTbW1tPbi6uoL5+XkZcgnNzc3M1taW4RtTUVEBPGnzu7u7O+vs7ISVlRUZggPm4YcyfL5iY2PrNzc34fT0VLa7uwuhoaFcUVVVFczNzSEhIQHi4+PB3t4edHV1ObCenh779euE8GQUvouLS7MSTwPdvjw8PISDgwMxIiKCg+nr6xMwXFxcgHKRYlxcHBgZGXFQZ2dnhokS6a23t/c3/tMSCgoKvs7MzMDx8bEolUoZesDMzMzA2toaMNMcKDc3F2pra/k3ZZ7e7OzsQE1NjU1NTXFO9/b2wNDQMFZALiT7+/uwtbUlLy4uZsbGxgwTBFgKMDY2RpZBW1sbrKys4Pb2Fl5fX8HPzw9cXV2BZJEOhv/kZCwzM7NFGBkZkXZ1dVF4CixazhOSDhgO2NjYgIODA1kGSgKt5eVl7p2/vz+QYfIWDSgmJiagvr5+Qujr65NiNklZkZaWxoF8fHz49vDw4ErDw8Mc7OjoCAIDA8Hb2xvIOBkmby0tLRWmpqaUvEkBa62RBLCF5EVFRSwgIICREgFGR0fD9vY2B6MOoYxT6NglEB4eTmA8ZAxdTobLy8tbhfPz82+4iGCRHrGAGYFRNu/u7jjY5eUlIPmwuLgI2LPckK+vLxU7S01NZerq6iI5gTLxAsprIvK1pqYmmJiYiJQYfGQZGRnw8vICT09P8Pb2BqLIqwMeHh54XUZGRrKysjKGHosaGhrUhrf4rM0LEftUQl5paWnJKLuFhYUsKiqKJSUlAfFKm1qP7lTg6CFLSUlhlGmsABl5i8Oj5W+nILJaQ0PDdwsLC8qqjMJFrxmOKxYTE8Po/mczKnyKwM3NjTiVE68VFRU/EeMLB8PpwhsaubRGkAfiJywsTIblI+bk5DAMhSENgEOEA+IbQ6Mieifz8vICbNtnjPA/wsBq+BgOWEN89GC32DQ1Nf3AcHlJYKgieYGACqRBgUbkOHHEkJAQ6l2q3Z9YexwMp8/H+CotLeXn+Pj4Z2X4+N3c0dHxGycQYMiAMlBSUgIERHcM+xY5b8GTh5mXl8d1ExMTBQHHvIAecFCch5/+4VULh2wsTusWbL8J9GSyurq6FWXjMbvaSjksG66D3gsoI/wPcdL7huWXdxcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;oauth 2 logo&quot;
        title=&quot;oauth 2 logo&quot;
        src=&quot;/blog/static/360a07bb16fbdc974215f53bb3cbd201/03fde/oauth-2-logo.png&quot;
        srcset=&quot;/blog/static/360a07bb16fbdc974215f53bb3cbd201/03fde/oauth-2-logo.png 124w&quot;
        sizes=&quot;(max-width: 124px) 100vw, 124px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;From today on, we advise developers to use &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth 2&lt;/a&gt; as preferred way of API authentication. Our implementation of the &lt;a href=&quot;http://tools.ietf.org/html/draft-ietf-oauth-v2-10&quot;&gt;Draft 10 OAuth 2&lt;/a&gt; specification is in production for several months months now and we made good experiences with it. Thus we move it from beta to official recommendation.&lt;/p&gt;
&lt;p&gt;OAuth 2 makes it easier for developers to implement authentication for accessing private resources. The feedback we got from external app developers and our in-house API users has been very positive. This is what &lt;a href=&quot;https://github.com/stigi&quot;&gt;Ullrich&lt;/a&gt;, one of the developers of the SoundCloud iPhone app and Cocoa wrapper, has to say about OAuth2:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We love OAuth 2! It takes away the hassle from the previous version and leaves
the awesomeness.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can see OAuth 2 in action in our &lt;a href=&quot;https://soundcloud.com/apps/iphone&quot;&gt;iPhone&lt;/a&gt; and &lt;a href=&quot;https://soundcloud.com/apps/soundcloud-desktop&quot;&gt;Mac&lt;/a&gt; apps.&lt;/p&gt;
&lt;p&gt;How to use OAuth 2 with SoundCloud is described in detail in our &lt;a href=&quot;https://github.com/soundcloud/api/wiki/02.1-OAuth-2&quot;&gt;API wiki&lt;/a&gt;.
It is so easy to implement, that you might not even require a wrapper/library. But if you prefer to use one: the &lt;a href=&quot;https://github.com/soundcloud/cocoa-api-wrapper/&quot;&gt;Cocoa&lt;/a&gt; and &lt;a href=&quot;https://github.com/mptre/php-soundcloud&quot;&gt;PHP&lt;/a&gt; wrappers have full OAuth 2 support. The &lt;a href=&quot;http://code.google.com/p/soundcloudapi-java/&quot;&gt;Java&lt;/a&gt; one has partial support. And more wrappers are in development …&lt;/p&gt;</content:encoded></item><item><title><![CDATA[iOS Sharing Guide]]></title><description><![CDATA[So you are familiar with the SoundCloud API Wrapper and want to use it to share the sounds you upload? And you want to use the existing connections on SoundCloud or make new ones? Awesome. Here’s how to do it.]]></description><link>https://developers.soundcloud.com/blog/ios-sharing</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/ios-sharing</guid><pubDate>Sat, 01 Jan 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So you are familiar with the &lt;a href=&quot;https://github.com/soundcloud/cocoa-api-wrapper&quot;&gt;SoundCloud API Wrapper&lt;/a&gt; and want to use it to share the sounds you upload? And you want to use the &lt;a href=&quot;https://soundcloud.com/settings/connections&quot;&gt;existing connections on SoundCloud&lt;/a&gt; or make new ones? Awesome. Here’s how to do it.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;In a nutshell, SoundCloud takes care of making the connections and authenticating to various social networks for you. Your app can just use those connections. There is no need to exchange and store tokens once you are authorized to SoundCloud.&lt;/p&gt;
&lt;p&gt;If you don’t use connections at all when uploading a track, then the default connection settings on the website will be used automatically. The purpose of the connection API is to change the sharing options on a per-file basis and to establish new connections.&lt;/p&gt;
&lt;p&gt;Currently the following Services are supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;li&gt;Facebook (Profiles and Pages)&lt;/li&gt;
&lt;li&gt;Foursquare&lt;/li&gt;
&lt;li&gt;Myspace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s what you need to do for sharing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting the users connections&lt;/li&gt;
&lt;li&gt;Provide UI for choosing connections (public sharing) or mail addresses (private sharing)&lt;/li&gt;
&lt;li&gt;Upload the file&lt;/li&gt;
&lt;li&gt;Establish new connections (optional)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on the privacy settings of your upload, the task is a little bit different: You want to share public tracks on social networks, and private uploads via mail.&lt;/p&gt;
&lt;h3&gt;Getting the users connections&lt;/h3&gt;
&lt;p&gt;For getting the user’s connections, use the connections resource at &lt;a href=&quot;https://github.com/soundcloud/api/wiki/10.7-Resources%3A-connections&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/me/connections.json&lt;/code&gt;&lt;/a&gt;. With the API Wrapper, it looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;api performMethod&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;GET&quot;&lt;/span&gt;
        onResource&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;me/connections.json&quot;&lt;/span&gt;
    withParameters&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil
           context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil
          userInfo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this returns is a JSON array of connections which looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2010/12/02 11:45:07 +0000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;display_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Joe Test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;post_publish&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;post_favorite&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;service&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;twitter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://api.soundcloud.com/connections/12345&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Use a &lt;a href=&quot;http://code.google.com/p/json-framework/&quot;&gt;JSON library&lt;/a&gt; to transform this into Ojbective-C structures. We recommend to do this as soon as possible, preferrably directly after the start of the App, so the user does not have to wait for connections to load. You might even want to store this info, but make sure to update it it on use, because not only your app might change it!&lt;/p&gt;
&lt;h3&gt;Provide a UI for the User’s connections&lt;/h3&gt;
&lt;p&gt;In your upload screen for &lt;em&gt;public&lt;/em&gt; files, we recommend using a table to display the connections. You can use &lt;code class=&quot;language-text&quot;&gt;UISwitch&lt;/code&gt; controls as AccessoryViews of your &lt;code class=&quot;language-text&quot;&gt;UITableCell&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;When publishing a file, it is important to respect the default sharing settings that have been made on the SoundCloud website. The &lt;code class=&quot;language-text&quot;&gt;post_publish&lt;/code&gt; field represents this and should be used as the default setting for the switch.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Foursquare&lt;/em&gt; sharing needs special treatment, though: If the user has not chosen a foursquare venue, this connection should be deactivated. If your app doesn’t integrate Foursquare locations, this connection should be omitted.&lt;/p&gt;
&lt;p&gt;When the user is done entering metadata and setting the sharing options, you should end up with an array of connections that the user has chosen.&lt;/p&gt;
&lt;p&gt;In case of &lt;em&gt;private&lt;/em&gt; sharing, provide a way to specify an array of email adresses. SoundCloud will take care of sending the mail, and shares internally if the user already has a SoundCloud account!&lt;/p&gt;
&lt;h3&gt;Upload the file&lt;/h3&gt;
&lt;p&gt;One all the metadata is in place, the file can be uploaded. For this, we use the API wrapper again:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;api performMethod&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;POST&quot;&lt;/span&gt; onResource&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;tracks&quot;&lt;/span&gt; withParameters&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;parameters context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil userInfo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can get more info about this call and its parameters from the &lt;a href=&quot;https://github.com/soundcloud/api/wiki/10.2-Resources%3A-tracks&quot;&gt;API documentation for tracks&lt;/a&gt;. One parameter is the &lt;em&gt;sharing note&lt;/em&gt; (the text that get’s displayed in a tweet, for example), so you should read it.&lt;/p&gt;
&lt;p&gt;The parameters dictionary can look something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSDictionary dictionaryWithObjectsAndKeys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    fileURL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;assetdata&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//a file URL&lt;/span&gt;
    title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;private&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;private&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;public&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track[sharing]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//a BOOL&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;@&quot;recording&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track[type]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sharingConnections&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track[post_to][][id]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//array of id strings&lt;/span&gt;
    tags&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; track&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag_list&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//also an array of strings&lt;/span&gt;
    …
    nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you don’t supply a &lt;code class=&quot;language-text&quot;&gt;track[post_to][][id]&lt;/code&gt; parameter, SoundCloud will use the default settings on the website. So to share to nobody, use this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSDictionary dictionaryWithObjectsAndKeys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    …
    &lt;span class=&quot;token string&quot;&gt;@&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track[post_to][]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    …
    nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But how to supply geo coordinates and how to set the Foursquare venue ID? For this we use machine tags that get into the array you send with &lt;code class=&quot;language-text&quot;&gt;track[tag_list]&lt;/code&gt;. The following tags are currently supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;geo:lat=12.34567&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;geo:lon=56.67890&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;foursquare:venue=1234567&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As with any upload you should put them somewhere in your app where the user can see them, cancel them, they should continue when the app is sent to the background, etc.&lt;/p&gt;
&lt;p&gt;When sharing &lt;strong&gt;private&lt;/strong&gt;, you don’t want to supply sharing connections, share to mail instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSDictionary dictionaryWithObjectsAndKeys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    …
    arrayOfStringMailAddresses&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;track[shared_to][emails][][address]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    …
    nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Bonus: Making new connections&lt;/h3&gt;
&lt;p&gt;So what about users that have not yet connected their favorite services to SoundCloud? Can they do it from within your app? Yes, they can, and it’s quite easy.&lt;/p&gt;
&lt;p&gt;You just need to send a &lt;code class=&quot;language-text&quot;&gt;POST&lt;/code&gt; with the service type of the connection and you get back an URL:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;objectivec&quot;&gt;&lt;pre class=&quot;language-objectivec&quot;&gt;&lt;code class=&quot;language-objectivec&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;api performMethod&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;POST&quot;&lt;/span&gt;
        onResource&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@&quot;connections&quot;&lt;/span&gt;
    withParameters&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;NSDictionary dictionaryWithObjectsAndKeys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;service&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;@&quot;x-myapplicationsurlscheme://connection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;redirect_uri&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//optional, forces services to use the mobile auth page if available&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;@&quot;touch&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;@&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        nil
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
           context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil
          userInfo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;nil&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The services that are currently supported are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;twitter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;facebook_profile&lt;/code&gt; (this will also connect Facebook pages!)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;foursquare&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;myspace&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The URL you get back in the JSON should be opened in a WebView. Listen for your callback URL in &lt;code class=&quot;language-text&quot;&gt;-webView:shouldStartLoadWithRequest:navigationType:&lt;/code&gt;. If you get it, close the webView and reload the connections. Voilà, your new connections are there and ready to use!&lt;/p&gt;
&lt;p&gt;That’s it! And when you’re done, don’t forget to promote your app in the &lt;a href=&quot;https://soundcloud.com/apps&quot;&gt;SoundCloud App Gallery&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Let’s Git it On]]></title><description><![CDATA[Being both a mediocre biz dev guy and a nerd means I get to post on the Developer blog as well as our Company blog, and today I’d like to…]]></description><link>https://developers.soundcloud.com/blog/lets-git-it-on</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/lets-git-it-on</guid><pubDate>Tue, 26 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Being both a mediocre biz dev guy and a nerd means I get to post on the Developer blog as well as our Company blog, and today I’d like to talk to you about Git.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is Git?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; is a free &amp;#x26; open source, version control system that when used in
conjunction with social coding websites such as &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt; can greatly improve
your efficiency for both small and large projects by keeping track of changes,
collaborators, and more. You’ll be wondering how you ever lived without it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve had to teach myself the basics of Git from day one with SoundCloud and I recently graduated from clueless to novice with my first successful Fork &gt; Edit &gt; Pull.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fork What?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Projects on Git are stored in repositories and when these are made public,
anyone can &lt;a href=&quot;http://help.github.com/forking/&quot;&gt;Fork it&lt;/a&gt; (make a copy of your project), Edit it, and Send a
Pull Request (notify you of the changes). Should you choose to Pull it, those
changes will be added to your project. This is the definition of social coding.
Killer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I recently did this dance with a great new Ruby gem called OmniAuth from Intridea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s OmniAuth?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“OmniAuth is a new Rack-based authentication system for multi-provider
external authentcation.” – which allows you as a developer to roll out a login
system consisting of any number of 3rd party providers, such as Twitter &amp;#x26; Facebook,
in no time at all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OmniAuth just had one thing missing: SoundCloud support! So I said, “Fork This,” (laughing all by myself at home) and forked the project. Within an hour or so I had a working implementation and sent a Pull request to Intridea. And then just this morning, SoundCloud support was approved and added to the gem. Victory!&lt;/p&gt;
&lt;p&gt;Here’s a link to the latest gem: &lt;a href=&quot;https://github.com/intridea/omniauth&quot;&gt;github.com/intridea/omniauth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you haven’t already, I invite you to dig deeper into the world of Git and open-source. While it can be a bit daunting at first for a novice (like myself)–once learned, you’ll never go back. Plus, you’ll have a better understanding
of our own open-source offerings available from &lt;a href=&quot;https://github.com/soundcloud&quot;&gt;github.com/soundcloud&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you have any questions or comments, I’m &lt;a href=&quot;mailto:lee@soundcloud.com&quot;&gt;lee@soundcloud.com&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/leemartin&quot;&gt;@leemartin&lt;/a&gt; on Twitter, and &lt;a href=&quot;https://github.com/leemartin&quot;&gt;leemartin&lt;/a&gt; on Github. Happy Hacking!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Music Hack Day Barcelona]]></title><description><![CDATA[Team SoundCloud hacking at Music Hack Day Barcelona Last weekend a team of SoundCloud attended Music Hack Day Barcelona. This
blog post is…]]></description><link>https://developers.soundcloud.com/blog/music-hack-day-barcelona</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/music-hack-day-barcelona</guid><pubDate>Fri, 08 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/5044855904/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/eric-300x139.jpg&quot; alt=&quot;Team SoundCloud Hacking at Music Hack Day Barcelona&quot; title=&quot;eric mhd&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Team SoundCloud hacking at Music Hack Day Barcelona&lt;/p&gt;
&lt;p&gt;Last weekend a team of SoundCloud attended &lt;a href=&quot;http://bcn.musichackday.org/&quot;&gt;Music Hack Day Barcelona&lt;/a&gt;. This
blog post is going to talk about what a &lt;a href=&quot;http://musichackday.org/&quot;&gt;Music Hack Day&lt;/a&gt; is and how we experienced
the one in Barcelona.&lt;/p&gt;
&lt;p&gt;At a &lt;a href=&quot;http://musichackday.org/&quot;&gt;Music Hack Day&lt;/a&gt; people from around the world gather to spend 24 hours hacking on music and tech. Projects can range from connecting APIs and creating new webapps to building new instruments or mashup audio. The first Music Hack Day happened in London in &lt;a href=&quot;http://london.musichackday.org/2009/&quot;&gt;July 2009&lt;/a&gt; and since then already Music Hack Days took place around the world in cities like &lt;a href=&quot;http://berlin.musichackday.org/&quot;&gt;Berlin&lt;/a&gt;, &lt;a href=&quot;http://amsterdam.musichackday.org/&quot;&gt;Amsterdam&lt;/a&gt;, &lt;a href=&quot;http://boston.musichackday.org/&quot;&gt;Boston&lt;/a&gt;, &lt;a href=&quot;http://stockholm.musichackday.org/&quot;&gt;Stockholm&lt;/a&gt; and&lt;a href=&quot;http://sf.musichackday.org/&quot;&gt; San Francisco&lt;/a&gt;. A lot of cool projects were created and a lot of people had tons of fun together. If you don’t believe me, have a look at &lt;a href=&quot;http://issuu.com/musichackday/docs/musichackdaylondon?mode=embed&quot;&gt;the newspaper&lt;/a&gt; that was printed for the last Music Hack Day in London. The interviews on page 4 offer a great insight into the Music Hack Day ecosystem. All Music Hack Days are free for the participants and financed by sponsors. Free food and drinks is supplied for the hackers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/hackers.jpg&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/hackers-300x131.jpg&quot; title=&quot;hackers&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hackers&lt;/p&gt;
&lt;p&gt;Music Hack Day Barcelona started for us with a flight from Berlin to Barcelona
on Friday. Unfortunately we missed the traditional pre-hack-meet-and-drink
that night. On Saturday the Hack Day was opened by a nice breakfast. Afterwards
companies like &lt;a href=&quot;http://www.last.fm/&quot;&gt;Last.fm&lt;/a&gt;, &lt;a href=&quot;http://www.musescore.com/&quot;&gt;MuseScore&lt;/a&gt; and &lt;a href=&quot;http://www.canoris.com/&quot;&gt;Canoris&lt;/a&gt; &lt;a href=&quot;http://wiki.musichackday.org/index.php?title=API_Schedule&quot;&gt;showcased
their APIs&lt;/a&gt;. After lunch the hacking started. This means that there is
people staring at their laptop screens everywhere and in irregular intervals
you hear somebody saying “This is so cool” or “What the fuck?”. We started
hacking as well, i’ll talk about our projects later in this post. Fairly regular
other hackers came to our table to get support for our API, grab stickers and
a T-Shirt or just have a nice chat with us. And we love that!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/hermzz/5046837914/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/hackingz-300x150.jpg&quot; title=&quot;hackingz&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hackingz&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/5044610253/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/concert-300x130.jpg&quot; title=&quot;concert&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Evening Concert&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/5046937182/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/sleeping-300x158.jpg&quot; title=&quot;sleeping&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sleepy Hacker&lt;/p&gt;
&lt;p&gt;After lunch concerts started outside which where a pleasant change to the hardcore
hacking going on in the main hall. Some hackers stayed overnight, working on
their hacks or just hanging around. Monday morning kicked-off with the realization
that there were only six hours left to finish the hacks. Tension increased
in the hall as everyone tried to get their hack in a demo-able state. In the
end &lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Barcelona_Hacks&quot;&gt;34 hacks&lt;/a&gt; were submitted. During the &lt;a href=&quot;http://www.ustream.tv/recorded/9975355&quot;&gt;final presentations&lt;/a&gt; the
hackers got 5 minutes each to present their hack, which often was accompanied
by a cheering crowd. Afterwards the best hacks were awarded with prices including
tickets for Sonar and an iPad.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/5043979038/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/presentations-300x120.jpg&quot; title=&quot;presentations&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Preparing for Demos&lt;/p&gt;
&lt;p&gt;We presented three hacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=TopBillin&quot;&gt;Top Billin&lt;/a&gt; – Who to follow on SoundCloud?&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=SoundCloud_Social&quot;&gt;SoundCloud Social&lt;/a&gt; – Synchronous listening to SoundCloud tracks and chat&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.musichackday.org/index.php?title=SoundCloudGmail&quot;&gt;SoundCloud Gmail Plugin&lt;/a&gt; -Player for SoundCloud tracks in emails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our hacks are still in a very beta stadium. You can try them now, but expect
proper announcements once we finished them.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/hermzz/5046214977/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/workshop-300x124.jpg&quot; title=&quot;workshop&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Reactable Workshop&lt;/p&gt;
&lt;p&gt;Hacking is one aspect of a Music Hack Day. Another one is &lt;a href=&quot;http://wiki.musichackday.org/index.php?title=Workshop_Schedule&quot;&gt;workshops&lt;/a&gt;.
People give in-depth presentations and offer hands-on workshops. Past topics
include Arduino, Android Audio and a massive amount of incredibly cool APIs.
And last but not least, a Music Hack Day is a great way to get to know cool
and likeminded people. Everybody at a Music Hack Day is super-nice and very
approachable. Everyone wants to share their knowledge and help-out each other.
The atmosphere at a Music Hack Day sports a great community feeling.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/hermzz/5046837980/&quot;&gt;&lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-content/uploads/2010/10/morehackingz-300x120.jpg&quot; title=&quot;morehackingz&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Music Hack Day Equipment: Laptop/Internet/Drink/Food&lt;/p&gt;
&lt;p&gt;The next Music Hack Day will happen in &lt;a href=&quot;http://boston.musichackday.org/&quot;&gt;Boston&lt;/a&gt; next weekend. And be assured
that more are currently in planning. Follow &lt;a href=&quot;https://twitter.com/musichackday&quot;&gt;@musichackday&lt;/a&gt; on Twitter
for more announcements on that and subscribe to the &lt;a href=&quot;http://musichackday.org/&quot;&gt;Music Hack Day main page’s&lt;/a&gt;
RSS feed. If you think your city deserves a Music Hack Day as well, then you
should organize one! We will be there &lt;img src=&quot;http://scbackstage.wpengine.netdna-cdn.com/wp-includes/images/smilies/icon_smile.gif&quot; alt=&quot;:)&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photos by &lt;a href=&quot;http://www.flickr.com/photos/thomasbonte/&quot;&gt;thomasbonte&lt;/a&gt; and &lt;a href=&quot;http://www.flickr.com/photos/hermzz/&quot;&gt;hermzz&lt;/a&gt; under &lt;a href=&quot;http://creativecommons.org/licenses/by-sa/2.0/&quot;&gt;cc-by-sa&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[node.js knockout — August 28th & 29th]]></title><description><![CDATA[A few days ago a small team of SoundCloud developers (@goldjunge, @jberkel, @purzelrakete and @sohm) participated in the first node.js…]]></description><link>https://developers.soundcloud.com/blog/node-js-knockout-august-28th-29th</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/node-js-knockout-august-28th-29th</guid><pubDate>Tue, 31 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago a small team of SoundCloud developers (&lt;a href=&quot;https://twitter.com/goldjunge&quot;&gt;@goldjunge&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/jberkel&quot;&gt;@jberkel&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/purzelrakete&quot;&gt;@purzelrakete&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/sohm&quot;&gt;@sohm&lt;/a&gt;) participated in the first node.js knockout competition. Aptly named “Team SoundCloud”, we set out to explore the current state of server side Javascript using node.js and the real-time web.&lt;/p&gt;
&lt;p&gt;(For those unfamliar with Node.js, head over to&lt;a href=&quot;http://nodejs.org/&quot;&gt; http://nodejs.org/&lt;/a&gt; or the project wiki at&lt;a href=&quot;https://github.com/ry/node/wiki&quot;&gt; https://github.com/ry/node/wiki&lt;/a&gt; for an introduction.)&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://nodeknockout.com/&quot;&gt;node.js Knockout&lt;/a&gt; is a competition inspired by the popular &lt;a href=&quot;http://railsrumble.com/&quot;&gt;Rails Rumble&lt;/a&gt;. The rules are simple: Teams of up to 4 people have 48 hours to build a web app that is awesome enough to woo the judges and results in as many votes from the audience as possible. To level the playing field, each team was required to deploy their application to a dedicated hosting environment provided by either &lt;a href=&quot;http://heroku.com&quot;&gt;heroku.com&lt;/a&gt; or &lt;a href=&quot;http://joyent.com&quot;&gt;joyent.com&lt;/a&gt;. The competition started on Saturday at 2:00AM and ended on Monday 2:00 AM. Yes, that’s a weekend right there.&lt;/p&gt;
&lt;p&gt;We had done a minimal amount of planning beforehand and agreed that we’d try to visualize traffic on&lt;a href=&quot;https://soundcloud.com&quot;&gt; soundcloud.com&lt;/a&gt; by hooking into the varnish HTTP accelerator that’s part of our infrastructure. We basically set out to build a pimp HTML5 + WebSockets based version of varnishtop (with a sprinkle of varnishlog, because that’s just so exciting!).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/blog/static/ae2bcdb351bb2faf3bb3c8b376bffbb4/48a11/late_night_hackingz.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAUGBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAFRrW0kIC1D/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACAwQRE//aAAgBAQABBQKjVa5ekRGcI6xKk//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB0QAAECBwAAAAAAAAAAAAAAAAEAAhARISIxQVH/2gAIAQEABj8CvRc2oGoz6VgL/8QAGhABAAIDAQAAAAAAAAAAAAAAAQARITFBgf/aAAgBAQABPyFxklYnGTb1G4XNag1tkNwJQXk//9oADAMBAAIAAwAAABDED//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/ECf/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAbEAEAAwEBAQEAAAAAAAAAAAABABExIUFhkf/aAAgBAQABPxAguUVrj+RuWGJbCeNbsKY05XT4xVikdmwtVFXkPgDwE//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;late night hackingz&quot;
        title=&quot;late night hackingz&quot;
        src=&quot;/blog/static/ae2bcdb351bb2faf3bb3c8b376bffbb4/48a11/late_night_hackingz.jpg&quot;
        srcset=&quot;/blog/static/ae2bcdb351bb2faf3bb3c8b376bffbb4/f544b/late_night_hackingz.jpg 200w,
/blog/static/ae2bcdb351bb2faf3bb3c8b376bffbb4/41689/late_night_hackingz.jpg 400w,
/blog/static/ae2bcdb351bb2faf3bb3c8b376bffbb4/48a11/late_night_hackingz.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;@purzelrakete, @goldjunge and @jberkel hard at work.&lt;/p&gt;
&lt;p&gt;Being confined to the Joyent environment, the first challenge to overcome was not having access to a varnish instance that serves any real traffic. To solve that, we hooked into the Twitter Streaming API and filtered all links to the popular URL shortening service &lt;a href=&quot;http://bit.ly&quot;&gt;bit.ly&lt;/a&gt;. For each bit.ly link a request is made against the local varnish instance to generate traffic. Of course, all of these requests result in (eventually cached) 404s, but oh well. The rest of Saturday saw us create a layout, start working on the C++ extension required to access a running varnish instance’s shared memory to read logs &amp;#x26; statistics and generate events in node, culminating in having the basic functionality and deployment configuration done at around 3AM on Sunday.&lt;/p&gt;
&lt;p&gt;After a few hours of sleep, Sunday was a blur of getting the C++ extension to emit more statistics and log details, writing the client-side javascript that updates the DOM and wrestling with Solaris on the Joyent SmartMachine. We deployed the final version about 30 minutes before the competition deadline.
Lessons learned (in no particular order):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node.js is fun, building realtime stuff with it is incredibly easy&lt;/li&gt;
&lt;li&gt;deploying early and continuously == win&lt;/li&gt;
&lt;li&gt;Solaris is a pain&lt;/li&gt;
&lt;li&gt;Twitter is full of spam and porn&lt;/li&gt;
&lt;li&gt;Server side javascript!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check out the app here (using Chrome or Safari):&lt;a href=&quot;http://team-soundcloud.no.de/&quot;&gt; http://team-soundcloud.no.de/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vote for us here:&lt;a href=&quot;http://nodeknockout.com/teams/team-soundcloud&quot;&gt; http://nodeknockout.com/teams/team-soundcloud&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Of CORS We Do]]></title><description><![CDATA[If you’re a JavaScript head, we’ve got something for you. SoundCloud now supports Cross Origin Resource Sharing, using XMLHttpRequest. Or…]]></description><link>https://developers.soundcloud.com/blog/of-cors-we-do</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/of-cors-we-do</guid><pubDate>Fri, 27 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re a JavaScript head, we’ve got something for you. SoundCloud now supports &lt;a href=&quot;https://www.w3.org/TR/cors/&quot;&gt;Cross Origin Resource Sharing&lt;/a&gt;, using XMLHttpRequest. Or, to put it another way: no more implausible &lt;a href=&quot;http://en.wikipedia.org/wiki/JSONP#JSONP&quot;&gt;JSON-P&lt;/a&gt; hacks.&lt;/p&gt;
&lt;p&gt;Some background on CORS can be found &lt;a href=&quot;https://developer.mozilla.org/en/HTTP_access_control&quot; title=&quot;here&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/&quot; title=&quot;here&quot;&gt;here&lt;/a&gt;.  Our implementation is super-simple:  we let you do GET requests, for our public resources. Full documentation of the feature &lt;a href=&quot;http://wiki.github.com/soundcloud/api/06-cross-site-scripting&quot; title=&quot;is on our wiki&quot;&gt;is on our wiki&lt;/a&gt;, but here’s a bit of code to get you started:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; invocation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Internet Explorer uses a propritary object called XDomainRequest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;https://api.soundcloud.com/tracks&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callOtherDomain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onreadystatechange &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As we’re just setting headers, the implementation was done as an addition to our Rack stack, which means that it’s easy for us to pull out or move around as needed. Once the appropriate headers are added, these newfangled modern browsers handle the rest.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Let’s go backstage. Welcome to the SoundCloud development blog!]]></title><description><![CDATA[Our very cool devs Matas and Hannes. With over 15 developers working on the SoundCloud site nowadays, we’re pumping out a steady stream of…]]></description><link>https://developers.soundcloud.com/blog/lets-go-backstage-welcome-to-the-soundcloud-development-blog</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/lets-go-backstage-welcome-to-the-soundcloud-development-blog</guid><pubDate>Thu, 26 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/ericwahlforss/3527478421/&quot;&gt;&lt;img src=&quot;http://farm4.staticflickr.com/3309/3527478421_c414163966.jpg&quot; alt=&quot;Our developers are too cool to not wear sunglasses!&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Our very cool devs &lt;a href=&quot;https://soundcloud.com/matas&quot;&gt;Matas&lt;/a&gt; and &lt;a href=&quot;https://soundcloud.com/hannes&quot;&gt;Hannes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With over 15 developers working on the &lt;a href=&quot;https://soundcloud.com/&quot;&gt;SoundCloud&lt;/a&gt; site nowadays, we’re pumping out a steady stream of &lt;a href=&quot;https://blog.soundcloud.com/2010/08/19/reuse-soundcloud-style/&quot;&gt;new features&lt;/a&gt; and scalable web application code. It’s been a long journey building up such a great team, with lots of lessons learned along the way. We’re really thankful for the fact that the web startup community is so open about sharing technical knowledge–this has been super-helpful for us sofar. And we’re still regularly checking blogs of peer companies for updates on the latest available technologies and techniques…&lt;/p&gt;
&lt;p&gt;So, we thought that &lt;em&gt;now&lt;/em&gt; would be a good time to start sharing back some of the innovation that is coming out of our own team, since we’ve got a fair amount of that going on these days. Our aim is to keep this a low-volume, high-quality blog for all things development related–from &lt;a href=&quot;http://berlin.musichackday.org/index.php?page=Xylobot&quot;&gt;nightly hacks&lt;/a&gt; at &lt;a href=&quot;http://musichackday.org/&quot;&gt;Musichackday&lt;/a&gt; to &lt;a href=&quot;http://sgi.com&quot;&gt;server infrastructure&lt;/a&gt; to &lt;a href=&quot;http://jquery.com&quot;&gt;Javascript&lt;/a&gt; lazy-loading techniques to &lt;a href=&quot;http://cassandra.apache.org/&quot;&gt;NoSQL&lt;/a&gt; installations. First up is a highlight on the newly available &lt;a href=&quot;https://www.w3.org/TR/cors/&quot;&gt;CORS&lt;/a&gt; support in the &lt;a href=&quot;https://soundcloud.com/developers&quot;&gt;SoundCloud API&lt;/a&gt; built by our mighty summer intern &lt;a href=&quot;https://soundcloud.com/fractal&quot;&gt;Thor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’re excited about this and hope you’ll like it!&lt;/p&gt;</content:encoded></item></channel></rss>