Hi, I’m Redowan

...and this is the graveyard for my musings on software.

Here-doc headache

I was working on the deployment pipeline for a service that launches an app in a dedicated VM using GitHub Actions. In the last step of the workflow, the CI SSHs into the VM and runs several commands using a here document1 in bash. The simplified version looks like this: # SSH into the remote machine and run a bunch of commands to deploy the service ssh $SSH_USER@$SSH_HOST <<EOF # Go to the work directory cd $WORK_DIR # Make a git pull git pull # Export environment variables required for the service to run export AUTH_TOKEN=$APP_AUTH_TOKEN # Start the service docker compose up -d --build EOF The fully working version can be found on GitHub2....

July 19, 2024

The sane pull request

One of the reasons why I’m a big advocate of rebasing and cleaning up feature branches, even when the changes get squash-merged to the mainline, is that it makes the PR reviewer’s life a little easier. I’ve written about my rebase workflow before1 and learned a few new things from the Hacker News discussion2 around it. While there’s been no shortage of text on why and how to craft atomic commits3, I often find those discussions focus too much on VCS hygiene, and the main benefit gets lost in the minutiae....

July 14, 2024

I kind of like rebasing

People tend to get pretty passionate about Git workflows on different online forums. Some like to rebase, while others prefer to keep the disorganized records. Some dislike the extra merge commit, while others love to preserve all the historical artifacts. There’s merit to both sides of the discussion. That being said, I kind of like rebasing because I’m a messy committer who: Usually doesn’t care for keeping atomic commits1. Creates a lot of short commits with messages like “fix” or “wip”....

June 18, 2024

Protobuffed contracts

People typically associate Google’s Protocol Buffer1 with gRPC2 services, and rightfully so. But things often get confusing when discussing protobufs because the term can mean different things: A binary protocol for efficiently serializing structured data. A language used to specify how this data should be structured. In gRPC services, you usually use both: the protobuf language in proto files defines the service interface, and then the clients use the same proto files to communicate with the services....

May 10, 2024

TypeIs does what I thought TypeGuard would do in Python

The handful of times I’ve reached for typing.TypeGuard in Python, I’ve always been confused by its behavior and ended up ditching it with a # type: ignore comment. For the uninitiated, TypeGuard allows you to apply custom type narrowing1. For example, let’s say you have a function named pretty_print that accepts a few different types and prints them differently onto the console: from typing import assert_never def pretty_print(val: int | float | str) -> None: if isinstance(val, int): # assert_type(val, int) print(f"Integer: {val}") elif isinstance(val, float): # assert_type(val, float) print(f"Float: {val}") elif isinstance(val, str): # assert_type(val, str) print(f"String: {val}") else: assert_never(val) If you run it through mypy, in each branch, the type checker automatically narrows the type and knows exactly what the type of val is....

April 27, 2024

ETag and HTTP caching

One neat use case for the HTTP ETag header is client-side HTTP caching for GET requests. Along with the ETag header, the caching workflow requires you to fiddle with other conditional HTTP headers like If-Match or If-None-Match. However, their interaction can feel a bit confusing at times. Every time I need to tackle this, I end up spending some time browsing through the relevant MDN docs123 to jog my memory. At this point, I’ve done it enough times to justify spending the time to write this....

April 10, 2024

Crossing the CORS crossroad

Every once in a while, I find myself skimming through the MDN docs to jog my memory on how CORS1 works and which HTTP headers are associated with it. This is particularly true when a frontend app can’t talk to a backend service I manage due to a CORS error2. MDN’s CORS documentation is excellent but can be a bit verbose for someone just looking for a way to quickly troubleshoot and fix the issue at hand....

March 12, 2024

Dysfunctional options pattern in Go

Ever since Rob Pike published the text on the functional options pattern1, there’s been no shortage of blogs, talks, or comments on how it improves or obfuscates configuration ergonomics. While the necessity of such a pattern is quite evident in a language that lacks default arguments in functions, more often than not, it needlessly complicates things. The situation gets worse if you have to maintain a public API where multiple configurations are controlled in this manner....

March 6, 2024

Einstellung effect

In 9th grade, when I first learned about Lenz’s Law1 in Physics class, I was fascinated by its implications. It states: The direction of an induced current will always oppose the motion causing it. In simpler terms, imagine you have a hoop and a magnet. If you move the magnet close to the hoop, the hoop generates a magnetic field that pushes the magnet away. Conversely, if you pull the magnet away, the hoop creates a magnetic field that pulls it back....

February 24, 2024

Strategy pattern in Go

These days, I don’t build hierarchical types through inheritance even when writing languages that support it. Type composition has replaced almost all of my use cases where I would’ve reached for inheritance before. I’ve written1 about how to escape the template pattern2 hellscape and replace that with strategy pattern3 in Python before. While by default, Go saves you from shooting yourself in the foot by disallowing inheritance, it wasn’t obvious to me how I could apply the strategy pattern to make things more composable and testable....

February 17, 2024

Anemic stack traces in Go

While I like Go’s approach of treating errors as values as much as the next person, it inevitably leads to a situation where there isn’t a one-size-fits-all strategy for error handling like in Python or JavaScript. The usual way of dealing with errors entails returning error values from the bottom of the call chain and then handling them at the top. But it’s not universal since there are cases where you might want to handle errors as early as possible and fail catastrophically....

February 10, 2024

Retry function in Go

I used reach for reflection whenever I needed a Retry function in Go. It’s fun to write, but gets messy quite quickly. Here’s a rudimentary Retry function that does the following: It takes in another function that accepts arbitrary arguments. Then tries to execute the wrapped function. If the wrapped function returns an error after execution, Retry attempts to run the underlying function n times with some backoff. The following implementation leverages the reflect module to achieve the above goals....

February 4, 2024

Type assertion vs type switches in Go

Despite moonlighting as a gopher for a while, the syntax for type assertion and type switches still trips me up every time I need to go for one of them. So, to avoid digging through the docs or crafting stodgy LLM prompts multiple times, I decided to jot this down in a gobyexample1 style for the next run. Type assertion Type assertion in Go allows you to access an interface variable’s underlying concrete type....

January 31, 2024

Patching pydantic settings in pytest

I’ve been a happy user of pydantic1 settings to manage all my app configurations since the 1.0 era. When pydantic 2.0 was released, the settings portion became a separate package called pydantic_settings2. It does two things that I love: it automatically reads the environment variables from the .env file and allows you to declaratively convert the string values to their desired types like integers, booleans, etc. Plus, it lets you override the variables defined in ....

January 27, 2024

Omitting dev dependencies in Go binaries

As of now, unlike Python or NodeJS, Go doesn’t allow you to specify your development dependencies separately from those of the application. However, I like to specify the dev dependencies explicitly for better reproducibility. While working on a new CLI tool1 for checking dead URLs in markdown files, I came across this neat convention: you can specify dev dependencies in a tools.go file and then exclude them while building the binary using a build tag....

January 21, 2024

Eschewing black box API calls

I love dynamically typed languages as much as the next person. They let us make ergonomic API calls like this: import httpx # Sync call for simplicity r = httpx.get("https://dummyjson.com/products/1").json() print(r["id"], r["title"], r["description"]) or this: fetch("https://dummyjson.com/products/1") .then((res) => res.json()) .then((json) => console.log(json.id, json.type, json.description)); In both cases, running the snippets will return: 1 'iPhone 9' 'An apple mobile which is nothing like apple' Unless you’ve worked with a statically typed language that enforces more constraints, it’s hard to appreciate how incredibly convenient it is to be able to call and use an API endpoint without having to deal with types or knowing anything about its payload structure....

January 15, 2024

Annotating args and kwargs in Python

While I tend to avoid *args and **kwargs in my function signatures, it’s not always possible to do so without hurting API ergonomics. Especially when you need to write functions that call other helper functions with the same signature. Typing *args and **kwargs has always been a pain since you couldn’t annotate them precisely before. For example, if all the positional and keyword arguments of a function had the same type, you could do this:...

January 8, 2024

Rate limiting via Nginx

I needed to integrate rate limiting into a relatively small service that complements a monolith I was working on. My initial thought was to apply it at the application layer, as it seemed to be the simplest route. Plus, I didn’t want to muck around with load balancer configurations, and there’s no shortage of libraries that allow me to do this quickly in the app. However, this turned out to be a bad idea....

January 6, 2024

Statically enforcing frozen data classes in Python

You can use @dataclass(frozen=True) to make instances of a data class immutable during runtime. However, there’s a small caveat—instantiating a frozen data class is slightly slower than a non-frozen one. This is because, when you enable frozen=True, Python has to generate __setattr__ and __delattr__ methods during class definition time and invoke them for each instantiation. Below is a quick benchmark comparing the instantiation times of a mutable dataclass and a frozen one (in Python 3....

January 4, 2024

Planning palooza

When I started my career in a tightly-knit team of six engineers at a small e-commerce startup, I was struck by the remarkable efficiency of having a centralized hub for all the documents used for planning. We used a single Trello board with four columns—To-do, Doing, Q/A, Done—where the tickets were grouped by feature tags. We’d leverage a dummy ticket as an epic to sketch out the full plan for a feature and link related tickets to it....

January 1, 2024