<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ray Epps on Medium]]></title>
        <description><![CDATA[Stories by Ray Epps on Medium]]></description>
        <link>https://medium.com/@rayepps?source=rss-b656dc19725c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*ECMOmL88YxVoe4QPPeWsyg.jpeg</url>
            <title>Stories by Ray Epps on Medium</title>
            <link>https://medium.com/@rayepps?source=rss-b656dc19725c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 03:50:50 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@rayepps/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Startup Infrastructure: No Matter the Choice, You’re Still Wrong.]]></title>
            <link>https://medium.com/exobase/startup-infrastructure-no-matter-the-choice-youre-still-wrong-74a4fa506f7d?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/74a4fa506f7d</guid>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[startup]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Tue, 07 Nov 2023 15:13:56 GMT</pubDate>
            <atom:updated>2023-11-07T15:13:56.392Z</atom:updated>
            <content:encoded><![CDATA[<p>Unpredictable changes in a startup’s first few years make the best infrastructure choices right today and wrong tomorrow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FTHDGAPprGwOInqVCX4-SQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@possessedphotography">Posessed Photography</a> on <a href="https://unsplash.com/">Unsplash</a></figcaption></figure><p>I recently had someone respond to an article I wrote and say, among other things, that if your team is in a situation where you realize that the cloud provider and service you choose isn’t the best fit and you need to switch… it’s your own fault.</p><blockquote>…the “oh I’m not getting the performance I need from this provider” is a failure of your engineers to vet the services they are using for your use cases.</blockquote><p>To be fair, I like to hold engineers to a high bar, so I don’t completely disagree. However, it’s not reasonable, especially in the context of an early-stage startup, to expect engineers to look into the future and predict through the next two years what will stay the same, what will change, and what those changes will be.</p><p>I wrote an article about senior engineers and what it means to be one. The point I make is that the most senior engineers understand one thing: it’s not possible to be absolutely right. Even if you look at all the factors, collect all the data, and understand every facet of the problem, things change. You can’t predict change. It’s all the more true with infrastructure.</p><p>Let’s assume for a moment that I’m wrong. Here are the factors, based on my personal experience of what has led to infrastructure changes in my career, that you would have to predict to be right about your infrastructure choices for more than one to two years in an early-stage startup environment:</p><ol><li>The Market</li><li>The Product</li><li>The Team</li><li>The Users</li><li>The Budget</li></ol><h3>The Market</h3><p>Sometimes, as a startup, you have to react to the market. It’s possible that interest rates go up, conflict breaks out in the Middle East, or a regime collapses in South America and — somehow — due to that, you have to modify your product to support a new feature. It sounds crazy, but startups are crazy.</p><p>Something as simple as an industry leader acquiring an up-and-comer and then immediately sunsetting their service can drive your users to start screaming, “take my money” if you’ll only support the use case the sunsetting product solved for.</p><p>More likely is, a competitor modifies their product to do what you’re doing with twice the performance and half the wait time for end-users. Your customers begin leaving your product for the competitors because the value of that time saved is too high to ignore.</p><p>All of this can lead to infrastructure changes. You might need to use a different cloud service, possibly on another cloud provider, that has lower concurrency restrictions, higher memory limits, lower cost, or better performance for your specific new use case.</p><h3>The Product</h3><p>In the search for that holy grail that we call product-market-fit, it’s not unusual for your product to turn out completely different from what it started as initially. If it did change, that’s a good thing. If you have the humility to listen to the market and change your product to fit the needs that exist, you’re ahead of the game.</p><p>You might start a dating site and end up with a video blogging platform, start an MMORPG game and end up with an online instant messaging platform, or start a social-good network and end up with a daily deals app. All this can happen in a year or two.</p><p>In any one of those changes, your infrastructure will have to change as well. As a founder, if you believe your initial problem/solution idea will last forever and commit fully — from a technical perspective — to a specific infrastructure provider and service, then you might be just a little bit screwed when you need to make large changes. In this context, screwed means you have to spend exceeding amounts of time and money. Some companies, like YouTube, Slack, and Groupon made it work (the examples above). Do you have the time, budget, and energy to work hard enough to push their luck?</p><h3>The Team</h3><p>Sometimes, at an early-stage startup, you have one person, out of the massive three-person company, who is the sole infrastructure expert. Many teams refer to this as the bus factor, and good teams will seek to spread that expertise around when the bus factor is less than three. But at a startup, there’s little you can do. Now, what happens when the bus comes and takes your one infrastructure expert off to a seven-figure job at Google?</p><p>Hiring isn’t easy or cheap. You might find someone with the same infrastructure expertise as the member who was ‘hit’ by the bus. If you can’t, you might need to make changes when your new expert, who has different values and tastes than the previous one, insists things will have to change to continue.</p><p>On the other hand, what if a bus pulls up and drops off a new team member? The new team member has ten years of experience more than the team member who initially designed your infrastructure. The latest expert says you can get 2x the performance at half the cost if you make significant changes.</p><p>Would you spend a week of dev time to cut your monthly cloud cost in half? Would you spend a day?</p><h3>The Users</h3><p>Users: those beautiful, sweet, wonderful, annoying, painful, awful little things that keep us going; they can surprise you, and their behaviors can change. At an early-stage startup where every $1 of revenue is worth a gallon of blood, sweat, and tears when customers begin complaining about platform stability, you will do whatever you need to, no matter how drastic, to satisfy them.</p><p>I’ve done a lot of platform performance and site reliability work, and when it comes to users, what we’re concerned about is their behavior. As your product grows, their behavior can change. Many times, as you get more users, you start to see distinct behaviors that weren’t clear when you only had one — or none.</p><p>Teamflow is an example. It’s a Slack meets Zoom company that provides a virtual office product. Users can design a virtual office, sit in it, move through virtual space to their coworker’s office, knock on the door, and get an all-around real-world experience while working remotely.</p><p>When the product was just getting started, we chose to use GCP App Engine. At the time, it was the best choice, the most cost-effective and performant service available to us, given our specific expertise. It also allowed us to build rapidly because of the simplicity. As we grew and got more users, we noticed some incredibly spikey user behavior. Every morning, at 7:59 AM, there would be zero users on the platform. Two minutes later, at 8:01 AM, nearly 80–90% of our daily active users were online. App Engine couldn’t keep up with that kind of spikey scaling. For the first hour of every weekday, our platform was nearly unusable. We moved to Cloud Run. It wasn’t easy, but we didn’t have a choice.</p><h3>The budget</h3><p>When the runway starts to run out, people start to ask very deep questions about the infrastructure. It’s typically the largest cost after salaries, so it gets the side eye first.</p><p>Even if the runway isn’t running out, nobody likes to leave money on the table. I talked to a founder yesterday who has $1M in free credits available across the three major cloud providers (AWS, GCP, and Azure), but his infrastructure is stuck on AWS where he can’t make use of the other credits. As a seed-stage startup, he can’t afford to take a week off to move his infrastructure.</p><p>Sometimes, in the beginning, we make sub-par choices because of budget constraints. When the budget increases because your startup is succeeding and growing, you’ll want to take that corner-cutting infrastructure choice you made and sharpen it up. Depending on how it was built, you might be screwed.</p><p>Again, screwed means it will cost you an exorbanate amount of time and money to make changes.</p><h3>Conclusion</h3><p>If you still think you can make infrastructure choices that are right in the present and will be right in the future… I can’t help you, good luck. If you see what I see, you’re realizing there’s no hope; you can’t predict varying factors moving unpredictably through time. It’s time to consider your ability and capacity to make infrastructure changes.</p><p>I write my code in an infrastructure-agnostic way. I call it cloud-nimble. I don’t depend directly on cloud APIs (like AWS S3), and within my build pipeline, I reduce my services to their smallest individual unit: functions. When I need to make changes, I can move a function, or a collection of functions, to a serverful service, a serverless service, or do a lot of other dynamic things with them.</p><p>I’m packaging all this up, building out a suite of developer tools to make it easy for others to use, and offering it as a platform: <a href="https://exobase.dev/">Exobase</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_1EY4cIPb-0Zs1AEJnGh6w.png" /></figure><h4>Share Your Thoughts: Agree or Disagree?</h4><p>If you’ve had to move infrastructure in the past, I’d love to hear about your experience. What factors made you move?</p><p>If you’ve never had to move infrastructure in the past, how did you beat the odds?</p><p>If you’re mad this article ended in marketing, then I’ll assume you disagree with the basic premise that there is a problem with infrastructure being hard to move… is your infrastructure easy to move?</p><pre>if (reader.read(this)) {<br>  request(reader, &#39;comment&#39;)<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=74a4fa506f7d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/startup-infrastructure-no-matter-the-choice-youre-still-wrong-74a4fa506f7d">Startup Infrastructure: No Matter the Choice, You’re Still Wrong.</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Your Cloud Infrastructure Scales, but is it Nimble?]]></title>
            <link>https://medium.com/exobase/your-cloud-infrastructure-scales-but-is-it-nimble-6b2fcfee0923?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b2fcfee0923</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Tue, 24 Oct 2023 14:25:47 GMT</pubDate>
            <atom:updated>2023-10-24T14:26:35.225Z</atom:updated>
            <content:encoded><![CDATA[<p>Scalability is how you handle expected changes in traffic and load. How do you handle the things you can’t expect?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ni2x_XCsNpyrL-objAMKrg.jpeg" /><figcaption>DALL-E 2</figcaption></figure><p>Assuming your infrastructure is on AWS, what would happen if…</p><ol><li>AWS claims your platform breaks its policies and shuts down your account.</li><li>AWS’s internal security system identifies your customers’ requests to your API/platform as a D/DOS attack and begins rate-limiting those requests.</li><li>You run out of free credits, you have limited runway, and you haven’t yet generated enough revenue to cover your cloud costs.</li><li>As your customer base grows, you discover your chosen cloud service can’t handle your uniquely spikey workload at scale.</li><li>One of the AWS services your platform depends on has an outage. Your platform is completely unavailable to your customers.</li><li>A company approaches you, offering an acquisition, but only if you can move your infrastructure to GCP in the next two weeks.</li></ol><p>For these examples, the answer is: You need to move your platform to a different cloud or cloud service. If you don’t, you’re in real trouble.</p><p>If this sounds like a list of complete impossibilities, read <a href="https://medium.com/exobase/aws-and-gcp-horror-stories-bad-reasons-good-companies-died-e849e2796933">AWS and GCP Horror Stories. Bad Reasons Good Companies Died</a>. These things happen.</p><h4><strong>The difference between scalable and nimble</strong></h4><p>Cloud-nimble means having the ability to quickly and easily move your platform between clouds or cloud services. If moving your platform from AWS to GCP requires code changes, you are not cloud-nimble.</p><h4><strong>How to be cloud-nimble</strong></h4><p>Cloud-nimble is being achieved in today’s market by using open-source constructs like Kubernetes. It’s a good start, but it leaves us with a lot of limitations.</p><p><a href="https://www.plural.sh/">Plural</a>, <a href="https://www.architect.io/">Architect</a>, and <a href="https://www.porter.run/">Porter</a> allow you to be cloud-nimble, but only if you’re willing to give up using native cloud services that don’t run Kubernetes. This means that if your API runs best on AWS Lambda — in terms of cost and performance — you can’t use these platforms and you can’t be cloud-nimble.</p><h4><strong>How to be cloud-native-nimble</strong></h4><p>Today, it’s not possible to be cloud-nimble and cloud-native. Nobody has built the tools you need, but I’m working on it at <a href="https://exobase.dev/">Exobase</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_1EY4cIPb-0Zs1AEJnGh6w.png" /></figure><p>If you&#39;d like to get notified when Exobase is publically available or are interested in being a beta user, email me at <a href="/service/mailto:ray@exobase.dev">ray@exobase.dev</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b2fcfee0923" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/your-cloud-infrastructure-scales-but-is-it-nimble-6b2fcfee0923">Your Cloud Infrastructure Scales, but is it Nimble?</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS and GCP Horror Stories. Bad Reasons Good Companies Died.]]></title>
            <link>https://medium.com/exobase/aws-and-gcp-horror-stories-bad-reasons-good-companies-died-e849e2796933?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/e849e2796933</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[cloud]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Thu, 19 Oct 2023 14:46:53 GMT</pubDate>
            <atom:updated>2023-11-02T22:15:05.061Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*cRYxhlhVH9SkkkoPMpIuYA.jpeg" /></figure><p>Nobody plans to leave AWS, but as they say, “sh*** happens.”</p><p>As engineers, when we write software, we’re taught to keep it nimble by never depending directly on external systems. When it comes to the cloud… *crickets*. Companies have died because they needed to move off AWS but couldn’t do it in a reasonable and cost-effective timeline.</p><p>It’s not rocket science why we do this. It’s simple: there are no alternatives. But maybe there is. I’ll explain why you should take cloud-nimble architecture seriously and then show you what I do to keep my projects cloud-nimble.</p><h3><strong>Cloud Service Optimization</strong></h3><p>The first reason you should value the ability to switch clouds and cloud services is the ability to select and use the cloud service that’s price and performance-optimized for your use case.</p><p>When I first got into serverless, I thought GCP’s Cloud Function service was better than AWS Lambda. I don’t recall why I thought that, but I built a consumer-facing API on GCP Cloud Run.</p><p>Can you guess what happened?</p><p>It was a horrible mistake. My API had an insane latency problem. Cold start requests added — at a minimum — 2 seconds per request. The AWS team has worked hard to build a service that can do things that GCP’s Cloud Functions simply can’t, specifically around cold starts and latency.</p><p>I had to move my infrastructure to a different service.</p><p>This happened again at Teamflow, a virtual office startup (think Slack meets Zoom). We built our API on GCP’s App Engine. The use case of a virtual office is naturally hyper-spikey workloads. At 7:59 AM, zero people are using your product. Three minutes later, at 8:02 AM PT, over 90% of your customers are online. Because App Engine couldn’t scale fast enough, those customers were banging the refresh button in frustration as the app failed to load.</p><p>I spent three weeks, with two other engineers, modifying our code and infrastructure to move it to GCP’s Cloud Run service — which was much easier to configure for our hyper-spikey scaling requirements.</p><h3><strong>Avoiding The Killswitch</strong></h3><p>AWS, GCP, Azure, and every other cloud provider reserves the right to ‘unalive’ your account and destroy your infrastructure anytime they want. If you’re thinking, “I’m not pot, porn, or gambling, so that would never happen to me,” you are wrong.</p><p>I recently spoke with a founder who told me a nightmare story. His team was using GCP’s Cloud Run, a container service, to host their API. They had a unique use case that required them to call back to their own API to kick off more work. It turns GCP monitors for this type of behavior and flags it as crypto mining. One sunny day, their infrastructure was gone, and their account was locked. They spent the next week working ~18 hours a day to move to AWS.</p><p>Maybe you are building a pot, porn, gambling, crypto mining, political, or firearm-related application. I don’t judge. I’m just telling you to think ahead and build like your cloud provider might kick you off their platform tomorrow.</p><h3><strong>Unlocking Free Credits</strong></h3><p>If you’re a pre-seed or seed-stage startup, free cloud credits can be the hand of mercy holding fire back from consuming your runway. Cloud credits are easy enough to get, but the way most people build their infrastructure — as though they’ve made a lifelong blood pact with their cloud provider — they’re limited to only the credits that cloud provided them.</p><p>Could you move from AWS to GCP when your credits are up?</p><p>If you’re a YCombinator startup, you can get $150K in free credits on AWS and about $200K on GCP. If you’re building AI, Azure will give you $300K in free credits to run your models on their cloud.</p><p>The ability to move your infrastructure from one cloud to another could save you an immediate $200K. Why would you ever build in a way that locked you into a single cloud?</p><h3><strong>Maximizing Redundancy</strong></h3><p>If Silicon Valley Bank can fail, so can Amazon. It may just be an outage, but the only thing certain about life and companies is that everything dies. Your job is to keep yours alive as long as possible.</p><p>How much money would your company lose during a 12-hour outage on AWS?</p><p>True disaster recovery is the ability to move from AWS to GCP. True redundancy is to run segments of your platform on AWS and GCP in parallel at all times.</p><h3><strong>Cloud Cost Negotiation</strong></h3><p>The ultimate move in any negotiation is to walk away. If the other side knows you can’t, it means their hands are wrapped firmly around a very specific organ of yours — whatever that is for you. Cost negotiations with cloud providers are no different.</p><p>Suppose you can’t leave AWS because you’ve built $100M worth of infrastructure on their platform. Suppose you’ve tightly coupled that infrastructure to their APIs (like S3, Cognito, and SQS). You can’t walk away. You’ll have to eat whatever number they tell you to regarding cost.</p><p>The ability to negotiate costs might not seem like a very big deal. If you’re a small company, it’s not. If you really did have $100M of infrastructure, then getting AWS to give you a 3% discount is substantial.</p><h3><strong>A Very Bad No Good Day</strong></h3><p>We don’t know what we don’t know. There are millions of permutations left unseen by the human eye. You never know what will happen or why. Here’s a crazy, impossible scenario: Google wants to acquire your company, but your infrastructure runs on AWS.</p><p>Meet Matt, this is his story. He did the founder’s hustle and the grind to bring a company to life from nothing. GCP approached and started talks to acquire. It was a dream come true until the conversation turned to the topic of their AWS infrastructure. Google, as you might expect, can’t acquire companies that run on AWS, and Matt’s team couldn’t move their platform to GCP in a time and cost-effective way. In the end, Google walked away, and Matt lost the exit.</p><p>You never know what can happen. Having the ability to quickly and easily switch between cloud providers might save your company or just save your company a few million dollars.</p><h3><strong>My Solution</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zKgyIwIle_8BACJgrCrTEg.png" /></figure><p>Here’s what I do about it. I use <a href="https://exobase.dev/">Exobase</a>. It started as a library that abstracted the cloud-specific APIs into generic APIs. Today, it’s a full-service build and deployment platform that allows you to deploy to AWS and GCP with just a few clicks.</p><p>Exobase isn’t public yet, but if you’re interested in being an early user, send me a message: <a href="/service/mailto:ray@exobase.dev">ray@exobase.dev</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e849e2796933" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/aws-and-gcp-horror-stories-bad-reasons-good-companies-died-e849e2796933">AWS and GCP Horror Stories. Bad Reasons Good Companies Died.</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Most Missed Sign of Bad Software Design: Integration Tests]]></title>
            <link>https://medium.com/@rayepps/the-most-missed-sign-of-bad-software-design-integration-tests-56093d001fee?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/56093d001fee</guid>
            <category><![CDATA[code]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Wed, 28 Sep 2022 22:23:21 GMT</pubDate>
            <atom:updated>2022-09-28T22:23:21.410Z</atom:updated>
            <content:encoded><![CDATA[<h3>Sign of Bad Software Design: Integration Tests</h3><p>If you have integration tests your software is probably lacking the fundamentals of good design.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QQXeApVqDTUoaDg5fMB3wA.jpeg" /></figure><h4>TLDR;</h4><p>Integration tests are only necessary if you’ve allowed two parts of your code that are unrelated to become tightly coupled. Quality software does not tightly couple unrelated things. Therefore, integration tests are a strong indicator of poor design.</p><h3>Types of Testing</h3><p>There are many different types or families of tests. I typically break the modern web application testing into three common groups: unit, integration, and end-to-end.</p><p>Unit tests <em>should </em>test deterministic functions. Given some input to a function, you want to assert the correct output. Unit tests are the simplest to write and fastest to run. They are ideal.</p><blockquote>As an aside, if you find that writing unit tests is hard (not simple to write), if you find yourself in a hellscape of mocking and injecting and hacking at the runtime to write unit tests you are also experiencing the consequences of poor software design.</blockquote><p>End-to-end testing is where we test the <em>thing </em>in real life, running it live — whatever it is. Sometimes your unit tests pass and you expect everything to work but when you deploy your API, site, service, or app it fails completely. The real world of bootstrapping, connecting to external services, and networking can cause errors you won’t catch in unit tests (or even local manual tests). End-to-end tests ensure the thing works in the wild.</p><p><strong>Integration tests are a way to test your so-called <em>application logic </em>within the structure of a framework.</strong></p><blockquote>If you’ve read any of my previous articles and began to drink my software purist Koolaid then the previous sentence in bold should sound off in your head like the alarms of a nuclear plant experiencing a radio-active reactor meltdown.</blockquote><p>When you write your code inside a framework the only way to test many of your code paths is to execute the entire framework. This is why integration tests exist. They test the integration of your code with the framework. You’ll find them in any language, like a crutch, allowing engineers to lazily write code <em>quickly </em>in exchange for total surrender to the framework and the quality of their application.</p><h3>What’s the Problem?</h3><p>Quality software does not tightly couple unrelated things. Integration tests only exist to test code that was written so deeply within a framework that you can’t test it without executing the framework. That is, integration tests exist to test code you tightly coupled to an unrelated framework.</p><p>Integration tests are designed to test what some call application logic. Application logic is not a real type of logic — it is a lie. <a href="https://medium.com/@rayepps/business-logic-vs-everything-else-cb3faf591a6f">There are only three types of logic</a>: business, utility, and access. Application logic is a dangerous term because it acts as a catch-all for all the logic in your entire application.</p><p>When you write your code with a mental model of application logic in mind you will write code where everything is attached to everything. Quality software is thoughtfully organized and uses enforceable boundaries to implement the organization.</p><p>Integration tests are a hack, a lazily wielded utility tool that allows engineers to test code they have written poorly, code that has no organization or thoughtful design except those few lines of advice they took from their chosen framework’s getting started page.</p><h3>I’ve Been There</h3><p>I’m not an all-knowing being of higher celestial order, I’m just an engineer who suffered through bad code long enough to open my mind and ask why. I’ve written countless integration tests. I am guilty of suggesting them, designing them, and wiring them up with great pride.</p><p>My story, going from integration test koolaid drinker to integration test hater, isn’t about the tests — it’s about the code. About three years ago I developed the <a href="https://medium.com/@rayepps/the-abstract-compose-design-pattern-814756d8e06a">Abstract &amp; Compose design pattern</a>. Since then, I haven’t <strong>needed </strong>to write an integration test.</p><p>You’re not a bad engineer if you are writing integration tests — you’re probably a good engineer — but your code could be better. Objectively, it is better to have business logic loosely coupled to the framework. No?</p><h3>Concrete Examples</h3><p>Javascript is my personal goto language so I’ll use it here but note that this applies to any language or framework.</p><blockquote>This does not apply to Java. If you’re using Java, then the problem actually is Java and the first step is to seek help. Call 1–800-nomo-java to talk with a counselor today. Don’t throw your life away for a language that can’t love you back.</blockquote><p>Let’s assume we’re building an API that publishes alerts. Clients send us requests, we then record and broadcast them. Whatever, we’re making it up.</p><p>This is the Express.js getting started example with some logic added to the POST /alert route.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*atXgh3vcKiuSBU6eHlUedg.png" /><figcaption>A simple Express API with an alert endpoint</figcaption></figure><p>The first thing we need to do is identify our business logic, that&#39;s what we need to test. A short list of cases to test:</p><ul><li>when no message is passed in the body an error should be thrown</li><li>when no app_id is passed in the body an error should be thrown</li><li>when a previous alert was posted within 5 seconds the alert should be skipped</li><li>a successful alert should be added to the database</li><li>a successful alert should be broadcast using the pubsub</li></ul><p>Notice something, these test cases have zero relation to the framework. We’re currently using express but we could use the raw Node http module, Koa, or even AWS Lambda and these test cases would not change. However, if we did switch to AWS Lambda we would have to change our code. <strong>This is the definition of tightly coupling unrelated things.</strong></p><p>With the current setup, we would need to use integration tests to run the entire express js application to test our business logic. Our code is literally written into the framework callback: app.post(path, our_business_logic)</p><p>With a little creativity, we can resolve it this. The naive approach would be to simply remove the business logic function and pass any dependencies to it so we can easily write tests without executing the framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b-vy3_5Tgn_AaBRxL7xzrw.png" /><figcaption>API with alert behavior split out of the framework</figcaption></figure><p>Our new postAlert function expects a body and returns an object or throws an error. This interface is completely agnostic of the framework. You can import postAlert in a test, call it with some input, and assert some response.</p><p>This isn’t elegant, but it&#39;s a good first step to removing our code from the framework. The line where we call postAlert and pass it req.body is still problematic. We’ll want to make sure it’s tested but in order to do so, we will still have to write an integration test to execute the entire framework.</p><p>Let’s make a higher-order function that can be used for any endpoint to abstract away the framework and call it withExpress</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9fyEuQ8dclGnpoH5dM7Dpg.png" /><figcaption>withExpress higher-order function</figcaption></figure><p>Now, our endpoints can be wired up with complete abstraction from the framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WhZxf7NJZK0SsfK2K8etYg.png" /><figcaption>Using our withExpress higher-order function</figcaption></figure><p>All these functions ( postAlert , listAlerts , and silenceAlert ) will take one AgnosticRequest argument. The withExpress function can be tested in isolation with unit tests and each function can be tested with unit tests.</p><p>Enjoy a new world of simple unit tests!</p><h4>Short Comings</h4><p>We’ve accomplished the basic goal, to decouple the business logic from the framework so we can remove the need for integration tests, but it’s a naive solution at best. Our withExpress and AgnosticRequest are a good start but could be much more robust. They should handle errors, path parameters, and status codes — just to name a few.</p><p>If you like this pattern, and you want something more robust, and you don’t want to write it, check out the <a href="https://medium.com/@rayepps/the-abstract-compose-design-pattern-814756d8e06a">Abstract &amp; Compose design pattern </a>and the <a href="https://github.com/exobase-inc/exobase-js">Exobase library</a>.</p><blockquote>I’m Ray, I created Radash, the ACP design pattern, and the Exobase library. I don’t want your money, I just want to work on codebases that don’t suck so please follow, comment, interact, and let’s make better practices together.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56093d001fee" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Javascript at the Nunnery: Pure & Undefiled]]></title>
            <link>https://medium.com/@rayepps/javascript-at-the-nunnery-pure-undefiled-353e32fd6e1b?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/353e32fd6e1b</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[code]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Wed, 28 Sep 2022 03:21:52 GMT</pubDate>
            <atom:updated>2022-09-28T03:21:52.472Z</atom:updated>
            <content:encoded><![CDATA[<p>The best Javascript is simple, clean, and untainted by filthy so-called features like classes, this, inheritance, and decorators.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lb9uOxL-in_ij9Pj9CWvHA.png" /></figure><h4>TLDR;</h4><p>Javascript is an incredibly robust programming language with a diverse set of language features. Most of them are complete garbage. In the last 10 years, they have only served to inhibit engineers&#39; ability to write quality code. I only use objects and functions and it’s so much better.</p><h3>The Problem with Javascript</h3><p>Javascript is an incredible language. The most incredible thing about javascript is how far it’s come since it was invented. Most languages follow a natural order of creation, obsolescence, and death. When weak programming languages don’t have what it takes they are surpassed by new languages and eventually die out.</p><p>Javascript is the exception to the rule.</p><p>Although Javascript was weak, being the technology that runs the web, it was not allowed to die. Instead, new features were taped, glued, and bound to the exterior with string and wire.</p><p>The Javascript we know, hate, and love today is a shanty town of features stacked on top of a core engine that was questionable from the very beginning.</p><h3>The Problem with the Community</h3><p>The real problem isn’t Javascript, it’s the way engineers are taught to think about Javascript. They’re taught that every language feature is special, useful, and has a valid time and place to be implemented so you should learn every feature and use them all whenever possible.</p><p><strong>False.</strong></p><p>Let’s take prototypal inheritance as an example. It’s weird, it’s ugly, and it has no place in a modern programming language. Just because it exists in the language doesn’t mean we should embrace it.</p><h3>The Solution</h3><p>I say no to all those filthy features. I simply refuse. Most notably, I do not use classes, the this keyword, decorators, or depend on any form of inheritance. All of my Javascript code is objects and functions.</p><h4>I don’t do classes</h4><p>A class is simply an object that has been initialized with state. No really, that&#39;s it!</p><p>The following two snippets are identical in spirit. The only difference, the class parser requires the new keyword while the const parser does not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GbNZjMhElTxd3NcE2ZUA3A.png" /></figure><p>So why would I use a class? If you’re thinking about inheritance, consider the following snippet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3jhfW9BNTCfOCt1ibwlyRg.png" /></figure><p>Anything you can do with a class I can do with an object and functions — and it’s not very difficult.</p><h4>I don’t do decorators</h4><p>I actually love the idea of decorators. Unfortunately, the Javascript committee was partying with the forbidden drink and found themselves drunk off their ash the night they decided on the format.</p><blockquote>In simple terms, a decorator is a function that wraps another function, doing optional processing before and/or after the underlying function does its processing.</blockquote><p>To accomplish this, Javascript created a format you have to learn. I prefer the Python approach. In Python, a decorator is a function that accepts a function as an argument and returns a function.</p><p>Here’s how I bring that to life in Javascript:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TIE3YMXKdPXTwFxMtBEZow.png" /><figcaption>Decorators without the `@` in Javascript</figcaption></figure><p>In this example, we’ve <em>decorated </em>the speak function with our uppercase and emphasize decorators to create the scream function. Now, in reality, we’re not decorating, we’re composing. That’s not a bug, it’s a feature. No matter, we’re accomplishing the same underlying goal of decoration.</p><h3>But Why?</h3><p>There are a handful of reasons why I limit myself to only objects and functions. Speaking in general terms, it helps keep my code simple, readable, fast, and just overall <em>better. </em>I like <em>better</em>.</p><h4>Classes tend to Accumulate State</h4><p>This is not a hard and fast rule but a tendency I’ve seen throughout my career. Personally, I <strong>strongly </strong>prefer deterministic code and — when possible — pure functions. If you&#39;re disciplined you can achieve these things with classes. However, it never <em>seems </em>to last. Even if I’m disciplined, it’s unlikely next 10 engineers share my sentiment.</p><p>Over time, classes tend to store more attributes and add more functions that depend on those attributes. When classes really get out of control the correct value of those attributes at any given time becomes crucial to the correct function of the methods. a.k.a the nightmare of state.</p><blockquote>This type of stateful complexity is something I want to <strong><em>bring to the surface</em></strong><em>.</em> Bringing state to the surface is a functional programming tenant. Stateful code and code that causes side-effects should be brought to the highest level of our software and everything below that top level should be strictly deterministic and — when possible — pure.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rOWRwMoYA7VYCKrnghpf1Q.png" /><figcaption>Immutable object instance with a module scope deterministic function</figcaption></figure><p>Using objects, I can write deterministic functions in the module scope and call them from my object instance, only passing what is necessary. This forces a manual passing of state to the functions and — hopefully — causes engineers to think twice about what they’re adding.</p><p>You could do this with classes but there are thirty-plus years of precedence for NOT doing it this way. When others dig into your code and find a class, they will increase the statefulness without a second thought.</p><h4>Classes are Harder to Test</h4><p>This point is tightly coupled to the previous one. The more state a class has, the harder it is to test it. When we remove the statefulness out of a class by making it an immutable object, then place complex functions in the module scope where they can be tested in isolation, we <strong>significantly </strong>reduce the complexity of testing.</p><p>I’m a big fan of code coverage for unit tests. Because of some painful experience, I can tell you that code coverage on stateful classes means <strong>NOTHING. </strong>On the other hand, if my classes are instead immutable object instances with deterministic functions in the module scope and I’ve covered 100% of my deterministic functions with unit tests, I can be confident the logic is actually covered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FI-XHsOsMg2rZkagGguQww.png" /><figcaption>A Motorcycle class</figcaption></figure><p>Our motorcycle class is slightly absurd — everyone knows red motorcycles are the fastest — but it illustrates a real-world scenario where the state derives the result.</p><p>When writing tests, it might not be apparent that something as seemingly unrelated as the color of the object has an impact on the speed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0qHfF1rq5I_zeNR_fLU7jw.png" /><figcaption>An immutable Motorcycle object</figcaption></figure><p>In the function &amp; object version, writing tests for the getSpeed function would scream out to the developer that color — a required argument — has some effect on the result.</p><h4>Objects Reduce Cognitive Load</h4><p>As a human, in order to understand a class method, I have to load all the state into my own head and then consider the runtime behavior given the state it depends on. As developers, this usually means I’m scanning a class method for keywords like this or self to identify the dependent state.</p><p>Deterministic functions define their dependencies through their arguments. As a tendency, this makes them easier to understand and reason about.</p><h4>Decorators Forever Bind the Decorator to the Function</h4><p>When I use the @decorator feature in Javascript I am binding the decorator function to the decorated function <strong>forever.</strong> This completely breaks the ability to unit test my function in isolation.</p><p>Let’s look at a version of the speak/scream decorator example. This one has been converted to use the@decoartor feature.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*54uDoDzwaOG6GCC-NE7vtA.png" /><figcaption>Decorators at work</figcaption></figure><p>I can’t test the speak method without executing the decorators. As someone who values quality unit tests, this is a non-starter.</p><h4>Decorators Require Knowledge of an Obscure Format</h4><p>Please look at the following snippet, our emphasize decorator, and tell me: what is a target? what is a name? and what is a descriptor?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VdNJ1H6oh3J8lWpDFzfH6A.png" /><figcaption>The emphasize decorator</figcaption></figure><p>The definition of a decorator is simple and beautiful — a function that takes a function and returns a function. Javascript defiled a truly elegant pattern with a convoluted format.</p><h4>Decorators Require This</h4><p>As seen in both of our decorators, they require the use of apply and this to correctly set up the context of the decorated method.</p><p>If you’ll just open your minds with me for a moment I’d like to share my take on this… it was a mistake 20 years ago and it’s a mistake today. Dear god please Javascript please stop building language features around it and engineers please dear god please stop writing code around it.</p><p>As an aside, if I’m interviewing you and I ask you to explain the this keyword in javascript, the correct answer is “I’m not sure, I don’t use that obsolete piece of trash, it only serves to needlessly complicate my code.”</p><blockquote>I’m Ray, I’m a founding engineer at Vessel, I created Radash, the ACP design pattern, and the Exobase library. I don’t want your money, I just want to work on codebases that don’t suck so please follow, comment, interact, and let’s make better practices together.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=353e32fd6e1b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Business Logic vs Everything Else]]></title>
            <link>https://medium.com/@rayepps/business-logic-vs-everything-else-cb3faf591a6f?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/cb3faf591a6f</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[api-development]]></category>
            <category><![CDATA[software-design]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Fri, 02 Sep 2022 23:20:04 GMT</pubDate>
            <atom:updated>2022-09-02T23:20:04.159Z</atom:updated>
            <content:encoded><![CDATA[<p>No matter the industry, language, or stack every line of your API is one of three types.</p><h3>TLDR;</h3><p>If you’re writing <em>‘best practices’ </em>applications using <em>‘modern design principles’ </em>your code is probably a mess because you’re following a framework or pattern without thinking critically about what your code is made up of.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1sSlNFkJ2VBB4bsCf7Y5QQ.jpeg" /><figcaption>A System of Identification &amp; Organization</figcaption></figure><h3>You Need a System</h3><p>Before you can accurately implement a system of organization you first need the ability to identify what the elements you have to work with are. If you don’t know what you’re working with, how can you design a system around it?</p><blockquote>Everything with a place, everything in it’s place</blockquote><p>If a thing does not have an assigned place, how will it ever be put ‘away’ in an assigned place? My great uncle told me this in regards to lost toys when I was young. Lucky for us, it applies to life generally. <strong>A thing without a dedicated place of belonging can never be put in the correct place — it does not have one.</strong></p><p>You’re reading this because you’re a nerd so with respect to software, what are the <em>things </em>in your application? Maybe some business logic? Have you identified it? Have you logically assigned it a place?</p><p>In my experience, you probably pulled a framework off the shelf and just write the code where they tell you to. Billion-dollar businesses have been built this way, but very few. Let’s do better.</p><h3>The Types of Logic</h3><p>It can be hard to distinguish one type of logic from another. We need to know what types of logic exist — in our code — and how to identify them. Otherwise, we can’t organize our application into a system correctly.</p><p>This is made worse when, having not been taught any better, we assume that there could be an unlimited number of unknown types, groups, and families of logic. This was me but it’s not true.</p><p>In any API there are only three types of logic: <strong>business, access, and utility.</strong></p><h4>Access Logic</h4><p>Every external resource (<em>i.e. anything that requires IO/async interaction — including the local filesystem) </em>should have a module that owns its interface. This means that all interaction with an external resource occurs through a single module that has the responsibility of communicating with the resource.</p><p>The iconic example is the database. You should have a module that knows how to interact with the database and exposes functions that accept input without requiring any database knowledge.</p><p>A few examples of the wrong abstraction:</p><ul><li>Your database.js module exposes a function with the signature query(sql: string) =&gt; Row[]. This will cause higher level modules to know SQL, to write SQL, and to pass SQL down to the database module. Also, because the function returns the Row type (presumably a database row) modules have to know how data is stored so they can correctly parse it into a model type. We often store data in the database in obscure formats that are more preferrable to the database we’re using or more performant for our access patterns. Only one module should be responsible for knowing about that format, mapping to it, and mapping from it.</li><li>You need to store files in AWS S3 so you make an s3.js module that exposes a function with the signature listInBucket(bucket: string) =&gt; S3Object . We can tell this is wrong because bucket and S3Object are both AWS S3 constructs. Higher level modules will have to know about buckets to pass the correct value. They also need to know what an S3Object is to parse the result into a file path or data stream they can use.</li><li>You need to make http requests to a third party API, let’s say it’s the <a href="https://www.abstractapi.com/">Abstract</a> API, so you install <a href="https://www.npmjs.com/package/superagent">superagent</a> and make the calls wherever you need to. In your controller, in your endpoint, in your validation… anywhere.</li></ul><p>Now, some correct abstractions:</p><ul><li>Your database.js module exposes a function with the signature listAllUsers() =&gt; User[] . As a new developer if I need to list all the users I can find the function and call it without needing to know anything about the database. A better design like this typically means a larger surface area on the exposed interface. Instead of exporting a single function query we’ll probably export dozens that are all specific to a model and use case. Internally, this database module will do mapping from the User model type to the type stored in the database.</li><li>You rename your s3.js module to file-storage.js and export a function with the signature listInDirectory(directory) =&gt; File[] . Everybody, even the self taught engineers and those who went to a boot camp <em>[caml down, I’m calling out myself]</em> can understand this interface. Inside the module, we’ll map directory and file constructs to buckets and S3Objects.</li><li>Abstract is a great example because they provide a dozen+ APIs. If your using their ip-geolocation API then you would make a module called geo.js and export a function with the signature lookupLocation(ip: string) =&gt; Coordinates . Without knowing anything about Abstract, anyone can see a function that takes an IP and returns map coordinates.</li></ul><p>As a rule, if a module does not own the interface to an external resource it should be deterministic. This means any action should produce the same result given the same inputs. This is inherently not the case with a database where the same call executed twice could return different results (<em>i.e. getting a user could return different results if the user just updated their profile</em>).</p><p>These other modules are utility logic.</p><h4>Utility Logic</h4><p>If you have a file or folder called helpers, utility, shared, or common you are the problem and I am not speaking to you. Utility logic is not the <em>catch all </em>case for anything that you couldn’t find a good home for otherwise. Utility logic is the logic in any API written in any language and on any framework that is all the same.</p><p>If you’ve been working on backend services for long you’ve probably written query validation a few dozen times. Even if you used a library and let it do the work, you wired it up. Regardless of what your CEO says, that is not intellectual property. Nobody gives a sh** how you validate the query parameters sent to your API.</p><p>Routing, query param validation, json body validation, CORS, or anything else that has zero affiliation with your product, business, or idea— and does not own an external interface — is utility logic. If written well, the utility logic is the stuff you don’t have to change when you pivot to crypto.</p><blockquote>You might be looking at your code and see that this type of utility logic has been wrapped up with all sorts of other logic and therefore <strong>is </strong>intellectual property… that’s bad, keep reading.</blockquote><p>Based on the idea that this code doesn’t change between APIs you might think this includes modules like the database. Keep in mind that a good abstraction in the access logic surfaces functions that take in and produce model types. If you’re building a real estate platform, your database module takes in and returns models like House Property Buyer and Seller. This will change when you pivot to crypto so it is not utility logic.</p><h4>Business Logic</h4><p>Ah, the holy grail. It gives value to the business and purpose to the application. Simply put, it is the decision-making logic. The job of utility logic and access logic is to do what it’s told — by the business logic. Because of this, I often call it orchestration logic.</p><blockquote>The utility logic handles a request, doing the typical work applicable to any API to prepare the request for handling, then passes the request to the business logic. The business logic orchestrates one or more access logic modules to get the data it needs to make a decision and use the access logic to persist or record that decision.</blockquote><p>Business logic is all that <strong>really</strong> matters in a codebase. You may have 1,000,000 lines of code but only the 1,000 lines of business logic actually matter. Everything else only exists to support the execution of the business logic.</p><p>Business logic is valuable but also costly to maintain so we want as little of it as possible. We want dense business logic with high volume value and low volume code.</p><h3>The Right Abstractions</h3><p>The ability to distinguish one line of code from another is only helpful if you’re willing to use it to make the right abstractions. Depending on your chosen framework and patterns the abstractions can look very different. Here are some guidelines to use now that you can identify what’s what.</p><ol><li>Being the only code that really matters in your application, the business logic should stand out at the highest level. When someone reads your code how many files, modules, classes, and lines of code do they need to read through before they hit the business logic? Good abstractions around business logic bring it to the surface and push the utility and access logic below.</li><li>Do not let access logic modules depend on each other. This is an anti-pattern I see often embraced as a feature and even mixed with business logic (I’m talking to you NestJS). If you need a dependency injection framework you’ve made a mistake. If the file-storage module needs the database module, stop to ask yourself why? Any case like this should be orchestrated with business logic. The business logic should know about both modules and call both of them, making decisions about how, why, and when.</li><li>Whenever possible, write utility logic in a way that abstracts the framework. Do not write a query validation function that accepts an AWS Lambda Context object as an argument. Similarly, do not write a JSON validation module that accepts the Express.js Request object as an argument. Utility logic is utility logic because of the fact it is applicable everywhere. Don’t limit yourself by writing it tightly coupled to a single framework.</li></ol><h3>The Moral</h3><p>It’s possible you have a way you prefer to organize and abstract your modules. No matter how you use a pattern of abstraction like I’ve described here, or a different pattern that’s obviously incorrect, you can’t concretely apply any organization or abstraction until you can distinguish blocks and lines of code from one another.</p><blockquote>I’m Ray, I created Radash, the ACP design pattern, and the Exobase library. I don’t want your money, I just want to work on codebases that don’t suck so please follow, comment, interact, and let’s make better practices together.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb3faf591a6f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Abstract & Compose Design Pattern]]></title>
            <link>https://medium.com/@rayepps/the-abstract-compose-design-pattern-814756d8e06a?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/814756d8e06a</guid>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[code]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Sat, 27 Aug 2022 03:20:17 GMT</pubDate>
            <atom:updated>2022-08-27T03:20:17.867Z</atom:updated>
            <content:encoded><![CDATA[<p>A modern solution to testable and maintainable APIs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a29KDasV0dNGmmRmPEtqTA.png" /><figcaption>Abstract &amp; Compose</figcaption></figure><h3>Better Practices</h3><p>I’m going to propose solutions you’ve likely never heard of and write code you’ve probably never seen. You may feel the urge to retch and reach for the “best practices” that make you feel safe. I’d like to encourage you to keep an open mind.</p><p>The term “best practices” comes with an implication of time we don’t often consider. It’s better said in full “currently known best practices”. Times change and practices evolve. The practices evolve specifically to solve for the problems of the times.</p><blockquote>Despite various publications of results where hand washing reduced mortality to below 1%, Semmelweis’s observations conflicted with the established scientific and medical opinions of the time and his ideas were rejected by the medical community.</blockquote><blockquote>- Wikipedia on Ignaz Semmelweis</blockquote><p>Ignaz Semmelweis was the first doctor to connect hand washing to reduced infection rates. He evangelized his discovery to anyone who would listen. In return, the community — including his own friends — had him committed to an asylum. He was tortured and eventually died there.</p><p>Be Ignaz. Don’t be Ignaz&#39;s friends.</p><h3>The Problem</h3><p>The industry standard for backend development has, from time to time, made me consider quitting my job, leaving my home, and taking up a quiet quaint life of poverty. I’ve considered making a roomy two-man tent under a shady bridge in San Fransisco my new forever home. I might live in dirt and eat canned food but at least I wouldn’t have to write code that doesn’t scale, isn’t maintainable, and can’t be tested, changed, or understood.</p><blockquote>It’s industry standard. These are “best practices”. Billion dollar businesses have been built like this. I’m so afraid of change…<br> <br>[they respond in unison]</blockquote><p>True, true, true, and ok… I can’t argue that the job isn’t currently getting done. I’m arguing that we can do better, a lot better.</p><p><em>Is good, </em>good enough?</p><h4>Testing</h4><p>Mocking by creating an object that <em>looks similar</em> to another and passing it as an argument is acceptable. Using black-box tools like jest.mock to hack at a runtime’s import system and hot swap a real module for a fake one is not acceptable. <strong>I should not have to mock anything at the module/import level.</strong></p><p>No matter the framework, language, or libraries, a testing suite should only require a slim test runner and, if needed, an assertion library. <strong>Testing should be easy</strong>. It should not require specialized tooling.</p><p>Martin Fowler provided a solution for this years ago: the Dependency Inversion principle. Since then, as engineers often do, a simple idea has been taken to the extreme with DI libraries, DI frameworks, injection containers, and dependency registration.</p><p>Keep that s*** away from me. I’m want to simplify my code and my tests, not the opposite. Using a DI framework to make testing easier is like trying to put out a house fire by loading it onto a truck, and dumping it into the nearest lake.</p><p>Simplicity is the way and there must be a way…</p><h4>Behavior Isolation</h4><p>I was once obsessed with the idea of <em>business logic </em>and the difference between it and other logic. What is business logic? Where does it live? How can I find it? If it’s the only thing that truly matters in every code bases why is it so hard to identify and find?</p><p>No matter the language, runtime, framework, or libraries every API is really just a lot of the same stuff. Endpoints validate requests, parse request arguments, make some decisions, access some data, and return a response.</p><p>The <em>make some decisions </em>part is where all the value is. That’s the business logic — or at least it should be. The problem is most code bases have this logic living intermingled with everything else. It’s difficult to identify or find. If it can’t be identified and found it can’t be protected and valued.</p><p><strong>I should be able to open a code base and read the code, at the highest level, and the “business logic” should jump out at me.</strong> It should be <em>like so totally obvi</em>. As engineers, we write 1,000 lines of boilerplate to build up to the 10 lines of business logic that creates real value. Those 10 lines we worked so hard for, they should stand out.</p><p>Some say the M in MVC is the business logic. Bullsh**. I’ve worked on MVC projects in .Net, Node, Python, and Swift. You and I both know the logic is all over the place. Yes, I’ve read it in a book and I’ve seen it in a thirteen-year-old’s YouTube tutorial, but I haven’t seen MVC isolate and emphasize business logic in the wild.</p><p>If a solution requires a team of MIT grads to be used correctly I’m out…</p><h4>Funnel Inversion</h4><p>In most projects, there&#39;s usually some form of what I call layering. A<em> </em>request enters an application, travels down through the layers, eventually bubbling back to the top as a response. Your layers might be well organized but if logic for a single operation or use case is spread across them, is it easily understood?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*FFe7s-y5jFwq775Tk3sUXA.png" /><figcaption>Is the important code at the top or the bottom?</figcaption></figure><p>We want our code to have a funnel-like shape. As I travel down through the application there should be less code and the code should be less specific. The code that’s important has been brought to the surface to create a broad top layer where the valuable code we care about is in plain view.</p><p>As a reader, to understand what an endpoint does in an inverted funnel codebase I have to traverse the code vertically, trying to first understand the child modules and functions before I can understand the top level. <strong>I should be able to skim one file to understand the behavior and intent of an endpoint. </strong>If I want to know <em>how</em> it works, of course, down I’ll go.</p><p>When making changes, the more code that is shared, the harder it is to make a change without unintentionally breaking other things. On the other hand, the more code that&#39;s repeated, the harder it is to make a change and be sure all needed updates have been made.</p><p>Some engineers worship the great god of DRY. I spit in his face. Contrary to the DRY ideology, there is a time to repeat yourself. <strong>I should be able to make a change to a shared piece of logic, but only for a single endpoint — easily and safely. </strong>This will require some repetition but with individual configurability.</p><h4>The Only Escape is Death</h4><p>As an engineer with a certain level of experience, you’ll come to know that there is no such thing as a correct decision. You can be right in two dimensions but in the third dimension — time — you’ll always, eventually, and inevitably be wrong. Because of this hard truth, <strong>I should be able to change my mind about the underlying runtime or framework without having to change my code.</strong></p><p>I’ve worked on a dozen projects where we dreamed about changing the framework or infrastructure. Decision makers were always excited when we told them we could save thousands of dollars, 2x our velocity, and fix our worst bugs by switching from EC2/Express to Lambda <em>(just an example)</em>.<em> </em>Understandably, all enthusiasm was lost when we told them we needed four engineers dedicated full time for three months to make the switch.</p><blockquote>This is the nail in the coffin for the current “best practices”. Solving this problem is the differentiating factor for the Abstract &amp; Compose Design Pattern. How much money is lost to startups and enterprises alike because they can’t make the changes they need to?</blockquote><p><strong>I should be able to write my code agnostic of the framework.</strong> I want to deploy to AWS Lambda, change my mind, deploy to AWS ECS, change my mind, deploy to Vercel, change my mind, deploy to GCP Cloud Run, and so on. Not because I’m manic, but because that&#39;s real life. Sadly, the industry has convinced us it’s not possible so don’t waste time talking about it.</p><p>It is possible, it has to be…</p><h3>The Solution</h3><p>To best understand the <em>why and how </em>we’ll take a snippet of common realistic API code and go through the steps to create a solution that solves for our stated problems.</p><h4>A Quick Review</h4><p>The problems we want to solve in our demo code:</p><ul><li>Tests should be easy to write. They should not require any module or file system mocking. They should not require a specific import order or a specialized testing library or framework.</li><li>The business logic should be isolated, naturally emphasized, and set apart.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4hSpcEJLmjGNtbeZ8Ryfdw.png" /></figure><ul><li>Logic should be bubbled to the top so that the boring and repeated is pushed below and the important and special is at the surface in plain view.</li><li>The implementation should not be dependent on, or have any knowledge of, the underlying runtime or framework.</li><li>Boring utility code (like query string validation, JSON body validation, cors, etc.) should be modular and usable in any runtime or framework. Just because I move from AWS ECS running Express to AWS Lambda I should not have to change my code.</li></ul><h4>Genesis</h4><p>In the beginning, there was an endpoint function with some logic for logging a user in. As an endpoint, its job is to validate the given credentials and return a token. Let’s assume it’s currently running on Express.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KZw55LHkBzdY0bexA3zT3w.png" /><figcaption>A Typical Express Login Endpoint</figcaption></figure><p>We’re skipping a few <em>issues </em>that would be really hard to <em>step-by-step </em>demo around module/logic organization. This endpoint is not layered, all the logic is already at the top. This means, there isn’t a function call like UserService.findOrThrow or AuthService.throwIfAuthMismatch where we would presumably push business logic into.</p><h4>Step 1: Abstract the Framework</h4><p>The first thing we need to do is remove the Express-specific (req, res) arguments and res.json function calls. We want a deterministic — or as close as you can get with a networked API — function that accepts the same input and returns a result — regardless of the underlying framework.</p><p>Not only will this help remove our strict framework dependency but it will make our code more testable.</p><p>We’ll do two things:</p><ol><li>Create a generic Request interface that modules specific to any framework can conform to and all endpoint functions can accept as input.</li><li>Create a function that wraps ours, taking Express-specific arguments, converting them to our new generic interface, and applying the response of our function to the Express response.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ptgx2gyYXHEsvdKSRs1DCw.png" /><figcaption>Abbreviated Request Interface (<a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/core">full version</a>)</figcaption></figure><p>Our generic request can be as simple or complicated as we need. If you think about the request object in different frameworks they’re typically very similar. They all have constructs like a body, query args, URL, IP address, host, and headers. As long as it’s not specific to a framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0ywyYYfS--w_kTjMSrO4yw.png" /><figcaption>Abbreviated Express Hook (<a href="https://github.com/exobase-inc/exobase-js/blob/master/packages/express/src/useExpress.ts">full version</a>)</figcaption></figure><p>This function <em>shape </em>is one that we’ll see often so let’s name it… “<strong><em>hook”</em></strong>. When composing functions, all the functions we compose (aside from the endpoint), will conform to this hook shape.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zJCGKmuksxJe_BvkYS1jSw.png" /><figcaption>The Hook Shape</figcaption></figure><p>In our useExpress hook, designed for the Express framework, the FrameworkArgs is (req: Request, res: Response) and the FrameworkResponse is actually void because the response is applied as a side-effect to the response object.</p><p>Using our generic Request interface and useExpress hook I’ll refactor the demo function to accept the Request type as an argument and return a result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ItC7eHfTfLtyYNQB9LvNRA.png" /><figcaption>Partially Abstracted &amp; Composed</figcaption></figure><p>Excellent, we now have a login function with all our valuable code and it knows nothing of the framework.</p><p>We’re using the compose function from <a href="https://github.com/rayepps/radash">radash</a> to do the composition. The compose function is super simple. If you don’t like Radash or don’t want the additional dependency you can implement it yourself in two lines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HykArm7g8dvBXRgE84geHA.png" /><figcaption>The Compose Function (<a href="https://github.com/rayepps/radash/blob/master/src/curry.ts#L11">from radash</a>)</figcaption></figure><p>Compose is just syntactic sugar. When you want to pass one function to the next, using compose you can write them in a much more readable series instead. If that doesn’t make sense, take a look at the following snippet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f8cRgCBo02tEbCBu3pTmHw.png" /><figcaption>Composition Example (<a href="https://github.com/rayepps/radash/blob/master/src/tests/curry.test.ts#L8">radash tests</a>)</figcaption></figure><p>If you’re familiar with Python this should be screaming <strong>decorators</strong> to you. They’re functions that take a function as an argument and return a function. If you have more of an OOP background just trust us… you’re in a safe place now, no one can hurt you here.</p><h4>Dependency Inversion</h4><p>By abstracting the framework we’ve also improved testability a bit. We can now call our function without needing to mock any global framework objects (like an express application object). However, our function is still referencing modules it wasn’t given as arguments. To test, this would require file-system/module mocking which is a stated problem to solve.</p><p>A bit of Dependency Inversion can go a long way here. I’m not going to install a DI framework, I’ll just extend the arguments we pass to our function and create another hook function to do the dependency injection.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qe-tHXzXWb3IOEstBKt4vA.png" /><figcaption>The Props Interface</figcaption></figure><p>Along with the req I’d like to pass services (the dependencies) now. I’m going to wrap them both up into an object I’ll call Props. A bit like React, this is everything our function gets passed. Because we’re doing function composition, it keeps things simpler to work with unary functions only.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aoietIuBX09s7Drg3vNciQ.png" /><figcaption>Using Props &amp; Services</figcaption></figure><p>Our function now gets an object with a services property that we can pull from. As a rule, I only put things into services that are non-deterministic. For example, the auth module’s compareCreds function is deterministic. Given the same input, it will always produce the same output (as any password comparison should). I won’t add it to services.</p><p>The current version of our demo code won’t actually work. We’re stating a Service type, and deconstructing services from the Props but we still need a hook to inject those service dependencies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cKsBAh5T0VPb4dsg6x_H9w.png" /><figcaption>A simple Dependency Inversion Hook</figcaption></figure><p>Here it is, our hook takes an object with the services you want to be injected into your endpoint function. This is a bit of a naive implementation. A more robust implementation accepts functions or promises as the values to the services argument and then makes sure they are resolved before calling the endpoint function.</p><blockquote>If you’re into Abstract &amp; Compose and you want to write your own hooks let me drop a bit of advice. Split your hook into a logic function — withServices above — and a wiring function — useService above. This makes testing your hook much easier!</blockquote><p>I hate on DI frameworks but they do some things well. One of those things is ensuring an object gets all the services it depends on. With that said, DI frameworks are still trash because we’ve done that here with a few simple types.</p><p>Our endpoint states a Services type that it passes to the generic Props . Because of this, the endpoint can only access items in services that have been declared on the Services type. Our useServices hook also requires the Services type as a generic parameter and uses it to enforce that all attributes on Services are provided as an argument to the function. Typescript is now validating that our function has all the dependencies it needs to run.</p><h4>Utility Abstraction</h4><p>At this point, we’ve created a design I’d personally be happy to work on. We can still do better by taking advantage of the new hook concept. We have some <em>utility logic </em>mixed in with our valuable business logic. For the sake of isolating our business logic, removing what is boring and repetitive, and making common tasks more reusable, we’ll add another hook.</p><p>My goal is to remove and abstract away</p><ul><li>reading of the request body</li><li>validating the body attributes</li><li>erroring if anything is invalid</li></ul><p>Before we dive into the solution I want to point out a few key concepts I’m using as a guide.</p><ol><li>First, the data in the request is unsafe and untrusted until it has been validated and parsed.</li><li>The req should be treated as an immutable property so other hooks can depend on the original req always being accurate.</li><li>For the sake of clarity, our function should clearly define the arguments it requires.</li></ol><p>Because of these three things I’m going to extend our Props type to include a generic args attribute. When we successfully validate and parse data from the request we’ll place the processed value in args without modifying req. Inside a function, for 99% of cases, you should only access request data through the args .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FKnNXtTAi4T1KpEnPxGTYw.png" /><figcaption>Abbreviated Props with Args (<a href="https://github.com/exobase-inc/exobase-js/blob/b8a57937bae2acd156180e3ee5c867e7d4fc6451/packages/core/src/types.ts#L97">full version</a>)</figcaption></figure><p>We’ll update our function to declare the arguments it requires as type Args and pass that type to the generic Props.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cb4FcOUDU2F_FsII6MdEEA.png" /><figcaption>Endpoint with Args</figcaption></figure><p>Our new hook will take the Args type parameter as well to help ensure that we are always validating all required arguments to a function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WKNImdBfL_fuoIfOWY_EDg.png" /><figcaption>Abbreviated Json Validation Hook (<a href="https://github.com/exobase-inc/exobase-js/blob/master/packages/hooks/src/useValidation.ts">full version</a>)</figcaption></figure><p>Our new hook allows us to specify the schema and let the hook take care of the rest. The logic specific to the endpoint function stays in the endpoint function while the utility logic of <em>validating and parsing</em> is pushed below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KHoCmkC2ww_jijiFWCWAjw.png" /><figcaption>Using our new Json Validation Hook</figcaption></figure><p>We can use this hook pattern to solve, in my experience, every common problem I’ve ever faced as an engineer. I’ve written hooks to handle CORS, caching, logging, alerting, metrics, billing, versioning, websocket messages, SQS record parsing, and more.</p><h3>Bonus Features</h3><p>There are some features you’ll get from using this pattern that we didn’t explicitly mention above.</p><h4>Functional Infrastructure</h4><p>Every endpoint is an entry point. By using compose to chain together the hooks and endpoint function we get a single self-contained function that has everything it needs to execute — accept the framework. This allows us to decouple the code from the infrastructure.</p><p>If every <em>endpoint </em>is a function in a file, the infrastructure is limitless. Our endpoints are like micro containers, able to be moved here and there without change. I can move a file that exports an Abstract &amp; Compose endpoint function from AWS Lambda to GCP Cloud Run or Heroku without needing to make changes.</p><h4>Language Agnostic</h4><p>Although the initial library was written in Typescript, Abstract &amp; Compose only needs functions to work. From my own experience I know I could implement Python, Swift, PHP, Java, Kotlin, Go, and C#.</p><p>An OOP engineer with a few beers and a slow Monday could probably find a way to make Abstract &amp; Compose work via classes as well.</p><h4>Performance</h4><p>Endpoints using the Abstract &amp; Compose design pattern <em>tend </em>to be faster than an equivalent endpoint not using the pattern. I’ll preface this by saying the amount of testing done has been minimal. However, it doesn’t take one of SpaceX’s rocket engineers to reason about why a composed function would be faster.</p><p>Let’s look at Express again, as an example. When a request enters it originates in the global application logic. That application routes it to the correct handler based on the path and method. As the request travels to the handler <strong>it goes through all globally registered middleware</strong>. The entire application is involved.</p><p>The opposite is true with composed function. Every endpoint function is composed of only the hooks it requires to execute. Assume a large project has 100 hooks and 100 endpoints. They have no effect on a new endpoint. The opposite is true with Express, if you have 100 middleware functions every request must be processed through all of them.</p><p>To be fair and maybe more clear, Abstract &amp; Compose does not make your API faster. It isolates the factors that affect a function’s performance to only that function.</p><h3>Exobase</h3><p>I created a library, <a href="https://github.com/exobase-inc/exobase-js">Exobase</a>, that gives you common root hooks, auth hooks, request validation hooks, and the generic Props typing out of the box.</p><p>To be clear, as you saw above, no library is needed. The code in the Exobase library is rudimentary at best. You could probably have any junior engineer write up the wiring given good specs. As the creator and maintainer this a feature I hold dear.</p><p>Exobase provides a handful of packages you can use:</p><ul><li>The @exobase/core package provides the Props type, a functional structure for errors, and a utility for parsing results returned from endpoint functions. It’s three files. You could install core only and write your own hooks. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/core">GitHub</a>.</li><li>The @exobase/hooks package provides commonly used hooks to do CORS, query validation, json validation, header validation, and dependency inversion. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/hooks">GitHub</a>.</li><li>The @exobase/auth package provides hooks and models useful for wiring up your own authentication. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/auth">GitHub</a>.</li><li>The @exobase/next package provides the NextJS root hook useNext that abstracts the NextJS framework. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/next">GitHub</a>.</li><li>The @exobase/lambda package provides the AWS Lambda root hook useLambda that abstracts the AWS Lambda framework. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/lambda">GitHub</a>.</li><li>The @exobase/express package provides the ExpressJS root hook useExpress that abstracts the ExpressJS framework. See on <a href="https://github.com/exobase-inc/exobase-js/tree/master/packages/express">GitHub</a>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CtOn2Wn2QBPrXDDIVRxHNA.png" /><figcaption>Our Endpoint using Exobase</figcaption></figure><h3>Real Life Code</h3><p>I’ve written a few personal and contract projects using Exobase that are now open source on GitHub. You can jump in and see a few dozen Abstracted &amp; Composed endpoints in the wild.</p><ul><li>Praxis <a href="https://github.com/rayepps/praxis-api/tree/master/src/modules">endpoints</a></li><li>IGT <a href="https://github.com/rayepps/igt-api/tree/master/src/modules">endpoints</a></li><li>ShopSwap <a href="https://github.com/rayepps/shopswap/tree/master/pages/api">endpoints</a></li></ul><blockquote>Thanks for reading!</blockquote><blockquote>Even the best idea is nothing without a community — as Ignaz Semmelweis found out. If your a fan, please give it a clap, star it on GitHub, and share with a friend. If you have questions or comments I’d love to hear them.</blockquote><blockquote>- Ray</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=814756d8e06a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lodash is dead. Long live Radash.]]></title>
            <link>https://medium.com/exobase/lodash-is-dead-long-live-radash-d9d52abf428b?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/d9d52abf428b</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[lodash]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Tue, 23 Aug 2022 05:05:02 GMT</pubDate>
            <atom:updated>2022-08-24T01:13:37.955Z</atom:updated>
            <content:encoded><![CDATA[<p>Lodash is old and lame, Radash is new and kool. What more do you need to know?</p><h4>What’s wrong with Lodash?</h4><p>In a time when Javascript’s dynamic abilities were held as a feature instead of a bug, Lodash was made to be as helpful as possible by providing functions that behave differently given different inputs. These days, we know better. Functional concepts like pure functions, deterministic behavior, and function composition have taken root in the javascript community — thank god 🙏</p><p>Lodash was created in 2009 (as Underscore) and rose to power after a fork (as Lodash) around 2012–2013. Do you remember Javascript circa 2012? At that time, I believed writing a backend in Javascript was irresponsible and foolish. The language was too unsafe, unmaintainable, and unwieldy. It’s a bit astonishing to think how far the language, runtimes, and community have all come in 10 years. <strong>It is not the same.</strong></p><p>Lodash was designed and developed to solve Javascript developers&#39; problems circa 2012. Those aren’t our problems anymore. The addition of optional chaining and null coalescing alone makes a large handful of Lodash functions obsolete. Not to mention the changes since the rise of Typescript. In today&#39;s market, a package as foundational as Lodash must<strong> </strong>provide helpful types.</p><h4>A Function Breakdown</h4><p>So you don’t think I’m just blowing smoke, I’ll walk through some of the Lodash functions and why they belong in a museum and not in your project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E7Dp2uQ3joUmNDJuhnEsmQ.png" /><figcaption><a href="https://lodash.com/docs/4.17.15#get">Lodash’s _.get function</a></figcaption></figure><p>The _.get function allows you to pass a string or array value to the path parameter. This was considered a dynamic feature <em>back in the day</em><strong> </strong>but we should all know now it’s just a bug waiting to happen now. In Typescript the code _.get({ name: &#39;ray&#39; }, &#39;age&#39;) won’t produce an error because Lodash’s types don’t verify the given string path exists in the object.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tqfp67joOtUvXDGHGC2udA.png" /><figcaption><a href="https://lodash.com/docs/4.17.15#filter">Lodash’s _.filter function</a></figcaption></figure><p>There are three issues here.</p><ol><li>The function accepts different types as the second parameter. Again, no longer a feature.</li><li>When passing an object, array, or string the types don’t work. Because of Lodash’s poor typing, Typescript can’t check if the values passed exist on the types of the provided array.</li><li>Because of nullish coalescing and optional chaining, thefilter function is a bit obsolete. It was helpful when null checking needed many lines of code to do safely but any of the examples in the image above can now be written with ? and ?? just as same and just as short.</li></ol><p>I won’t drag on these same topics but these points apply to many other functions as well. The <a href="https://lodash.com/docs/4.17.15#find">_.find</a> and <a href="https://lodash.com/docs/4.17.15#some">_.some</a> functions are just two examples.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hIsguUVunvlTkaa-VoLD4Q.png" /><figcaption><a href="https://lodash.com/docs/4.17.15#map">Lodash’s _.map function</a></figcaption></figure><p>Even more egregious are functions like _.map and _.size. They have all the issues mentioned above as well as the ability to accept a variety of values as the first and second parameters. <strong>Not a feature!</strong></p><h4>Code Quality</h4><p>I could accept everything talked about so far but this is really where I draw the line. If I’m using a small helpful toolbelt library with functions I could easily implement myself — <strong>they’re not that hard to write</strong>— I should expect that I can easily look at the source code.</p><p>Here’s a link to the <a href="https://github.com/lodash/lodash/blob/4.17.15/lodash.js">source code</a> that the Lodash docs link to. Take a look.</p><p>There was a time when I was younger when I would have spent hours digging through the source, learning every internal API, cataloging every function call, and eventually understanding it well enough to answer a simple question like <em>how does the isNumber function work?</em></p><blockquote>I should not have to sift through 15,000 lines of code and learn internal APIs to understand how a one-liner function works. This is the anti-pattern of all anti-patterns.</blockquote><p>I understand why this is the reality. If you are traditionally educated as an engineer you’re taught to be DRY above all else. If you’re not in the cult and haven’t drank the Kool-Aid you might know that there are things more valuable than DRY code. In some cases attributes like readability, maintainability, testability, and not pissing off your tech lead are only possible by repeating yourself.</p><p>Like anything, it’s a balance. Lodash, as a simple toolkit library, has gone off the deep end with its code base.</p><h4>But that Community Though…</h4><p>This is the one strength of Lodash. It’s old, everyone knows it, and StackOverflow is full of QA about it.</p><p>Also, to its credit, it was critically helpful to a previous generation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GM2hw1D-p7PTRdWTfpyI6A.png" /></figure><h3>What Then?</h3><p>Radash, pronounced /raw-dash/ (<strong>Ra</strong>mda + Lo<strong>dash),</strong> is the Lodash of our generation.</p><ul><li>It’s a zero-dependency Javascript utility toolkit built in this decade.</li><li>It’s written in Typescript so strong, and useful, types come prepackaged.</li><li>It omits the functions that have become obsolete in Lodash over the years</li><li>It provides new functions you’ve never seen before but always wanted (see below)</li><li>The <a href="https://github.com/rayepps/radash">source code</a> is maintained with understandability for newcomers as a top priority. In most cases, if you want to use a Radash function but don’t want to install it you should be able to copy it straight from GitHub.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xmBsSYVNsRF7Ry2DUbaxvw.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/async-try">The try function</a></figcaption></figure><p>The <a href="https://radash-docs.vercel.app/docs/async-try">try</a> function is probably my favorite Radash function. It’s the biggest <em>hack </em>to code cleanliness I’ve found in the last 10 years. No more forking the control flow when you want to try something. No more creating a mutable let variable outside the try, setting it inside, and checking it after.</p><p>As always with Radash, you’ll love <a href="https://github.com/rayepps/radash/blob/master/src/async.ts#L184">the source</a> and how strong the types are.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XtoceIywD8d36dOBYkfZYw.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/async-parallel">The parallel function</a></figcaption></figure><p>The parallel function has become my drop-in replacement for Promise.all . I know a team who has completely replaced the use of Bluebird in their project with parallel. Not only can you control how many requests are made at once but the error handling is far better. The parallel function throws an AggregateError with all child errors inside after all promises have resolved. This means the response from parallel isn’t an awkward array of objects with an error and value property, it’s just the values you want and expect.</p><p>Check out <a href="https://github.com/rayepps/radash/blob/master/src/async.ts#L105">the source</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sFvs2NmcKVhlrHXFadEqJA.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/async-retry">The retry function</a></figcaption></figure><p>The retry function has become a replacement for the async-retry library for me and my team. Combine try, parallel, and retry and you have the tools to replace most backend service fault tolerance libraries out there.</p><p>Check out <a href="https://github.com/rayepps/radash/blob/master/src/async.ts#L146">the source</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PcXmD_7pmGMVlziyOMXiPA.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/array-counting">The counting function</a></figcaption></figure><p>The counting function counts the occurrences of keys or identities in an array. It’s one of those small functions you find yourself using more than you expected.</p><p>Check out <a href="https://github.com/rayepps/radash/blob/master/src/array.ts#L91">the source</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SzRoJ72XqTzdwkfJ52ibyQ.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/array-range">The range function</a></figcaption></figure><p>The range function is incredibly helpful when dealing with loops. When I see a raw for (let i; i &lt; n; i++) loop I tend to throw up in my mouth a little. The range makes the same kind of function so easy and simple that you can get the same behavior without of the smelly code and risk.</p><p>Range returns a generator so you can safely call range(0, 100000000)</p><p>See <a href="https://github.com/rayepps/radash/blob/master/src/array.ts#L226">the source</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0z-vZC64uR3KBzaqSDdzLQ.png" /><figcaption><a href="https://radash-docs.vercel.app/docs/array-list">The list function</a></figcaption></figure><p>The list function is the range function’s beautiful cousin. In some cases, you need a real list, not a generator. In those cases, you have the list function. Easy, quick, and dynamic list creation is also useful in a lot of use cases outside of looping.</p><p>See <a href="https://github.com/rayepps/radash/blob/master/src/array.ts#L244">the source</a>.</p><p>There are so many more amazing functions. Here are a few more of my favorites.</p><ul><li><a href="https://radash-docs.vercel.app/docs/array-objectify">objectify</a> and <a href="https://radash-docs.vercel.app/docs/object-listify">listify</a></li><li><a href="https://radash-docs.vercel.app/docs/curry-throttle">throttle</a> and <a href="https://radash-docs.vercel.app/docs/curry-debounce">debounce</a></li><li><a href="https://radash-docs.vercel.app/docs/object-pick">pick</a> and <a href="https://radash-docs.vercel.app/docs/object-omit">omit</a></li><li><a href="https://radash-docs.vercel.app/docs/curry-compose">compose</a> and <a href="https://radash-docs.vercel.app/docs/curry-chain">chain</a></li><li><a href="https://radash-docs.vercel.app/docs/curry-memo">memo</a> and <a href="https://radash-docs.vercel.app/docs/curry-proxied">proxied</a></li></ul><p>All functions are well-typed, well-documented, well-tested, and written with simplicity as a priority. If you find that I’m wrong please submit a PR or open an issue!</p><p>The one thing Radash is missing that Lodash has is the community. If you like Radash, share this article, star it on GitHub or share it with a friend.</p><blockquote>I wrote and maintain Radash. This changes nothing.<br>- Ray</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9d52abf428b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/lodash-is-dead-long-live-radash-d9d52abf428b">Lodash is dead. Long live Radash.</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[You’re Not a Senior Software Engineer]]></title>
            <link>https://medium.com/exobase/youre-not-a-senior-software-engineer-9056ef9ffb96?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/9056ef9ffb96</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[code]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Fri, 19 Aug 2022 20:48:24 GMT</pubDate>
            <atom:updated>2022-08-19T20:48:24.408Z</atom:updated>
            <content:encoded><![CDATA[<p>It seems arbitrary the way engineers are given their titles. From the moment you start in the field you long for that glorious Senior title. Most engineers, at least those with no management or founding ambitions, view it as the pinnacle of success.</p><p>But how do you get it? If it can’t be measured, tracked, or valued concretely then we’re just making it up. I’ll concede it’s a bit ethereal but it’s not arbitrary. After 11 years in the industry, working with other engineers of all levels, I‘ll tell you what I see: Your engineering <em>level </em>is the delta between your knowledge and your experience.</p><h3>Knowledge &amp; Experience</h3><p>Let me define these in terms local to the context. Your knowledge and your experience are opposite sides of the same coin. They are the same statement made once in a postivie and the other in a negative. Knowledge means you know how to do a thing. Experience is to know how NOT to do a thing. You can gain knowledge through a variety of educational methods, it’s information you learn. You can only gain experience by doing something wrong, by making a mistake, or by failing.</p><p><em>Some extraordinary people have the ability, to a higher degree than others, to learn from other people&#39;s mistakes. This skill is not common to engineers but it’s a superpower if you have it.</em></p><p>On your first official day working as a software engineer you have zero experience and an overflowing amount of knowledge. That knowledge may have come from university courses or — as in my case — YouTube. You are a Junior Engineer on this day because you have too much knowledge relative to your experience. That&#39;s right, I said it like it&#39;s a problem because it is.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*p_SAL6jluREBst5r.jpeg" /><figcaption>Has some knowledge but no experience</figcaption></figure><p>Junior Engineers believe that they’re right. They’re wrong, they just don’t know it yet. They don’t know it because they don’t have the experience. When you have experience (again, mistakes and failures) it will hit you over the head reminding you it’s possible you could be wrong. Junior Engineers, having an abundance of knowledge and a total lacking of experience, often don’t even consider the possibility they’re way off course.</p><p>I’m not bashing my juniors out there, I was there once. I remember so clearly standing in a small meeting room, around my third year as an engineer, marker in hand, waving furiously at the whiteboard, and arguing as though my life depended on it. I was convinced, beyond reason (as I can only see now), that users MUST have the ability to choose their own username and that the system would fall apart if they were forced to use their email to log in.</p><p>Looking back now, it was so trivial…</p><p>That Senior Engineer, Dave, let me have my way and I was wrong. For our use case, there were many pitfalls with the username system. Dave saw them coming, he tried to warn me, but I wouldn’t listen. As we worked through the project Dave was kind enough to point this out to me.</p><p>Although I regret the decision, I’m thankful because it provided valuable experience. I now understand the pitfalls of a username-based account system again. I did it wrong. That’s experience.</p><p>You might notice if you&#39;re a critical thinker, that a username-based account system isn’t necessarily wrong. I know. I’m not saying I’m correct in my feelings. The value of experience doesn’t come from how it changes your opinions, it comes from how it teaches you that you <em>could</em> be wrong. Nobody is a <em>senior </em>engineer because they have all the correct opinions.</p><p>Many times over my career, when having an argument, I thought back to that whiteboard with Dave and how I believed beyond reason that I was correct. It makes me question what I currently believe and consider the possibility that I’m wrong.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*iUQcKOt4H_KhfD5q.jpeg" /><figcaption>More Knowledge but Experience is catching up</figcaption></figure><h3>Having more Experience</h3><p>Nobody <em>wants</em> to be a mid, being lost in the void between junior and senior. It’s a second-place price reminding you you don’t have what it takes to make senior. Once you have enough experience to question your knowledge you are officially a Mid Level Engineer. This makes it a genuinely important step and stage.</p><p>You’ll know you’re there when you think you know the answer but you look up alternate solutions anyway. When you’re in an argument you won’t cling to your opinions like they provide the oxygen you require to take breath.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*pOi6xWFBlfMo7J9w.jpeg" /><figcaption>Finally, a bit more experience than knowledge</figcaption></figure><h3>More Experience than Knowledge</h3><p>You’re a Senior Engineer when your experience surpasses your knowledge. At this point, you’re more likely to recognize a faulty belief than not. In the job that you do, you have more lessons sourced from mistakes and failures than you do from textbooks and blog posts.</p><p>There is an important assumption I’m making here that&#39;s important to recognize. The technology industry contains near limitless information available to be learned and absorbed as knowledge. Your knowledge and experience are vertical restricted, they don’t apply to the entire industry.</p><p>I’m a fullstack web developer sort of engineer. I’m familiar with clouds, API design, frontend javascript engineering, and other stuff you’d expect from a web dev. Around these topics, I’ve built up a lot of knowledge and experience. If I decided to join an embedded programming team at SpaceX, writing the C code that&#39;s loaded into the microprocessor on a servo that controls the pitch of a rocket&#39;s arms… my wealth of experience would be worth nothing.</p><p>The point, senior engineers are senior on particular subject matters. If you’re young and wondering if you should go deep in one area or go broad in many keep this in mind. It’s possible to be a true fullstack engineer and go deep in both front and backend for example but as I can tell you, it might take up most of your twenties.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*6VC68y7d-8Ai0Pnh.jpeg" /><figcaption>Exceedingly more experience than knowledge</figcaption></figure><p>dFor the last few years, I’ve maintained that there has to be a title above Senior Engineer. It simply doesn’t make sense, based on my experience, that Senior Engineer is the ceiling for an individual contributor — or anyone else who works hands-on in the code. I’ve seen engineers who operate at a whole different level and recently I can say I’ve joined them.</p><p>I’ve always kept my hands deep in the code but over the last four years, I’ve been a tech lead (in different capacities and titles) leading senior engineers. What I’ve found is that it’s impossible to be completely right. You’re always wrong, if not now, then sometime in the future.</p><p>The problem is that businesses change, customers’ needs change, markets change, products pivot, engineering teams and their expertise change, and so on. All this means that what we build as engineers will eventually, one day, have to change.</p><p>This is another level of realization that requires a lot of experience over time to see. Choosing AWS Lambda instead of GCP Cloud Run as infrastructure won’t be the right choice if AWS goes bankrupt in ten years. There are things that are out of your control that you can’t predict.</p><p>As a final-level engineer, you identify the things you <strong>can</strong> control and predict and build your software around them. Everything else is an externality that your system should be abstracted from to allow for quick and easy change.</p><p>How do you abstract away elements as deeply connected and ingrained in your application as the underlying infrastructure of framework? That’s for <a href="https://unishine.dev/">another article</a>.</p><h3>Relativism: What’s the point?</h3><p>A note about being wrong and right. Sometimes you’re wrong… but sometimes you’re right. Not all ideas, designs, solutions, or implementations are equal. Some solutions are better than others.</p><p>Knowledge is the gas pedal, driving you forward in an argument. You have facts, code samples, articles, best practices, and a whole chapter from a Martin Fowler book on your side. These things fuel the thought, the instinct, that you’re right. They drive the pedal down and the argument forward.</p><p>Experience is the break, a lifeline to slow and stop the argument. Memories of previous mistakes and a healthy fear that your solution won’t <em>actually </em>work bring clarity and make an opening for you to consider an alternate viewpoint.</p><blockquote>If you can’t intelligently argue for both sides of an issue, you don’t understand the issue well enough to argue for either.<br>- <a href="https://www.reddit.com/r/quotes/comments/4ruxex/if_you_cant_intelligently_argue_for_both_sides_of/">GBlink’s Dad</a></blockquote><p>Sometimes you’re right, and when you are, push. The team, company, and project depend on the correct decisions being made. Sometimes you’re wrong, and when you are you won’t be able to see it if all you have is knowledge driving you forward into an argument.</p><p>How do you concretely evaluate someone’s knowledge and experience? I’m not sure yet. I’m working on it. For now, I just know this is the way.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9056ef9ffb96" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/youre-not-a-senior-software-engineer-9056ef9ffb96">You’re Not a Senior Software Engineer</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Only Three Types in an API]]></title>
            <link>https://medium.com/exobase/the-only-three-types-in-an-api-246544b257c7?source=rss-b656dc19725c------2</link>
            <guid isPermaLink="false">https://medium.com/p/246544b257c7</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[code]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Ray Epps]]></dc:creator>
            <pubDate>Tue, 09 Aug 2022 23:07:03 GMT</pubDate>
            <atom:updated>2022-08-09T23:07:03.827Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="View, Model, and Storage Types" src="https://cdn-images-1.medium.com/max/1024/1*LhLXkGWmkNfhEb26egtexw.jpeg" /><figcaption>View, Model, and Storage Types</figcaption></figure><p>As a small child, one who tended to loose toys and jackets, I was thought this phrase:</p><blockquote>Everything with a place and everything in its place</blockquote><p>The idea: if everything has been assigned a location then everything can be put where it belongs. If an item does not have an assigned location it can’t be put away. A terrific lesson for children and engineers alike.</p><p>As engineers, we should think critically about our types, classes, layers, services, and the other devices we use to organize our software. Everything should have a dedicated purpose and home where it belongs. After 10+ years of working on APIs in disarray, this is how I organize my types.</p><h3>The Only Three Types</h3><p>There are only three types in an API. They are the view, model, and storage types. Of course you may have others but they should be isolated and abstracted into modules where the interface interacts strictly by model types.</p><p>As a request comes in we validate and parse it to produce arguments, often partial model types. When putting model objects into the database we convert them to storage types. When pulling from the database we map the storage type to a model type. When returning models to the client we transform them into view types.</p><h3>Model Types</h3><p>This is what you’re probably used to. Everything in your API is probably a model type right now. Model types are the shapes that the API expects to work with. They are the standard shape of data. Let’s assume your API is a public community commerce where users can post listings they’d like to sell.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/85b678c7a4329fb46d6a0687a4298ca0/href">https://medium.com/media/85b678c7a4329fb46d6a0687a4298ca0/href</a></iframe><h3>Storage Types</h3><p>When you store a model you often modify it to enrich the object with properties that are helpful for the specific storage your using. For example, when using Mongo I take any <em>foreign key</em> properties and add a new property where this id is converted to Mongo’s ObjectId type. This makes Mongo’s indexes and queries faster.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b722931abeb374db93414e9e9c32572e/href">https://medium.com/media/b722931abeb374db93414e9e9c32572e/href</a></iframe><p>In a SQL database, you might want to convert millisecond timestamps from your model into Date objects or flatten properties represented in the model as arrays or objects.</p><p>We call these storage types instead of database types because in modern applications we’re often storing different data in a variety of storage devices. Your storage types might cover timeseries data you put into Mongo, relational data you put in Postgres, payload data you put in SQS, event data you put in Dynamo, and cached data you put in Redis. Storage types cover all these modules. Each of the modules should accept and return model types.</p><h3>View Types</h3><p>These are the types surfaced by your API. Often times your model includes data you don’t want exposed to the client. Or maybe, you only want it exposed in different endpoints for specific authorization levels. View types clearly express the data that should be returned and if done properly will enforce correctness and disallow over-exposing data to the user.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/68f78e9ba9c1031d64d9e58b1e40b62a/href">https://medium.com/media/68f78e9ba9c1031d64d9e58b1e40b62a/href</a></iframe><p>There are only two differences between this type and our `Listing` model type.</p><ol><li>We’ve added a <strong>_view</strong> property. This is how we ensure that the `Listing` model can’t be unintentionally used in place of the `ListingView`. Because `ListingView` is really just a subset of properties from the `Listing` model, Typescript would allow the `Listing` model to be used if not for a unique property on the view type: `_view`.</li><li>We’ve omitted the `reported` property. For our use case in the basic find listing endpoint we don’t want to expose how many times a listing has been reported.</li></ol><h3>Storage Mappers</h3><p>To maintain this abstraction, any storage module needs to accept and return model types. However, internally, those model types should be mapped to storage types when storing and mapped back to model types when reading from storage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/30eb6cfe1178d8217ae93a2804247219/href">https://medium.com/media/30eb6cfe1178d8217ae93a2804247219/href</a></iframe><p>This should live inside any database or storage module you have. This allows the business logic to focus on business logic things using the model types.</p><h3>View Mappers</h3><p>A small module with view mapping function handles converting the model type to the view type strictly and safely.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/35118e39324ec8ec684d8adde025df73/href">https://medium.com/media/35118e39324ec8ec684d8adde025df73/href</a></iframe><h3>Endpoints</h3><p>It all works together in an endpoint. A request enters, model types are created, storage modules are called with model types, they return model types, any business logic is done, and the endpoint returns a view type.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/92138a1a643f84d3021b526227fdc72a/href">https://medium.com/media/92138a1a643f84d3021b526227fdc72a/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=246544b257c7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/exobase/the-only-three-types-in-an-api-246544b257c7">The Only Three Types in an API</a> was originally published in <a href="https://medium.com/exobase">Exobase</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>