<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://irace.me/feed.xml" rel="self" type="application/atom+xml" /><link href="http://irace.me/" rel="alternate" type="text/html" /><updated>2025-08-10T16:55:31+00:00</updated><id>http://irace.me/feed.xml</id><title type="html">Bryan Irace</title><subtitle>I’m Bryan – a software developer from Brooklyn, NY.</subtitle><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><entry><title type="html">Tim, don’t kill my vibe</title><link href="http://irace.me/vibe" rel="alternate" type="text/html" title="Tim, don’t kill my vibe" /><published>2025-03-21T00:00:00+00:00</published><updated>2025-03-21T00:00:00+00:00</updated><id>http://irace.me/vibe</id><content type="html" xml:base="http://irace.me/vibe"><![CDATA[<figure>
  <img src="/images/vibe/wall.png" />
</figure>

<p>Recent criticism of Apple’s AI efforts <a href="https://stratechery.com/2025/apple-ais-platform-pivot-potential/">has</a> <a href="https://stratechery.com/2025/apple-ais-platform-pivot-potential/">been</a> <a href="https://daringfireball.net/2025/03/something_is_rotten_in_the_state_of_cupertino">juicy</a> to say the least, but this shouldn’t distract us from continuing to criticize one of Apple’s most deserving targets: App Review. Especially now that there’s a perfectly good AI lens through which to do so.</p>

<p>It’s one thing for Apple’s AI <em>product offerings</em> to be non-competitive. Perhaps even worse is that as Apple stands still, software development is moving forward faster than ever before. Like it or not, LLMs—both through general chat interfaces and purpose-built developer tools—have meaningfully increased the rate at which new software can be produced. And they’ve done so both by making skilled developers more productive while also lowering the bar for less-experienced participants.</p>

<p>Barring a sharp correction, Apple looks increasingly likely to miss out on a generation of developers conditioned to first reach for tools like <a href="https://cursor.com">Cursor</a>, <a href="https://replit.com">Replit</a>, or <a href="https://v0.dev">v0</a>—especially as Apple’s own AI tooling <a href="https://dimillian.medium.com/where-is-swift-assist-6ea348767cf3">remains notably absent</a>. This goes well beyond enabling new entrants to “<a href="https://x.com/karpathy/status/1886192184808149383">vibe code</a>”—experienced mobile developers who, despite history with Xcode and a predilection for building native apps, are begrudgingly swapping out their tools in acknowledgement of the inarguable productivity benefits.</p>

<p>Sure, AI-assisted developer tools <em>can</em> be used to generate native iOS apps, but they’re not nearly as good at this as they are at generating e.g. React, whose developer experience advantage predates the LLM wave and has only since accelerated. While Mobile Safari can run webapps quite well, and native apps <em>can</em> be built using React Native, those clearly aren’t strategically ideal for Apple<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>So iOS apps may increasingly be built using Cursor, and perhaps a larger percentage of them end up being built using React Native. And yet iPhones remain massively popular, so what’s the problem as long as they’re being built?</p>

<p>The wall that you’ll hit when actually trying to distribute them:</p>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">I legit think Apple’s risking it all right now.<br /><br />Creating apps has never been easier. Releasing the app is harder than making it.<br /><br />If they don’t reduce the friction, they will lose. <a href="https://t.co/iZXseNPrjf">https://t.co/iZXseNPrjf</a></p>&mdash; Theo - t3.gg (@theo) <a href="https://twitter.com/theo/status/1892322548626469215?ref_src=twsrc%5Etfw">February 19, 2025</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>App Review has always long been a major source of developer frustration. Authoritarian yet inconsistent policy enforcement aside<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, it’s simply too hard to distribute software <em>even to your own Apple devices</em>, let alone someone else’s. This isn’t new by any means, but as the time to build an app shrinks from weeks/months to hours/days, it feels more egregious—and thus like more of a liability—than ever before.</p>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">It’s just not sustainable for App Review to take longer than building the app itself.<br /><br />Apple’s had no incentive to approve App Review for… ever? Not sure that the decreasing cost of software actually changes that. But between this and the growing gap in dev tools/experience… <a href="https://t.co/zo5ExXxlQb">https://t.co/zo5ExXxlQb</a></p>&mdash; Bryan Irace (@irace) <a href="https://twitter.com/irace/status/1888206108197613806?ref_src=twsrc%5Etfw">February 8, 2025</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>I recently built a small iOS app for myself. I can install it on my phone directly from Xcode but it expires after seven days because I’m using a free Apple Developer account. I’m not trying to avoid paying Apple, but there’s enough friction involved in switching to a paid account that I simply haven’t been bothered. And I used to wrangle provisioning profiles for a living! I can’t imagine that I’m alone here, or that others with <em>less</em> tribal iOS development knowledge are going to have a higher tolerance for this. A friend asked me to send the app to them but that’d involve creating a TestFlight group, submitting a build to Apple, waiting for them to approve it, etc. Compare this to simply pushing to Cloudflare or Netlify and automatically having a URL you can send to a friend or share via Twitter. Or using tools like v0 or Replit, where hosting/distribution are already baked in.</p>

<p>Again, this isn’t new—but being able to build this much software this fast <em>is</em> new. App distribution friction has stayed constant while friction in <em>all other stages</em> of software development has largely evaporated. It’s the difference between inconvenient and untenable.</p>

<p>So what’s the alternative? Tech platforms are dominant until they aren’t and—obviously—iPhone hegemony won’t last forever. When sufficiently zoomed in, it’s really hard to see the first few cracks in the foundation—the harbingers of the beginning of the end, even if we’re a long way from the <em>actual</em> end. I would’ve recently laughed at the suggestion that webapps could legitimately contend on mobile <a href="https://www.macstories.net/stories/the-ipads-sweet-solution/">but I’m no longer laughing</a>. <a href="https://bgr.com/tech/sam-altman-wants-his-upcoming-chatgpt-ai-device-to-replace-your-smartphone/">OpenAI building an iPhone competitor</a> might sound similarly hard to believe, but don’t forget that such skepticism—“The PC guys are not going to just, you know, knock this out. I guarantee it”—was levied by incumbents towards the iPhone itself.</p>

<p>If Apple sees its developer tooling and policies as neatly-paved sidewalks, new <a href="https://en.wikipedia.org/wiki/Desire_path">desire paths</a> are forming—paths increasingly designed to minimize touchpoints with Apple. Alarm bells should be ringing for those concerned with <a href="https://marco.org/2021/06/03/developer-relations">what remains of</a> Apple’s developer goodwill, and consequently, its market position.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>I’d argue that it isn’t ideal for <em>users</em> either, though the product quality gap does seem smaller than ever. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>For the purposes of this essay only—they <em>really</em> shouldn’t be put aside. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/vibe/wall.png" /><media:content medium="image" url="http://irace.me/images/vibe/wall.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Mea culpa</title><link href="http://irace.me/mea-cupla" rel="alternate" type="text/html" title="Mea culpa" /><published>2023-12-26T00:00:00+00:00</published><updated>2023-12-26T00:00:00+00:00</updated><id>http://irace.me/mea-culpa</id><content type="html" xml:base="http://irace.me/mea-cupla"><![CDATA[<figure>
  <img src="/images/mea-culpa/flag.png" />
</figure>

<p>In January I wrote:</p>

<blockquote>
  <p>It’ll be pretty embarassing if this is still my latest post a year from now.</p>
</blockquote>

<p><em>Technically</em> I needn’t be embarassed, but let’s be honest, not writing <em>at all</em> until now clearly isn’t what I intended. It’s been a busy year! I moved into a new house, which honestly took up more time and energy than I ever could have imagined.</p>

<p>But still. More to come.</p>

<center class="centered-tweet"><iframe src="https://infosec.exchange/@codinghorror/111547842851770006/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://assets.infosec.exchange/embed.js" async="async"></script></center>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/mea-culpa/flag.png" /><media:content medium="image" url="http://irace.me/images/mea-culpa/flag.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Watered down (and better for it)</title><link href="http://irace.me/watered-down" rel="alternate" type="text/html" title="Watered down (and better for it)" /><published>2023-12-26T00:00:00+00:00</published><updated>2023-12-26T00:00:00+00:00</updated><id>http://irace.me/watered-down</id><content type="html" xml:base="http://irace.me/watered-down"><![CDATA[<figure>
  <img src="/images/watered-down/dissolve.png" />
</figure>

<p>Studies have shown that algorithmic timelines drive more engagement than reverse-chronological timelines comprised exclusively of content that the user has explicitly chosen to follow. This makes sense—the overwhelming majority of social media users don’t build and cultivate robust networks on these platforms. As such, it shouldn’t be much of a surprise that algorithmic timelines “outperform” what would likely otherwise be a pretty desolate experience.</p>

<p>That said, the few of us who <em>did</em> invest in curation got something even better. Every launch of Twitter or Reddit was comprised purely of <em>exactly what you actually wanted to see</em>. And if you used certain clients—an option that users of both services had until very recently—you could pick up right where you left off.</p>

<p>Exactly what you wanted and right where you left off, always. Without ads! The pure uncut experience. No wonder so many of us were addicted.</p>

<p>Things are very different today. Twitter and Reddit no longer let you use clients that provide this experience. “Following” timelines, while often still possible, no longer sync one’s position and routinely switch back to the algorithmic default. Low-quality ads fill every nook and cranny.</p>

<p>But beyond this, most importantly, there’s just no longer a single Twitter.</p>

<p>When everyone used Twitter, even the algorithmic feed—a worse experience than “Following,” to be sure—was OK. It was primarily comprised of tweets from accounts that you <em>had</em> actually followed, just in a different sort order. Now, with users spread across a myriad of different services, “For You” is full of performative, SEO’d clickbait—the likes of previously only seen on LinkedIn.</p>

<p>Before the diaspora there used to be one place to look and one place to post. But everything’s now spread across Twitter, Threads, Mastodon, and (I think?) Bluesky. Before posting, one must double-check if they’re sharing the right content for the right audience subset. Before reading, one must stop to remember if the app that they’re going to open is actually the one most likely to provide what they’re looking for in this moment. The likelihood that you’ll see what you want is reduced. The likelihood that the right audience will see what you posted is reduced. The likelihood that you’ll open multiple apps only to see the same content multiple times is increased. As is the likelihood that none of them provide the high that used to be available at will.</p>

<p>It adds up. It’s exhausting. It’s impossible to imagine that anyone who spent years with the uncut experience would spend nearly as much time doing all of this.</p>

<p>So we won’t. And we’ll be so much better for it. It couldn’t have been ideal for us to have spent so much time that way, but how could we not have?</p>

<p>We didn’t know how good we had it. But you can have too much of a good thing, so it’s better now that it’s worse.</p>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/watered-down/dissolve.png" /><media:content medium="image" url="http://irace.me/images/watered-down/dissolve.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Reblogging</title><link href="http://irace.me/reblogging" rel="alternate" type="text/html" title="Reblogging" /><published>2023-01-11T00:00:00+00:00</published><updated>2023-01-11T00:00:00+00:00</updated><id>http://irace.me/reblogging</id><content type="html" xml:base="http://irace.me/reblogging"><![CDATA[<figure>
  <img src="images/reblogging/blogger.png" />
  <figcaption>How <a href="http://midjourney.com">Midjourney</a> envisions this post being written. I asked for visible writing on screen, and, well, yep 🤦‍♂️</figcaption>
</figure>

<p>For the third straight year, it was New Year’s Eve and I hadn’t written anything all year. I only had a few hours left to change that, just like <a href="https://irace.me/primer">last year</a> and <a href="https://irace.me/phish">the year before that</a>.</p>

<p>But did I really <em>need</em> to? I didn’t. I enjoyed the holiday with my family and made peace with this site not having anything from 2022, the first year without a post since it was launched<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>How did I get to this point? When did blogging turn from an outlet to an <em>obligation</em>?</p>

<p><a href="https://irace.me/writing/">As you can see</a>, I used to write a decent amount and simply don’t anymore. Two contributing factors are that I haven’t kept up with the topic I used to primarily cover (iOS development), and I got older and busier<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>

<p>But more than either, I think the bigger reason is that rather than trying to expand my thoughts beyond 280 characters, I became content just tweeting something out and being done with it. Any medium-sized thought was either compressed into a tweet or, increasingly infrequently, expanded into an essay.</p>

<p>This wasn’t always the case! <a href="https://irace.me/just-watch">Here‘s a short post</a> that I’m glad wasn’t a tweet, though almost certainly would have been in recent years. <a href="https://irace.me/series-zero">Here’s another</a>.</p>

<p>While the bar for tweeting couldn’t be lower, the bar for publishing here felt like it had grown to be insurmountable. If you’re only going to publish once a year, it had better be good, right<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>?</p>

<p>No more. This is my space on the Internet and I can change it however I want such that I’ll actually start to use it again. And I will<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>.</p>

<p>While my inability to blog clearly isn’t a recent phenomenon, Twitter’s current managerial situation has unsurprisingly prompted a lot of my recent reflections on the topic. As articulated well in <a href="https://www.theverge.com/23513418/bring-back-personal-blogging">Bring back personal blogging</a>:</p>

<blockquote>
  <p>The biggest reason personal blogs need to make a comeback is a simple one: we should all be in control of our own platforms.</p>

  <p>If what is happening on Twitter hasn’t demonstrated it, our relationship with these social media platforms is tenuous at best. The thing we are using to build our popularity today could very well be destroyed and disappear from the internet tomorrow, and then what?</p>

  <p>Owning your content and controlling your platform is essential, and having a personal blog is a great way to do that.</p>
</blockquote>

<p>So here I am. It’ll be pretty embarassing if this is still my latest post a year from now.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Funnily, I likely wouldn’t have ever cared if the design of my <a href="https://irace.me/writing/">Writing</a> page didn’t group posts by year. I could have just changed the design… <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I have two kids now! <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>I felt like <a href="https://irace.me/phish">my one post from 2020</a> actually <em>was</em>. <a href="https://irace.me/primer">Last year’s</a>, less so. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>I’ve always liked how <a href="https://mgsiegler.com">M.G. Siegler</a> keeps different blogs for different purposes: <a href="https://500ish.com">500ish</a> for posts almost exactly like this one, <a href="https://5ish.org">5ish</a> for links, etc. He’s a prolific writer and I have to imagine that the different framings serve as somewhat of a mental lubricant. While I only plan to write here, there are tweaks I can and probably should make: renaming “writing” to “blog,” allowing myself to post without including a header image, etc. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[How Midjourney envisions this post being written. I asked for visible writing on screen, and, well, yep 🤦‍♂️]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/reblogging/blogger.jpg" /><media:content medium="image" url="http://irace.me/images/reblogging/blogger.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The read-write web in 2021, or “A Young Man’s Illustrated Primer”</title><link href="http://irace.me/primer" rel="alternate" type="text/html" title="The read-write web in 2021, or “A Young Man’s Illustrated Primer”" /><published>2021-12-31T00:00:00+00:00</published><updated>2021-12-31T00:00:00+00:00</updated><id>http://irace.me/the-read-write-web-in-2021</id><content type="html" xml:base="http://irace.me/primer"><![CDATA[<figure>
  <img src="images/primer/books.jpg" />
</figure>

<p>The web feels a bit more malleable than it did just a couple of years ago. Why might that be?</p>

<p>Knowledge management software has been with us for decades but only recently feels like it’s become a fixture of how early adopters use the web<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. Networking one’s own personal notes might just be a step towards having the web be authored by a much larger subset of its users, doing so from rich text environments rather than IDEs. Great products like <a href="https://roamresearch.com">Roam Research</a><sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> have started to popularize the benefits of contributing nodes and edges to a knowledge graph, making their creation feel as frictionless as bolding or italicizing. How does networked note-taking lead to web <em>authoring</em>? I’m confident that we’ll see this new class of tools increasingly lead to our “notes” becoming both collaboratively authored <em>and</em> publicly readable, at which point the lines will have become quite blurry<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>.</p>

<p>In Neal Stephenson’s <a href="https://www.goodreads.com/book/show/827.The_Diamond_Age">The Diamond Age</a>, our young protagonist comes to possess a copy of <em>The Young Lady’s Illustrated Primer</em>, an interactive storybook designed to teach its owner everything they need to know through choose-your-own-adventure edutainment. The more I thought about Roam’s choice to include the word “research” in their product name, the more it helped me reframe my own relationship with the web. “Browsing”<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup> started to feel passive or reactive (if not a bit wasteful), while <em>researching</em>—reading but crucially also <em>writing</em>—felt proactive and enriching, a way to <a href="https://nesslabs.com/conor-white-sullivan-interview">earn compound interest on your thoughts</a>.</p>

<p>Researching made me feel like I was meaningfully investing in myself, not unlike how writing had in the past but now with tools tailor-made to empower. Networked note-taking became my Primer—tangents could be sought out instead of avoided, with backlinks turning the anecdotal into the indexed, the random into the patterned, the hazy into the coherent. Unsurprisingly, the web feels more pliable when you feel like a main character as opposed to an outside observer.</p>

<p>Next, the explosion of blockchains and distributed applications feels like a significant lowering of the barrier to contribution and participation. Yes, open source software predates smart contracts and traditional REST APIs can already be built on top of. But the <a href="https://future.a16z.com/how-composability-unlocks-crypto-and-everything-else/">composability</a> and interoperability inherent in these new ecosystems brings increased opportunity to leverage existing code and data, both to adapt in new ways or to wrap in higher levels of abstraction.</p>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">My simplistic mental model:<br />• Web 2.0: Each service has their own authentication/database, owns a subset of each user’s data.<br />• web3: User data lives in a public ledger, is brought from service to service by the user who owns their own public keys. Auth by connecting wallet.</p>&mdash; Bryan Irace (@irace) <a href="https://twitter.com/irace/status/1452389822513942543?ref_src=twsrc%5Etfw">October 24, 2021</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>Building on top of existing code, data, and authentication affords the benefit of focusing on novel logic and UX alone, and unsurprisingly has led to an incomprehensible number of new projects. The overwhelming majority won’t succeed, but we can repurpose a <a href="https://stratechery.com/2019/shopify-and-the-power-of-platforms/">Ben Thompson quote</a> about Shopify to understand why this can be reason for optimism:</p>

<blockquote>
  <p>I would argue that for Shopify a high churn rate is just as much a positive signal as it is a negative one: the easier it is to start an e-commerce business on the platform, the more failures there will be. And, at the same time, the greater likelihood there will be of capturing and supporting successes.</p>
</blockquote>

<p>When I think of the web, I think of a series of siloed databases and applications: built by engineers, stitched together by crawlers, and served to the masses via search. Too often, this doesn’t feel particularly approachable <em>even to me</em>, someone with a career of experience building exactly these kinds of systems. It’s not purely about having the requisite knowledge<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>—the activation energy currently required to create something out of nothing undeniably keeps far too many good ideas from ever evolving past that.</p>

<p>But I’m optimistic that this can change in a big way. Are networked notes and blockchains really going be meaningful change agents? Perhaps not. But on this last day of 2021, they’re what come to mind.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Sure, we all <em>use</em> Wikipedia, but how many of us contribute to it—or better yet, maintain wikis of our own? <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>As well as others like <a href="https://obsidian.md">Obsidian</a>, <a href="https://logseq.com">Loqseq</a>, and <a href="https://reflect.app">Reflect</a>, but let’s give credit where credit’s due. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>See my friend <a href="https://twitter.com/orta">Orta</a>’s <a href="http://orta.io/notes/recommendations/books">public notes</a>, implemented using <a href="https://github.com/foambubble/foam">Foam</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Or worse, “surfing.” <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p><a href="https://irace.me/documents-vs-apps">No-code</a> tools have long been making inroads here. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/primer/books.jpg" /><media:content medium="image" url="http://irace.me/images/primer/books.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Phish and digital content strategy</title><link href="http://irace.me/phish" rel="alternate" type="text/html" title="Phish and digital content strategy" /><published>2020-12-31T00:00:00+00:00</published><updated>2020-12-31T00:00:00+00:00</updated><id>http://irace.me/phish-and-digital-content-strategy</id><content type="html" xml:base="http://irace.me/phish"><![CDATA[<figure>
  <img src="images/phish/nye.jpg" style="margin-bottom: 0;" />
  <figcaption>Photo by Rene Huemer via <a href="https://www.instagram.com/p/BsFl82YnQ37/">Phish: From The Road</a></figcaption>
</figure>

<p>Let’s start by getting the tropes out of the way: drugs, hippies, <a href="https://www.benjerry.com/flavors/phish-food-ice-cream">Ben &amp; Jerry’s</a>, “the Grateful Dead were better,” <a href="https://www.austinchronicle.com/music/2000-06-16/77606/">that “Gin and Juice” cover from Napster with the wrong MP3 metadata</a>. And say what you will about their music; love it, hate it, or have no opinion whatsoever<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, this essay isn’t really about music. <mark>It’s about a unique–and I have to think <em>incredibly successful</em>–digital content strategy unlike anything else I’ve ever seen, and seemingly nearly impossible to replicate.</mark></p>

<p>Before we dig in, let’s jump back a couple of decades.</p>

<p>In the heyday of the aforementioned Napster and during the transition from physical media to streaming video, the music and film industries attempted to combat piracy by clamping down as hard as possible with restrictive DRM, unskippable DVD preambles, and exorbitant lawsuits. Pundits and industry observers bemoaned this approach, countering that an effective defense wouldn’t be through draconian restrictions but by competing on quality and convenience; that DRM and similarly user-hostile approaches would lead to <em>more</em> piracy in service of a better user experience irrespective of cost. That the way to win was by charging a fair price for a <em>superior</em> product.</p>

<p>If services like Netflix and Spotify have proven this to be true<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, Phish has taken it to an extreme. While most bands would consider their studio records to be more canonical than their live recordings, the opposite is true for Phish. Yet Phish embraces the recording and sharing of their live music rather than trying to stop it–you can legally listen to hundreds of Phish shows right now <a href="http://www.phishtracks.com">just by going to this website</a>.</p>

<p>This isn’t because they don’t want you to pay for their music, however. To the contrary, their <a href="https://livephish.com">LivePhish</a> website competes on quality and convenience by making every show since 2002 available:</p>
<ul>
  <li>In lossless audio quality, straight from the soundboard</li>
  <li>Literally within minutes of each concert ending<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></li>
  <li>To either download or stream (via web, iOS/Android/Apple TV apps, Sonos, etc.)</li>
  <li>To purchase either à la carte or through a monthly subscription</li>
  <li>With beautiful, thoughtful, artwork unique to that show or tour</li>
</ul>

<p>Oh, and they also broadcast the live video streams of these shows. In high-definition. With extremely high-quality camerawork. For a fair price, naturally.</p>

<p>At this point, you might be thinking that this doesn’t actually sound particularly lucrative. After all, if you’ve heard one live show haven’t you heard them all?</p>

<p>This would be the case for most bands but not for Phish. Phish plays with an improvisational style which means no two shows are ever the same. <mark>If this sounds like an exaggeration, it isn’t</mark>. All four band members are virtuosic in their own right, but have also been playing together for almost 40 years as of this writing. This gives them both an enormous repertoire to draw from<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>, plus the ability to freely experiment with near any track without veering off the rails in a way that often feels impossible.</p>

<p>So that’s the playbook, unemulatable as it may be: <mark> pick a musical genre that lends itself incredibly well to improvisation, spend four decades mastering it, stand up the requisite streaming/recording/distribution infrastructure, then turn on the recurring SaaS revenue faucet</mark>. Most bands simply <em>do</em> play more-or-less the same set for the entirety of a tour, and there’s nothing wrong with that. Those bands just can’t expect their fans to pre-order a whole tour’s worth of MP3s and/or live-streamed webcasts. Phish can, and they unabashedly lean into it. In 2017, they played <a href="https://www.rollingstone.com/music/music-live-reviews/phishs-bakers-dozen-residency-breaking-down-all-13-blissful-nights-197436/">thirteen straight shows at Madison Square Garden without repeating a single song</a><sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>.</p>

<p>And Phish fans happily capitulate. Here’s <a href="https://mashable.com/article/phish-streaming-pioneers-band/">a Mashable article from 2018</a> that, in addition to detailing the evolution of Phish’s digital streaming empire, nicely summarizes the fan mentality:</p>
<blockquote>
  <p>Each one is unique, and if you’re a fan, you want to hear every possible version of your favorite song, and to collect them all. As a fan of the band for the last 23 years, I’ve been hell-bent on trying.</p>
</blockquote>

<p>The author isn’t alone. Phish fans <a href="https://phish.net/song">crowdsource song statistics</a> with the fervor of sabremetricians studying nascent baseball defensive metrics.</p>

<p>So while the band is clearly embracing the opportunity in front of them–each performance capitalizing on the chance to produce new differentiated content–they’re only able to do so because they <em>happen</em> to perform in a style that makes their music uncommoditizable. And that’s, frankly, lucky. When <a href="https://en.wikipedia.org/wiki/Junta_(album)">Junta</a> debuted in 1989, foreshadowing their trademark lack of brevity with five tracks stretching beyond the nine minute mark, they certainly didn’t foresee each bespoke incarnation becoming an elaborate snowflake sold to voracious adorers over HTTP. This would be a lot harder if they played e.g. <a href="https://en.wikipedia.org/wiki/Three-chord_song">three-chord</a> punk rock instead.</p>

<p>Like most software companies with high multiples, Phish benefits from what <a href="https://stratechery.com">Stratechery</a>’s <a href="https://twitter.com/benthompson">Ben Thompson</a> sums up as <a href="https://stratechery.com/concept/aggregation-theory/distribution-and-transaction-costs/">the effective elimination of marginal distribution and transaction costs</a> brought about by the Internet. Software companies traditionally have high P/E multiples because of this dynamic–there’s a fixed cost inherent in developing an application but no substantive additional cost to onboarding as many new users as possible over time, subsidizing the original expenditures. Phish benefits similarly–while there are certainly some ongoing costs to film and edit each show, the infrastructure exists and they clearly have it down to a science at this point (see: the truly-hard-to-believe turnaround time).</p>

<p>Is Phish’s audience big enough, though? Of course, when the total addressable market is “anyone with an Internet connection.” <a href="https://stratechery.com/2020/never-ending-niches/">Ben Thompson, again</a>:</p>
<blockquote>
  <p>While quality is relatively binary, the number of ways to be focused — that is, the number of niches in the world — are effectively infinite; success, in other words, is about delivering superior quality in your niche — the former is defined by the latter.</p>
</blockquote>

<figure>
  <img src="images/phish/niches.png" style="margin-bottom: 0;" />
  <figcaption>Source: <a href="https://stratechery.com/2020/never-ending-niches/">Never-Ending Niches, Stratechery</a></figcaption>
</figure>

<p>And if you’re lucky enough to be the type of person who enjoys Phish’s music, the quality of their offering is undeniable. So much so that they’re able to charge just as much as Spotify does for Spotify Premium. Put another way by <a href="https://marco.org/2013/04/22/business-of-phish">Marco Arment</a>:</p>
<blockquote>
  <p>If you’ll permit a pretty rough analogy, imagine a world in which the vast majority of published fiction was in the form of 3,000-word short stories, and most people had never read anything longer. Phish is the one outlier publishing novels, and they’re pretty weird, complex novels. No effort to condense such novels into bite-sized short stories will truly capture the appeal.</p>

  <p>But if you’re one of just a handful of novel publishers in this rough metaphor, you’re going to slowly accumulate a hell of a fanbase from the people who actually like novels, even if yours get a bit too weird sometimes, because almost nobody else is creating what these fans want and love.</p>
</blockquote>

<p>Taking a look at <a href="http://twitter.com/nbashaw/">Nathan Baschez</a>’s <a href="https://divinations.every.to/p/why-content-is-king">Why Content is King</a>, it’s pretty remarkable how many of the <a href="https://www.amazon.com/dp/B01MRLFFQ7/ref=dp-kindle-redirect?_encoding=UTF8&amp;btkr=1">seven power</a> boxes Phish is able to check<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup>:</p>
<ul>
  <li>Scale economics (free distribution)</li>
  <li>Network economies (crowdsourced cataloging by the fan community)</li>
  <li>Counter positioning (producing new differentiated content in a way that few other bands can)</li>
  <li>Branding</li>
  <li>Cornered resource (a monopoly on soundboard-quality Phish recordings)</li>
  <li>Process power (filming/recording/editing/distribution infrastructure)</li>
</ul>

<p>And if you find yourself thinking that a shrewd move would be to reuse the underlying LivePhish platform for other bands as well, not unlike how Amazon sells access to AWS despite <a href="https://stratechery.com/2017/amazons-new-customer/">being its first-and-best-customer</a>, the folks behind <a href="https://www.nugs.net">nugs.net</a> would agree<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">7</a></sup>.</p>

<p>At the end of the day, it‘s not lost on me that no amount of strategic novelty will change your mind if you simply can’t stand Phish’s music (and I <em>truly</em> understand why many cannot). But like them or not, they undoubtedly occupy a unique space amongst their peers. The technologist in me can’t help but also take satisfaction in the alignment of their digital offerings with their strongsuits as musicians.</p>

<hr />

<p>Many thanks to <a href="https://twitter.com/benr75">Ben Reubenstein</a> for his feedback on a draft of this post.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>If you’re looking for a more general introduction, I recommend <a href="https://marco.org/2011/05/26/geek-intro-to-phish">this one</a> by <a href="https://twitter.com/marcoarment">Marco Arment</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>To be clear, I don’t feel knowledgable enough to opine on whether or not Spotify/Apple Music/etc. really charge “fair prices” insofar as how artists end up getting compensated. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Phish also gives all concert attendees a free copy of that show’s MP3s via their ticket stub, which I think is a delightful gesture. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>They also have a proclivity for cover songs, covering a different album in full each Halloween. This only adds to the gigantic bucket of songs that might make a surprise appearance on any given night. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>If seeing a totally different show each night wasn’t enough, the band also <a href="https://www.jambase.com/article/go-behind-donuts-phish-bakers-dozen-residency-felicia-federal-donuts">went as far as to provide donuts</a>. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>They do <em>not</em> have high switching costs but that’s clearly in the interest of competing on convenience (allowing MP3s to be downloaded as opposed to purely streamed). <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">
      <p>It’s not clear to me exactly how or if Phish directly profits from other artists <a href="https://www.nugs.net">nugs.net</a>, but it is <a href="https://www.pollstar.com/article/qs-with-nugsnets-brad-serling-how-streaming-platform-is-adapting-during-coronavirus-crisis-144283">run by the same team behind LivePhish</a>. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[Photo by Rene Huemer via Phish: From The Road]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/phish/nye.jpg" /><media:content medium="image" url="http://irace.me/images/phish/nye.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">On specialism vs. generalism</title><link href="http://irace.me/generalism" rel="alternate" type="text/html" title="On specialism vs. generalism" /><published>2019-05-18T00:00:00+00:00</published><updated>2019-05-18T00:00:00+00:00</updated><id>http://irace.me/on-generalism-vs-specialism</id><content type="html" xml:base="http://irace.me/generalism"><![CDATA[<figure>
  <img src="/images/generalism/leaves.jpg" />
</figure>

<p>“You’re basically not going to be an iOS engineer anymore?”</p>

<p>When my good friend <a href="https://twitter.com/khanlou">Soroush</a> asked this upon hearing that I had taken a new job at Stripe, I doubt he thought very much about how it’d be received. It really threw me for a loop, however. I didn’t exactly consider myself to be undergoing a career change, but was I? It’s true that I’m not going to be developing for iOS in my new role, but I hadn’t <em>always</em> worked in this capacity at previous jobs either. Did spending the better part of five years focused on iOS make me an “iOS engineer”? If so, when exactly did I become one and when did I subsequently cease to be? Should this kind of designation be descriptive, based on one’s actual day-to-day, or prescriptive, aspirationally describing the work being primarily sought out and anticipated?</p>

<p>Work as a software engineer for long enough and it’s highly likely that you’ll end up having a say over whether or not you go deep on any particular sub-discipline (and if so, which), or choose to primarily float around the surface, swimming back and forth and maybe holding your breath to go under for a bit here and there, but not taking many dives necessitating special training or equipment.</p>

<p>There’s no right answer here, and it’s really not a strict dichotomy anyway.</p>

<p>While programmers <em>can</em> undeniably be either specialists or generalists, there’s a whole lot of grey in the middle. As opposed to inherently <em>being</em> a specialist, it’s also very common to <em>specialize over a period of time</em>. Perhaps this is a subtle difference, but I think it’s one worth teasing apart; one can act in a specialist capacity when the situation dictates - and I presume that effectively every “generalist” does, from time to time - without self-identifying as such for the long haul.</p>

<p>There isn’t a right answer because one isn’t better than the other, but also because many teams should contain both specialists and generalists in order to perform their best work. The best products are often brought to fruition through a combination of generalist thinking <em>and</em> specialist expertise. Only specialists have the domain knowledge necessary to build best-in-breed software that takes full advantage of the platform being built for; given how advanced the various platforms that we build for in 2019 have gotten, it’d be near impossible to sweat all the right details without having first dedicated yourself to fundamentally understanding a particular one’s intricacies. We’re all very lucky that many have.</p>

<p>At the same time, specialists run the risk of “only having a hammer,” and as such, having every possible project “look like a nail.” With only one tool in your belt - a deep but relatively narrow area of expertise - it’s easy to inadvertently build an app that really should’ve been a website or vice versa. Or to have a great idea that you can’t quite realize, despite your excitement, due to it requiring both frontend and backend work. Said idea might be exactly the provocation that can prompt one who has historically specialized to start branching out a bit. But after having done so, are they still “a frontend developer” or “a backend developer”? Clearly, such labels start to lose their significance as we tear down the boundaries defining what we’re able to do, and perhaps more importantly, what we’re <em>interested</em> in doing.</p>

<p>In the Twitter, Slack, and GitHub circles that modern software developers often travel in, it’s easy for a discrepancy to form between how one is best known vs. how they actually view themselves. Tumblr was quite popular during the time that I led iOS development there, which gave me the opportunity to <a href="https://irace.me/tumblr-ios-extension">write</a> and <a href="https://speakerdeck.com/irace/ios-at-tumblr">speak</a> about the work that we were doing, and even <a href="https://github.com/tumblr/TMTumblrSDK">release some of it as open source</a>. These slide decks and blog posts neglected to mention that I was actually hired to be a web developer and only moved over to iOS as needs arose, subsequently parking myself there for a few years to come. I built Rails backends and React frontends at my next job, but <a href="https://irace.me/prefer">at an early-stage company</a> with a much smaller platform, where we primarily worked heads-down without much outward-facing evangelism for our technology. Few knew.</p>

<p>I’m not unique in this regard. One of the best mobile developers from my time at Tumblr has since switched over to the web. Another, an expert in animations, gestures, and UI performance, is now a designer. Since acting as a specialist at a high-profile company can cement your status as such well after you’ve stopped working in that capacity, it’s crucial not to let outside perception prevent you from shaping your career however you see fit.</p>

<p>In August 2014, I gave a talk entitled <a href="https://speakerdeck.com/irace/dont-be-an-objective-c-or-a-swift-developer">Don’t be “an Objective-C” or “a Swift Developer”</a> to a room full of new programmers who were learning how to build iOS applications at the <a href="https://flatironschool.com/">Flatiron School</a>. The Swift programming language had been unveiled only two months prior, and reactions amongst iOS developers were divisive, to say the least. Many felt as though it was <em>finally</em> <a href="https://ashfurrow.com/blog/we-need-to-replace-objective-c/">time for a modern language to replace Objective-C</a>, and that such a change was long overdue, while others didn’t believe that Objective-C needed fixing, and would’ve preferred if Apple’s resources and the focus of its community were directed elsewhere. My goal was to try and convince these new engineers that they shouldn’t aspire to land in one camp or the other, but rather to learn the underlying, transferrable programming concepts, and to expose themselves to many different ways of concretely building software. Without understanding what’s out there, how can one make an informed decision as to how they should spend their time? Even if you decide to put down roots in a single community, how can you avoid perceiving the way that that community has historically operated as being the way that it should be going forward?</p>

<p>I feel like I could give this same talk today, to a more experienced set of engineers no less, and simply replace “Objective-C and Swift” with “frontend and backend” or “mobile and web.” The idea is the same - technologies move fast and careers are long, and while you may enjoy being a specialist or a generalist for some time, you never really know when your situation could change and when circumstances may warrant otherwise. Or, when you might simply feel like trying something new.</p>

<p>When I write Ruby, it’s painfully obvious to me that I don’t know Ruby to nearly the same extent that I <em>know</em> Swift. On some days, this makes me sad, but it just as often makes me feel empowered. Perhaps I’ll decide to spend the time needed to achieve Ruby mastery, or maybe I’ll end up retreating back to Swift at some point in the future. Or, more realistically, I’ll get slightly better at the former and slightly worse at the latter and come to peace with that, just in time to shift towards learning something different altogether. In any case, <mark>how others describe what I do, and more importantly, how I view it myself, remains a fluid work in progress</mark>.</p>

<p>I don’t expect this to change, and this I <em>am</em> at peace with.</p>

<hr />

<p><a href="https://medium.com/better-programming/on-specialism-vs-generalism-and-not-being-an-ios-engineer-anymore-ed5fa65ed76e">Originally published</a> on <a href="https://medium.com/better-programming">Better Programming</a>.</p>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/generalism/leaves.jpg" /><media:content medium="image" url="http://irace.me/images/generalism/leaves.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Software without coding, documents vs. apps, or the impossible dream of the “personal CRM”</title><link href="http://irace.me/documents-vs-apps" rel="alternate" type="text/html" title="Software without coding, documents vs. apps, or the impossible dream of the “personal CRM”" /><published>2019-02-19T00:00:00+00:00</published><updated>2019-02-19T00:00:00+00:00</updated><id>http://irace.me/documents-vs-apps</id><content type="html" xml:base="http://irace.me/documents-vs-apps"><![CDATA[<figure>
  <img src="/images/documents-vs-apps/legos.jpg" />
</figure>

<p>A couple of years ago, I <a href="https://twitter.com/irace/status/798545010723590144?ref_src=twsrc%5Etfw">asked for a recommendation</a> for a “personal CRM” – a client-relationship management tool intended for individual use. As it turns out, I <a href="https://twitter.com/nakul/status/1002231482931470338">wasn’t</a> <a href="https://twitter.com/delk/status/1025525171292057600">the</a> <a href="https://twitter.com/ganeumann/status/1086457913856663552">only</a> <a href="https://www.producthunt.com/ask/228-what-is-the-best-personal-crm">one</a> asking, and a solution still hasn’t emerged despite outsized interest from the very designers and developers who should be well-poised to create it. Why?</p>

<p>I’ve come to believe that this is because “personal CRM” means something a bit different to each person who asks for it. To <a href="https://twitter.com/ShaneMac/status/1095787850501685249">my friend Shane</a>, it’d be akin to a proactive virtual assistant, skewing towards the “magical” end of the spectrum. I myself have envisioned a few different products, all which I’ve lazily described using this umbrella term: one for letting me know when I haven’t seen a certain friend in a while, another for applying “tags” to former coworkers, etc. During my current job search, I’ve needed different tools at different times: one centered around colleague outreach to start, but another for tracking interview processes as the’ve continued to progress.</p>

<p>When faced with a moving target and only concerned with meeting one’s own individual needs, we don’t need an “app” or a “product” per se. A document can be structured in a way that meets today’s needs, with the flexibility to easily evolve as requirements change. My “personal CRM” is currently a spreadsheet – I could’ve built a version using Excel or Google Sheets, but I chose <a href="https://airtable.com">Airtable</a> because it stores data in a more structured, database-like format. This lets me build more powerful, customized views on top of my data.</p>

<p>A database. With custom UI sitting on top of it.</p>

<p>This is “an app,” my friends.</p>

<h2 id="documents-vs-apps">Documents vs. apps</h2>

<p>From this perspective, Airtable isn’t necessarily a product in-and-of-itself, but a platform that empowers you to build your <em>own</em> products – and it’s far from the only one. <a href="https://coda.io">Coda</a>, <a href="https://www.notion.so">Notion</a>, <a href="https://quip.com">Quip</a>, <a href="https://clay.run">Clay</a>, and <a href="https://www.actiondesk.io">Actiondesk</a> are also similar in that they’re democratizing software development under the familiar guise of “collaborating on documents.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>” But their document-sheen is only skin-deep:</p>

<blockquote>
  <p>A creative palette of app-like functionality that you can mix and match</p>
</blockquote>

<blockquote>
  <p>Build something as unique as your team</p>
</blockquote>

<blockquote>
  <p>We took the quintessential parts of apps and turned them into building blocks for your docs</p>
</blockquote>

<blockquote>
  <p>Our goal is to make it much easier to build software</p>
</blockquote>

<blockquote>
  <p>Forget the back and forth with your tech teams. Build powerful automations yourself</p>
</blockquote>

<p><a href="https://zapier.com/">Zapier</a>, <a href="https://ifttt.com/">IFTTT</a>, and Apple’s <a href="https://support.apple.com/guide/shortcuts/welcome/ios">Shortcuts</a> focus more on integrations and less on data storage and UI, but they’re also key parts of this emerging space – not all software needs a visual user interface<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. Even Slack – an enterprise chat application on the face of it, has ingrained itself in no small part due to its integrations platform, allowing teams to build mini-products and workflows that suit their own specific needs.</p>

<p>These aren’t necessarily new ideas – Excel macros and <a href="https://www.labnol.org/internet/website-uptime-monitor/21060/">Google Sheets scripts</a><sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> are less holistic solutions that nonetheless strive to answer the same question: how can we lower the software development barrier to entry?</p>

<h2 id="apps-for-small-audiences">Apps for small audiences</h2>

<p>Part of what made my own CRM so easy to build was the assumption that I’m going to be the only one who ever uses it. Turning it into a consumer-facing product would be a much taller task, but there’s still a ton of value to be captured by software that’s only used by individuals or <a href="https://twitter.com/NatSandman/status/1097853881265020928?ref_src=twsrc%5Etfw">teams internal to a company</a>.</p>

<p>Shishir Mehrotra, the co-founder and CEO of Coda, highlighted this very point in a piece titled <a href="https://hbr.org/2019/01/what-will-software-look-like-once-anyone-can-create-it">What Will Software Look Like Once Anyone Can Create It?</a>:</p>

<blockquote>
  <p>We’ll start designing apps for small audiences, not big. Companies will run on their own apps, hundreds of them, tailor-made for every team, project, and meeting. In this world, there’ll be no such thing as an edge case. All the previously underserved teams and individuals will get a perfect-fitting solution without needing to beg an engineer.</p>
</blockquote>

<p>Internal applications also have lower user experience expectations than their consumer-facing counterparts, and are simpler for users to authenticate with. And by <em>providing</em> value, they don’t require business models of their own to justify the upfront development cost.</p>

<p>That doesn’t mean that these hurdles can’t be overcome, however – we may be building some consumer applications without code sooner than we know it. A product like <a href="http://squarespace.com">Squarespace</a> could shift ever-so-slightly towards app development, or Google’s <a href="https://firebase.google.com">Firebase</a> could aim for broader consumer appeal. <a href="https://bubble.is">Bubble</a> is one platform purporting to <a href="https://techcrunch.com/2018/11/11/bubble-lets-you-create-web-applications-with-no-coding-experience/">already facilitate this sort of codeless development experience</a> today. At least in some cases, <a href="https://twitter.com/andupotorac/status/1097786137748353025">it does seem to be working</a>.</p>

<p>Another approach: instead of using one app-building “platform”, what if the makers of the future study the skills necessary to combine existing applications together themselves, instead of studying computer science fundamentals? <a href="https://www.makerpad.co">MakerPad</a> is one such example of a program and community attempting to educate in this regard:</p>

<center class="centered-tweet"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">✔️ Airbnb-clone marketplace w/ no-code<br /><br />- Pages auto-generated from CMS<br />- Book via <a href="https://twitter.com/airtable?ref_src=twsrc%5Etfw">@airtable</a> <br />- <a href="https://twitter.com/zapier?ref_src=twsrc%5Etfw">@zapier</a> updates the row<br />- <a href="https://twitter.com/stripe?ref_src=twsrc%5Etfw">@stripe</a> invoice sent<br />- Once paid, the row is &#39;confirmed&#39; in calendar on site<br /><br />Tutorial 🔜<a href="https://t.co/fUpbUZpqwR">https://t.co/fUpbUZpqwR</a> <a href="https://t.co/yHP3qxkjE2">pic.twitter.com/yHP3qxkjE2</a></p>&mdash; Ben Tossell (@bentossell) <a href="https://twitter.com/bentossell/status/1093921136419713026?ref_src=twsrc%5Etfw">February 8, 2019</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>While such an approach may not suffice in perpetuity, it could mean the difference between bootstrapping yourself to profitability and prematurely taking venture capital in order to hire an engineering team.</p>

<h2 id="what-happens-to-software-developers">What happens to software developers?</h2>

<p>As a software developer, should I find this concerning? I admittedly do not, and think that Steven Sinofsky succinctly articulates why by providing some key historical context:</p>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">One thing to consider is that no-code tools arise when there&#39;s an implied consensus that platforms are stable. One takeaway is that the web, iOS, android are not going to change a ton in the foreseeable future so tools can abstract across them.</p>&mdash; Steven Sinofsky (@stevesi) <a href="https://twitter.com/stevesi/status/1097564918939758592?ref_src=twsrc%5Etfw">February 18, 2019</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="en" dir="ltr">This is all a point in time thing. The longer term challenge is that software built this way may/may not be more difficult to adapt when the platforms do change.<br /><br />Platform makers put some effort into moving ‘native’ apps to new eras, but abstracted apps get stuck.</p>&mdash; Steven Sinofsky (@stevesi) <a href="https://twitter.com/stevesi/status/1097588258412802049?ref_src=twsrc%5Etfw">February 18, 2019</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>The layers of abstraction that software developers work on top of <a href="https://twitter.com/btaylor/status/1097592086998614016">are continually changing</a>, but there will always be problems to solve despite moving higher and higher up the stack. Most of the platforms covered in this essay already integrate with the popular services of the moment, but developers will always have an advantage insofar as being able to build their <em>own</em> integrations to augment whatever a platform vendor happens to provide out-of-the-box. Understanding databases makes it easier to create a complex Airtable sheet, just as being familiar with loops and conditionals makes you better equipped to craft <a href="https://www.macstories.net/tag/shortcuts/">a complicated workflow</a> in the Shortcuts app. The line between what is and isn’t “programming” <a href="https://twitter.com/viticci/status/840322224708026369">really starts to blur</a>.</p>

<p>Similarly, there has always been a gradient between what can be done without code and when you’ll eventually hit a wall with that kind of approach. <a href="https://en.wikipedia.org/wiki/Adobe_Dreamweaver">Adobe Dreamweaver</a> never quite obviated writing your own custom HTML, did it? I don’t personally foresee this ceasing to be the case.</p>

<p>But traditional software development being long for this world isn’t an excuse not to keep up with the changing times. Adobe’s <a href="https://support.airtable.com/hc/en-us/articles/360009887334-Airtable-for-Adobe-XD">new XD/Airtable integration</a> might be marketed as a prototyping tool today, but how long will it be until those prototypes are good enough for a single designer to ship as production software?</p>

<p><a href="https://en.wikiquote.org/wiki/Eric_Shinseki">If you don’t like change, you’re going to like irrelevance even less</a>.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Coda goes so far as to intelligently translate traditional desktop documents into common mobile app paradigms. A section in a document becomes a tab in an app’s navigation, and a select box with multiple states can be edited using a native swipe-gesture. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Especially as voice control and home automation continue to increase in prominence. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Not to mention, entire <a href="https://www.labnol.org/internet/website-uptime-monitor/21060/">Google Sheets apps</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/documents-vs-apps/legos.jpg" /><media:content medium="image" url="http://irace.me/images/documents-vs-apps/legos.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">GraphQL and the end of shared client-side model objects</title><link href="http://irace.me/graphql" rel="alternate" type="text/html" title="GraphQL and the end of shared client-side model objects" /><published>2019-02-08T00:00:00+00:00</published><updated>2019-02-08T00:00:00+00:00</updated><id>http://irace.me/graphql-and-the-end-of-shared-client-side-models</id><content type="html" xml:base="http://irace.me/graphql"><![CDATA[<figure>
  <img src="/images/graphql/dinosaurs.jpg" />
</figure>

<p>In traditional client-server development, client-side models don’t often differ too dramatically from server-side models.</p>

<p>Should they?</p>

<p>A standard RESTful API might serialize a server-side user model as such:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">12345</span><span class="p">,</span><span class="w">
  </span><span class="nl">"firstName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bryan"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"lastName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Irace"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"avatar"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"thumbnail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://some/s3/url/a39d39fk"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"large"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://some/s3/url/39fka39d"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"profession"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Software developer"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"location"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Brooklyn"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NY"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"friendCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">40</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Instances of this same user model type can be vended by multiple routes, e.g.:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">/me</code> – A route that returns the currently authenticated user</li>
  <li><code class="language-plaintext highlighter-rouge">/friends</code> - A route that returns the current user’s friends</li>
</ol>

<p>RESTful APIs aren’t inherently type-safe, so a frontend developer will generally learn that these routes both return objects of the same <code class="language-plaintext highlighter-rouge">User</code> type by looking at the API documentation (and hoping that it’s accurate<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>), or by eyeballing the HTTP traffic itself.</p>

<p>After realizing this, a type definition like the following can be manually added to your client-side application, instances of which you can populate when parsing response bodies from either of these two routes:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Avatar</span> <span class="p">{</span>
  <span class="nl">thumbnail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">large</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">User</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
  <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">lastName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">avatar</span><span class="p">:</span> <span class="nx">Avatar</span><span class="p">;</span>
  <span class="nl">profession</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This shared, canonical <code class="language-plaintext highlighter-rouge">User</code> model can be used by any part of the frontend application that needs any subset of a user’s attributes. You can easily cache these <code class="language-plaintext highlighter-rouge">User</code> instances in your client-side key-value store or relational database.</p>

<p>Suppose that your application includes the following capabilities (and is continuing to grow in complexity):</p>

<ol>
  <li>Rendering user profiles (requires all user properties)</li>
  <li>Viewing a list of friends (requires user names and avatars only)</li>
  <li>Showing the current user’s avatar in the navigation (requires avatar only)</li>
</ol>

<p>Your server will initially return the same user payload from all of these features’ routes, but this won’t scale particularly well. A model with a large number of properties<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> will be necessary to render a full profile, but problematic when rendering a long list of users’ names and avatars. It’s unnecessary at best and a performance bottleneck at worst<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> to serialize a full user when most of its properties are simply going to be ignored.</p>

<p>Perhaps your API developer changes your server to return only a subset of user properties from the <code class="language-plaintext highlighter-rouge">/friends</code> route. This is followed by a change to the API documentation and a hope that your frontend engineer notices, at which point they’ll add a new type to the client-side codebase. Perhaps this new type looks something like:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">SimpleUser</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
  <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">lastName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">avatar</span><span class="p">:</span> <span class="nx">Avatar</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, your frontend will need to:</p>

<ol>
  <li>Keep track of which routes vend <code class="language-plaintext highlighter-rouge">User</code> instances vs. <code class="language-plaintext highlighter-rouge">SimpleUser</code> instances, when processing HTTP responses</li>
  <li>Have its caching logic updated to support both of these different types</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">User</code> vs. <code class="language-plaintext highlighter-rouge">SimpleUser</code> is admittedly a coarse and superficial distinction. If we add a <em>third</em> flavor to the mix, what would we reasonably name it?</p>

<p>Instead of <code class="language-plaintext highlighter-rouge">SimpleUser</code>, we could instead call this new type <code class="language-plaintext highlighter-rouge">FriendListUser</code>, named after the feature that it powers. Having separate user models for each use case is a more scalable approach – we could end up with quite a few different versions, whose names all accurately convey intention better than “simple” does:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">FriendListUser</code></li>
  <li><code class="language-plaintext highlighter-rouge">EditAccountUser</code></li>
  <li><code class="language-plaintext highlighter-rouge">ProfileUser</code></li>
  <li><code class="language-plaintext highlighter-rouge">LoggedOutProfileUser</code></li>
</ul>

<p>The risk here is that we’re likely to incur a lot of overhead in terms of keeping track of which routes vend which models, and how to make sense of all of these different variants when modeling our frontend persistence layer.</p>

<p>Reducing this overhead by more tightly coupling our client-side type definitions to our API specification would be a big step in the right direction. <a href="https://graphql.org">GraphQL</a> is one tool for facilitating exactly this.</p>

<h2 id="graphql">GraphQL</h2>

<p>There’s a lot to like about GraphQL – if you’re looking for a comprehensive overview, I’d recommend checking out <a href="https://graphql.org/learn/">the official documentation</a>.</p>

<p>One advantage over traditional RESTful interfaces is that GraphQL servers vend strongly-typed schemas. These schemas can be programmatically introspected, making your APIs self-documenting by default<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>. But this is an essay about client-side models, not avoiding stale documentation.</p>

<p>With higher model specificity comes higher clarity and efficiency, the primary downside being the additional work involved to maintain a larger number of models. Let’s dig deeper into how code generation can mitigate this downside.</p>

<p>By introspecting both:</p>

<ol>
  <li>Our backend API’s strongly-typed schema</li>
  <li>Our frontend app’s data needs</li>
</ol>

<p>We can easily generate bespoke client-side models for each individual use case.</p>

<p>First, we must understand how GraphQL queries work. In a traditional RESTful API server, the same routes always vend the same models. Let’s say that our GraphQL server exposes the following two queries:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">me: User</code></li>
  <li><code class="language-plaintext highlighter-rouge">friends: [User]</code></li>
</ol>

<p>While both queries expose the same server-side <code class="language-plaintext highlighter-rouge">User</code> model, the client specifies the subset of properties that it’s interested in, and only these properties are returned. Our frontend might make the following query:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>me {
  firstName
  lastName
  location {
    city
    state
  }
  avatar {
    large
  }
}
</code></pre></div></div>

<p>The server will only return the properties specified above, even though the server-side user model contains far more properties than were actually requested.</p>

<p>Similarly, this query will return a different subset:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>friends {
  firstName
  lastName
  avatar {
    thumbnail
  }
}
</code></pre></div></div>

<p>Code generation tools can introspect these client-side queries, plus the API schema definition, in order to:</p>

<ol>
  <li>Ensure that only valid properties are being queried for (even directly within your IDE, validating your API calls at compile-time)</li>
  <li>Generate client-side models specific to each distinct query</li>
</ol>

<p>In this case, the generated models would look as follows:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generated to support our `me` query</span>

<span class="kr">interface</span> <span class="nx">User_me</span> <span class="p">{</span>
  <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">lastName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">location</span><span class="p">:</span> <span class="nx">User_me_location</span><span class="p">;</span>
  <span class="nl">avatar</span><span class="p">:</span> <span class="nx">User_me_avatar</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">User_me_location</span> <span class="p">{</span>
  <span class="nl">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">User_me_avatar</span> <span class="p">{</span>
  <span class="nl">large</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Generated to support our `friends` query</span>

<span class="kr">interface</span> <span class="nx">User_friends</span> <span class="p">{</span>
  <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">lastName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">avatar</span><span class="p">:</span> <span class="nx">User_friends_avatar</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">User_friends_avatar</span> <span class="p">{</span>
  <span class="nl">thumbnail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Each of our app’s components can now be supplied with a model perfectly suited to their needs, without the overhead of maintaining all of these type variations ourselves.</p>

<h2 id="trees-of-components-trees-of-queries">Trees of components, trees of queries</h2>

<p>User interface libraries like <a href="https://reactjs.org">React</a> and <a href="https://developer.apple.com/documentation/uikit">UIKit</a> allow encapsulated components to be composed together into a complex hierarchy. Each component has its own state requirements that the other components ideally needn’t concern themselves with.</p>

<p>This is at odds with traditional RESTful API development, where a single route will often return a large swath of data used to populate whole branch of the component tree, rather than just an individual node.</p>

<p>GraphQL query fragments better facilitate the colocation of components <em>and</em> their data requirements:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const FriendListItem = gql`
  fragment FriendListItem on User {
    firstName
    lastName
    avatar {
      thumbnail
    }
  }
`;

const FriendListRow = props =&gt; {
  return (
    &lt;Container&gt;
      &lt;Avatar source={props.user.thumbnail} /&gt;

      &lt;NameLabel firstName={props.user.firstName}
        lastName={props.user.lastName} /&gt;
    &lt;/Container&gt;
  );
};
</code></pre></div></div>

<p>This results in a “query hierarchy” that much better aligns with our component hierarchy.</p>

<p>Just as a UI rendering layer will walk the component tree in order to lay out our full interface hierarchy, a GraphQL networking layer will aggregate queries and fragments into a single, consolidated payload to be requested from our server.</p>

<h2 id="heterogenous-caching-made-simple">Heterogenous caching made simple</h2>

<p>GraphQL is a high-level query language; while you can use it to query a GraphQL <em>server</em>, client-side libraries such as <a href="https://www.apollographql.com">Apollo</a> and <a href="https://facebook.github.io/relay/">Relay</a> can act as abstraction layers on top of both the network as well as an optional local cache<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>.</p>

<p>(Additionally, Apollo and Relay both also handle the code generation and query fragment unification outlined in the sections above 💫)</p>

<p>Traditional client-server applications often end up with logic that looks as follows:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Check if we have a certain user in our cache</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">user</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="nf">query</span><span class="p">(</span><span class="s">"SELECT FROM users WHERE id = </span><span class="se">\(</span><span class="n">id</span><span class="se">)</span><span class="s">"</span><span class="p">)</span><span class="o">.</span><span class="n">first</span> <span class="p">{</span>
  <span class="nf">callback</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
  <span class="c1">// Fetch from the network instead</span>
  <span class="kt">API</span><span class="o">.</span><span class="nf">request</span><span class="p">(</span><span class="s">"/users?id=</span><span class="se">\(</span><span class="n">id</span><span class="se">)</span><span class="s">"</span><span class="p">)</span><span class="o">.</span><span class="n">onSuccess</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
    <span class="nf">callback</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">user</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In this case, we’re querying for the same user via two different mechanisms: SQL against our local database and a URL-encoded query string against our remote server.</p>

<p>Apollo and similar libraries allow us to more declaratively specify the data that we need in one unified way. This level of abstraction lets us delegate the heavy-lifting – checking whether our request can be fulfilled purely from cache, and augmenting with additional remote data if not.</p>

<p>To continue our example: if you first made a <code class="language-plaintext highlighter-rouge">friends</code> query, your cached users would only contain <code class="language-plaintext highlighter-rouge">firstName</code>, <code class="language-plaintext highlighter-rouge">lastName</code>, and <code class="language-plaintext highlighter-rouge">avatar.thumbnail</code> properties. A subsequent <code class="language-plaintext highlighter-rouge">me</code> query for one of those same users would hit the server in order to “fill in” the additional properties – <code class="language-plaintext highlighter-rouge">location</code> and <code class="language-plaintext highlighter-rouge">avatar.large</code>. From this point forward, subsequent <code class="language-plaintext highlighter-rouge">friends</code> or <code class="language-plaintext highlighter-rouge">me</code> queries could avoid the network roundtrip altogether<sup id="fnref:5:1" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>.</p>

<p>As long as two user models have the same unique identifier, it doesn’t matter which subset of their properties were fetched in which order. Apollo will take care of normalizing them for us.</p>

<p>Sound magical? It certainly can be, for better or for worse. Like all high levels of abstraction, it’s amazing when it’s working and infuriating <a href="https://github.com/apollographql/react-apollo/issues/2678">when it isn’t</a>.</p>

<p>But such is the promise of GraphQL; <mark>the sky is the limit for tooling when a typed schema definition is the foundation being built upon</mark>. Tooling of this nature can make a premise that would’ve otherwise seemed prohibitively unwieldy – having a distinct client-side model type for every slight use case variation – not only achievable, but ideal.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>More likely if it’s generated using something like <a href="https://swagger.io">Swagger</a>, less likely if your API engineer is manually doing their best to keep it up to date. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Just imagine how many properties a Facebook user is comprised of, for example. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Not to mention, disrespectful of your users’ time <em>and</em> cellular data plans. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Tools like <a href="https://github.com/graphql/graphiql">GraphiQL</a> allow you to see exactly which server-side models are vended by each of your GraphQL queries. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Depending on your caching policy, of course. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/graphql/dinosaurs.jpg" /><media:content medium="image" url="http://irace.me/images/graphql/dinosaurs.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How I learned to read (with regularity, as an adult)</title><link href="http://irace.me/reading" rel="alternate" type="text/html" title="How I learned to read (with regularity, as an adult)" /><published>2018-12-30T00:00:00+00:00</published><updated>2018-12-30T00:00:00+00:00</updated><id>http://irace.me/how-i-learned-to-read</id><content type="html" xml:base="http://irace.me/reading"><![CDATA[<figure>
  <img src="/images/reading/books.jpg" />
</figure>

<p>Yours truly, <a href="https://bryan.tumblr.com/post/41294703284/code#notes">in January 2013</a>:</p>

<blockquote>
  <p>Last year I only read two books. I’m disappointed by that, and my goal this year is to read somewhere closer to ten, alternating between fiction and non-fiction. To hold myself accountable, I will try to write a little about each, starting with this one.</p>
</blockquote>

<p>I only ended up reading <a href="https://bryan.tumblr.com/tagged/2013books">five books</a> in 2013, but read <a href="https://www.goodreads.com/user/year_in_books/2017/52033672">seven in 2017</a> and <a href="https://www.goodreads.com/user/year_in_books/2018/52033672">seventeen in 2018</a><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. At risk of jinxing it, I think it’s safe to say that – many years later – I’ve finally adopted a real reading habit for the first time as an adult.</p>

<p>Habits are obviously quite personal, and there’s no shortage of advice out there on how to make one out of reading in particular<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. Here are the few keys to my own personal success on this front that I hope may prove helpful to you as well.</p>

<h2 id="committing-to-a-platform">Committing to a platform</h2>

<p>I spent far too long being really indecisive as to whether I should invest in Apple or Amazon:</p>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Sadly, I’m positive that I’d read a lot more if I wasn’t paralyzed by indecision over iBooks vs. Kindle lock-in.</p>&mdash; Bryan Irace (@irace) <a href="https://twitter.com/irace/status/620800395825291265?ref_src=twsrc%5Etfw">July 14, 2015</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<center class="centered-tweet"><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">iBooks or Kindle? Lock-in indecision usually results in my just not buying anything.</p>&mdash; Bryan Irace (@irace) <a href="https://twitter.com/irace/status/694298139273121792?ref_src=twsrc%5Etfw">February 1, 2016</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></center>

<p>I eventually committed to Amazon due to Goodreads, local library rentals, and e-ink Kindle devices (more on all of these later). <mark>Most important was to simply make a decision, however</mark>; I think I’d be doing just fine had I chose Apple instead.</p>

<p>I still prefer Apple for programming books because I find the Apple Books software for macOS to be far superior to Kindle’s desktop web experience, but these are a small percentage of the books that I read overall. I’ve been very happy with Kindle as my default for everything else.</p>

<h2 id="keeping-a-streak">Keeping a streak</h2>

<p>If you’re the type of person who <a href="https://irace.me/habits">finds streaks to be motivational</a>, keeping track of a streak is an easy way to make sure that you’re spending at least a few minutes reading each day. Just five or ten minutes each day is a great place to start; <mark>those minutes really do add up</mark>, but perhaps more importantly, a streak helps ingrain the habit of dipping into your book during your commute or while waiting in line at the grocery store. Or really, anytime when you might otherwise default to scrolling through Twitter or Instagram.</p>

<figure>
  <img src="/images/reading/streaks.png" style="width: 300px;" alt="My 2018 reading streak" />
  <figcaption>I highly recommend <a href="https://streaksapp.com">Streaks</a> for iOS. It even has built-in timers for letting you know when you’ve finished reading for the desired increment.</figcaption>
</figure>

<h2 id="allowing-myself-to-take-breaks-from-books">Allowing myself to take breaks from books</h2>

<p>There are some days where I simply don’t want to read a book, but that doesn’t mean I can’t read something else of value. The goal here was to instill a reading habit, not <em>necessarily</em> a book habit.</p>

<p>Maybe I just finished my last book and haven’t decided which my next one will be, or maybe there’s a lot going on in the news that I’d like to catch up on. I use <a href="https://www.instapaper.com">Instapaper</a> to save articles of all different kinds to read at a later date, and on days where I choose to spend at least ten minutes reading these instead of a book, I’m happy to count that against the streak as well.</p>

<h2 id="using-an-e-ink-kindle">Using an e-ink Kindle</h2>

<p>I resisted getting a dedicated Kindle device until December 2016, primarily because I really don’t mind reading on iOS. I’ve read entire books on iPhones without issue.</p>

<p>I eventually moved to a Kindle as my primary reading device <em>not</em> because I prefer e-ink screens to LCD or OLED, but because I am very bad at avoiding distractions. My Kindle doesn’t have notifications, a web browser, or a Twitter client. <mark>I can do nothing but read on it, so read without interruption I do</mark>.</p>

<p>Modern Kindles also have the benefit of being quite light and small. I expected to only bring mine along when carrying a bag, but it fits pretty easily in many of my jacket pockets. As such, I’ve carried it far more often than I would’ve originally guessed.</p>

<h2 id="reading-wherever-whenever">Reading wherever, whenever</h2>

<p>Despite having really grown to like my Kindle, it’s only with me a fraction of the time compared to my iPhone. Like with <a href="https://www.amazon.com/Best-Camera-One-Thats-You/dp/0321684788">cameras</a>, <mark>the best book is the one that’s with you</mark>. Kindle sync means that your book is always with you as long as your phone is, even if your primary reading device is not. And as mentioned, I’m definitely not above reading books on my phone.</p>

<p>There are plenty of times where I most certainly do <em>not</em> want to carry anything more than my phone – specifically in the summer – but this doesn’t mean that there can’t still be any number of planned or unplanned reading opportunities along the way.</p>

<h2 id="drowning-out-the-noise">Drowning out the noise</h2>

<p>In the spirit of reading wherever and whenever, it’s often helpful to have something to help you drown out the background noise of your neighborhood coffee shop, or the New York City subway. I’ve been using <a href="https://brain.fm">Brain.fm</a> almost exclusively for the past year but have also used <a href="http://noiz.io">Noizio</a> a lot in the past. They’re both really good at what they do.</p>

<h2 id="putting-a-book-cover-on-my-lock-screen">Putting a book cover on my lock screen</h2>

<p>Whenever I start a new book, I put the book’s cover on my iPhone lock screen<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>. It’s not exactly subtle, but I benefit from the constant reminder that I could be reading my book whenever I’m tempted to use my phone for something less beneficial.</p>

<figure>
  <img src="/images/reading/lockscreen.png" style="width: 300px;" alt="“Breakfast of Champions” adorning my lockscreen" />
  <figcaption>“Bonnie made a joke now as she served him his martini. She made the same joke every time she served anybody a martini. “Breakfast of Champions,” she said.”</figcaption>
</figure>

<p>If you’d like to make your own lock screen wallpaper, here’s the <a href="https://irace.me/files/book-wallpaper.sketch">Sketch template</a> that I use.</p>

<h2 id="using-goodreads">Using Goodreads</h2>

<p><a href="goodreads.com">Goodreads</a> is a social network for keeping track of what you and your friends are reading and want to read in the future. It’s owned by Amazon, and as such is able to automatically track any books that you’ve read using Kindle apps or devices.</p>

<figure>
  <img src="/images/reading/goodreads.png" style="width: 300px;" alt="The Goodreads app, showing a feed of updates from friends" />
  <figcaption>Having Goodreads friends like <a href="https://twitter.com/katiesmillie">Katie</a> is one of the best ways to keep inspired and motivated.</figcaption>
</figure>

<p>While the website and iOS app could both use a bit of user interface love, I’ve found it to be a huge help in keeping a reading habit by providing:</p>

<ol>
  <li>A steady influx of new book suggestions, specifically from those who share your tastes</li>
  <li>Encouragement in the form of <a href="https://www.goodreads.com/challenges/show/7501-2018-reading-challenge">annual challenges</a></li>
  <li>Guilt, if you’ve lapsed a bit yet see all of the books that your friends keep finishing</li>
</ol>

<h2 id="renting-from-the-library">Renting from the library</h2>

<p>Did you know that not only can you borrow physical books for free from your local library, but ebooks as well? More surprising, perhaps: <a href="https://meet.libbyapp.com">Libby</a>, the iOS app used to do so, is actually quite nice.</p>

<figure>
  <img src="/images/reading/libby.png" style="width: 300px;" alt="The Libby app, used to rent e-books from local libraries" />
  <figcaption>Libby even supports multiple accounts, if you’re lucky enough to have more than one in your area.</figcaption>
</figure>

<p>The selection isn’t perfect, but I’ve been able to find a lot of books on my reading lists at either the New York or Brooklyn Public Libraries<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>. You may need to wait a few weeks for popular books to become available, but you can put them on hold such that they’re automatically rented once they are. Once rented, you can keep the book for up to 21 days (which I’ve found can serve as a helpful forcing function to make sure you’re moving through it with haste).</p>

<hr />

<p>These are just a few tactics, but they’ve worked well for me over the past year and I expect will continue in years to come. I still think there’s room for improvement<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>, but the first step to improving a habit is to have said habit in the first place. As of 2018, I can finally say that I do.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>I didn’t really keep track from 2014 through 2016. <a href="http://dayoneapp.com">Day One</a> and Goodreads indicate that I read at least three in 2014, at least three in 2015, and at least two in 2016. Nothing to brag about. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>A couple of favorites: <a href="https://medium.com/rick-webb/my-reading-habits-1e4039966f18">this one</a> from <a href="https://twitter.com/rickwebb">Rick Webb</a> (my former Tumblr colleague and a far more prolific reader than I am ever likely to be), and <a href="https://paulstamatiou.com/reading-more-kindle-oasis/">this one</a> from Paul Stamatiou. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>I wish I could take credit for this, but I got the idea from Twitter at some point and can’t find the source for the life of me. Apologies! <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Your mileage may vary depending on your local library, of course. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Specifically, <a href="https://robertheaton.com/2018/06/25/how-to-read/">retaining and utilizing information gleaned from non-fiction books</a>. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bryan Irace</name><email>bryan@irace.me</email></author><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://irace.me/images/reading/books.jpg" /><media:content medium="image" url="http://irace.me/images/reading/books.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>