While Kelvin talks about the benefits of using Sails, I was thinking about the other “boring” things that constitute a boring stack. It goes beyond tecnologies, like how to run a business, how to organize your team, and how to market the product. And when those things are boring, they are unsexy and oftentimes neglected.
A feature flag is a decision point in your code that can change the behavior of your application.
Temporary flags are often used to safely deploy changes to your
application or to test new behaviors against old ones. After a new behavior
is being used by 100% of your users, the flag is intended to be removed.
Traffic light showing a green light. Unsplash - Eliobed Suarez
Permanent flags give you a way to control the behavior of your
application at any time. You might use a permanent flag to create a
kill-switch or to reveal functionality only to specific users.
Traffic light showing a green light. Unsplash - Eliobed Suarez
Feature Flags Are Context Sensitive
The code path token can change based on the context provided; for example, the user’s identity, the plan they’ve paid for, or any other data.
Feature Flags Are Deployment Agnostic
Feature flags can be used to control which users can see each change. This decouples the act of deploying from the act of releasing.
Use Cases for Flags
Release - A temporary flag that initially serves false to all targets, then progressively rolls out true to targets until it reaches 100%
Kill Switch - A permanent safety mechanism used to shut off non-core functionality or third-party tools in an emergency
Experiment - A flag to test a hypothesis and provide continuous optimization using beta users and A/B tests
Migration - A temporary flag used to migrate data or systems while keeping your application available and disruption free
These are the ones advertised by LaunchDarkly. There are many more use cases, and they allow for customization.
What is LaunchDarkly?
LaunchDarkly is a company that provides feature flags as a service.
At work, flags are served and managed by LaunchDarkly’s services.
LaunchDarkly is integrated through streaming via web sockets, meaning each web app user session receives messages only when necessary. Changes can be pushed out to clients in real time.
A diagram showing the end-to-end connection between LaunchDarkly’s flag
delivery network and your application.
From here, we usually add conditional logic to toggle the flag logic to the target code.
We can also use the same method to do the same on the server-side as well.
Adding User Context
LaunchDarkly uses context to target which flags should be on or off.
The user’s context, known in LaunchDarkly as identity, must be used when initializing the application.
Anonymous Users
On initialization, a user session will be identified as an anonymous user.
This is because in the initialization, the user’s session token (access token) has not been verified yet.
Because we want to use flags for public users as well, i.e. those not logged into the app, we initialize the Provider before the token check.
An anonymous user has two attributes:
key - a unique identifier for the anonymous user and a field on the ldUser type.
anonymous - a field on the ldUser type that notes if a user is anonymous or not. Stored as a Boolean.
Authenticated Users
Once the user authenticates, and the application has retrieved the user context,
we identifies the user using the ldClient.identify function.
In this identify function, we pass along the following information about our user:
key: Our user’s unique identifier (as a uuid)
anonymous: false
email: the user’s email address
In addition, we pass along some custom fields that we can use to narrow down the user’s targeting.
This includes their role and their organization, since our app is a multi-tenant.
If this user has already been added to LaunchDarkly, their flag profile will be returned.
If this user is new, LaunchDarkly will automatically create this user, create their flag profile, and be returned.
Logout
On logout, the application re-identifies the user using the ldClient.identify function.
Since the application has a logout hook, we add a handler to identify the user to be an anonymous user again.
This resets all flags to switch over for anonymous users.
When do we change our flags?
Per Release - For each release, our release team has a list of flags to modify, including toggling targeting and changing rules and segments per rules.
On Market Support - There may be a request to make a non-release changes. These include new customer onboarding and turning on a feature for an existing customer.
Deployments
There are different types of deployments:
Canary Releases - User groups who would like to opt in
Ring Deployments - Different user segments at a time - e.g. beta or power users
Percentage-based Deployments - Start with low percentage, then move to higher. For operational changes
Each of these can be implemented using feature flags.
Feature flags and blue/green deploys are complementary techniques. Although there are areas of overlap, each approach has distinct benefits, and the best strategy is to use both.
Testing
It isn’t necessary (or even possible) to test every combination of feature flags. Testing each variation of a flag in isolation (using default values for the other flags) is usually enough, unless there’s some known interaction between certain flags.
Here’s an example using jest and LaunchDarkly’s mock testing library.
import { mockFlags } from 'jest-launchdarkly-mock';it('tests with the flag on' , () => { mockFlags({ [FLAG_IN_QUESTION]: true, }); // Write your test here});it('tests with the flag off' , () => { mockFlags({ [FLAG_IN_QUESTION]: false, }); // Write your test here});
Flag Maintenance
Cleaning up flags aggressively is the key to preventing technical debt from building up. There’s no royal road to flag cleanup, but there are some processes that make it manageable.
A stale flag is a temporary flag that is no longer in use and has not been cleaned up. Too many stale flags are a form of technical debt and an antipattern that you should avoid.
At work, we follow a practice to do it prior to any major release. This is about every 3 to 4 months.
Questions you should be able to answer now
What is feature management?
A decision point in your code that can change the behavior of your
application.
What is the difference between a temporary vs. a permanent flag?
A temporary flag will ultimately be removed from the application. A
permanent won’t and will stay as a kill switch.
How do flags configured in LaunchDarkly get delivered to our applications?
LaunchDarkly uses streaming via web sockets.
How can flag management be resilient to failures?
LaunchDarkly has multiple fallbacks. If their CDN goes down, it goes to
their service. If their service goes down, you can route it to an
external service like their relay proxy. If all of those go down, then
there will be cached results from the last sucessful response. And if
this is a first time request, then a fallback default value is used.
I wrote a script that updates my daily notes to add properties. Since I’ve been using Obsidian for awhile, properties was introduced much later. I’ll walk through my code.
// Get the current file title, which is in a common date format: YYYY-MM-DDconst currentFileTitle = tp.file.title;// Set folder you want to get latest file for hereconst folder = "Calendar/Personal Reviews/journal and daily review/2022";// Get all files in that folder, including nested foldersconst filesInFolder = app.vault.getMarkdownFiles().filter(file => { return file.path.startsWith(folder);});// Sort files by file namefilesInFolder.sort((a, b) => a.basename < b.basename ? 1 : -1);// Get the index of the current fileconst currentIndex = filesInFolder.findIndex(file => file.basename === currentFileTitle);// Get basename of previous and next TFiles to be used in linklet previousEntry = '';let nextEntry = '';// Wrap it around a try catch block in case there's something wrong with getting these basenamestry { previousEntry = `[[${filesInFolder[currentIndex + 1].basename}]]`} catch (err) { console.error(err);}try { nextEntry = `[[${filesInFolder[currentIndex - 1].basename}]]`} catch (err) { console.error(err);}
Here’s my template that the templater plugin uses in markdown.
<%*This is where the JS code above is inserted-%>---tags: - logs/daily created: <% currentFileTitle %>previousEntry: - "<% previousEntry %>"nextEntry: - "<% nextEntry %>"---
I’ve had to modify this depending if the file already has properties or not.
I’ve been enjoying micro-blogging a lot more than I anticipated. It lowers the barrier for writing a quick post and sharing it. An essay is something that is a much heavier lift, and goes through many edits before I publish.
I didn’t realize where we were with AI-generated music.
Besides the Fuzzy Search, VSCode also now has locked scrolling when you have two windows side by side
I’ve been listening to the podcast, Shit You Don’t Learn in School. There are a lot of gems, especially around finding business ideas. I might take the patent one at some point
Also, if anyone would like to collaborate with me for a potential business idea brainstorm, let me know
There’s a waitlist for Ali Abdaal’s Productivity Lab. I’m debating it
I got a chance to play around with Elicit and was reading through Adam Wiggin’s
Tweet. I think
I might make a quick web app trying multi-column queries about a particular
topic and allow more of a computational style of conversational chat.
Computational style of conversational AI using Elicit
I was getting some errors in my unit test output because I converted my SVGs to React components using SVGR.
As such, I needed to re-configure Jest accordingly.
Create the mock file
export default "svgr-div"; // This is the kebab case that jest is looking forexport const ReactComponent = "div";// @see https://react-svgr.com/docs/jest/ for setup
In practice, you try to eliminate task barriers that require back and forth communication by setting dedicated block of time to meet. An office hour, reminiscent of your professor’s office hours, means you broadcast to your working network you are available in this block of time to chat about open loops.
The problem this solves is eliminating the number of unknowns in your work, whether that be client feedback, questions about a design contract, or ambiguous bug tickets. These tasks become open loops are require external help in order to uncover what tasks you have to do next. It also lowers the communication gap with your work network or team and bridge that gap of understanding. The end goal is it helps with end output and/or goals.
Championed by Cal Newport and highlighted in his book, Slow Productivity.
I periodically try to dump all of my mental contexts into my notes. I noticed this comes in a few different flavors.
Safari Tabs on my iPhone and iPad: Goes into daily notes
Random thoughts - go into drafts and get processed daily
Shopping - including groceries, bills, and things I would like but not need. Those all go into a task manager
Dangling tasks - gets reviewed in the task manager inbox weekly
Personal thoughts - journaling every two weeks, although I want to do this more often
Events I have not logged - go into my calendar.
Downloads folder - weekly processed into other folders.
Long-term storage in Dropbox and backup drive.
Short-term in Inbox folder
Photos - place special ones in folders. Select shots in favorites.
I am trying to think of other things that I purge. I used to write in a notepad and write on the page of everything I was thinking about. I used to write about people I have not spoken to in a long time that I should reach out to, mantras I came up with, or completely random thoughts.
Last week, I launched the Stream which I’m proud of. I finally have a short space to write things down quickly and post to the website, rather than the longer posts for my essays. It all blurs together as I work on different output types. At some point, I want a more robust content funnel for writing. Or somewhere I can see the progress of different writing pieces visible on one dashboard.
The stream is a personal feed of my thoughts on things I’m working on, interested in, or a random shower thought that might be worth putting some additional thought into. The stream is a form of expression, posting something spur of the moment, without the crowdedness of a social network like Twitter or Facebook. Don’t get me wrong, those places are great for comment and reply interactions. But I also need my own trail of what I’m writing and thinking about that’s not hammered by likes, comments, and other social features that I find distracting.
There’s something nice and calming about seeing half-formed thoughts and ideas on a screen that’s wholly yours. I wish there was more of this kind of web. I was drawn to Tumblr back in 2008 because you could Microblog before you had a Facebook, LinkedIn, Twitter, Threads, or your variant of Fediverse website. Or even prior like posting your thoughts, fan-fiction, and other inspired writing on LiveJournal. Nowadays, that’s all we read from and post to. Or graduated to video like an algorithmic feed like TikTok. No shade for these companies vying for our limited attention. I think I’d rather take it slow and read some stranger’s beautifully designed blog that has little nuggets than an endless scrolling monstrosity that makes you wonder why you’ve spent hours on Reddit, but can’t recall much of what you did.
There’s more I want to add here, like better search capability, pagination, and an updated description of the stream. It’s designed minimally to emphasize the point that it’s not supposed to be too rich nor attention grabbing. And its flow is based on my writing, and no one else. Welcome to The Stream.
Stands for Observe-Orient-Decide-Act (“OODA”) Loop. It’s an iteration because it
feeds back into itself. Initially applied for military use, but has since been
used for business decision-making and personal development.
Observe the situation and mentally note any key data points.
Orient by placing data in the context of existing knowledge and mental maps to create a picture of the current situation.
Make a decision on how to act in light of that situation.
I’m finally cleaning up my RSS feeds because it’s too untenable. In my Readwise Reader, my inflow is far greater with things I know I won’t read over things that I know I’ll read later. Hence trapping me in a cycle of scrolling over reading. I’m going to unsubscribe from a bunch of things.
For the past few years, I’ve kept a note of inspirational websites. These websites include blogs that I continue to follow, creatives showing their talents, and web design that makes me wonder. I remember years ago, Ayush and I would break down websites that made us go, “How did they do that?” and reverse engineer them. It was interesting going into the source code, trying to solve how someone managed to wrangle with CSS to create a pretty infinite scrolling experience.
I wanted to share some of those websites with you. In the inspirations section, I break down which websites I’ve found and share them with you. I am starting with people whose work I admire and ideas within it that I want to remix back to this website.
Sometimes these websites are minimal, and there’s elegance to it. Sometimes, there’s a lot of interactivity and media, and it’s not overwhelming. When I think back to this website, I’m constantly wondering what I could add, and more importantly, remove. It’s a constant battle to strike the right balance in design.
Watch out for more updates to inspirations, including written work, YouTubers, and so much more. There’s overlap with creators, but that’s a deeper dive into an artist’s work. I’m looking forward to updating these inspirations from time to time.
I’ve slowed down on the website updates and have focused more on projects and
writing. I think there’s always a balance to strike with what more I could do
versus what really matters.
We have a little over a month until the wedding. The final details are being looked
at. When people tell me wedding planning is no joke, I can echo that sentiment.
Brooklyn Nine-Nine Promo
We bought the Brooklyn Nine-Nine boxset and have been watching through the series.
Netflix has the first four seasons, but there wasn’t enough closure for the remaining
seasons, and iTunes or Apple TV had a sale, so we’ve been putting that in the
background while we work on the wedding. It’s nice to have something grounding.
As for books, I’ve been wondering if I should read “The Power Broker” by Robert Caro.
It’s a monster of a book about Robert Moses. I was listening to 99% Invisible’s mini-series
about the book, and it sounds intense.
I’ve been slowly making my way through Cal Newport’s new book, “Slow Productivity: The Lost Art of Accomplishment Without Burnout” as well as Emily Nagoski’s new book, “Come Together: The Science (and Art!) of Creating Lasting Sexual Connections”.
My partner is starting to use Capacities, and I set up
an account to also try it out in tandem. There’s a lot of good ideas in that app,
but I’m not sure I’m going to covert from Obsidian. I’m used to my tool, and I’m
probably going to stick with it for now.
For the past few years, I’ve been trying something new instead of New Year’s resolutions: a yearly theme.
But first, what is a “yearly theme”? Instead of setting resolutions at the start of the year, you set an overall idea of how you would like to approach each year or season. This then becomes a guide for your personal and/or professional life throughout that period.
Ideally, you would also think of what the ideal outcome is for a set period and some key actions you would like to complete. This helps you maintain focus throughout the year when considering new projects.
My yearly theme for 2024 is “Year of Renewal”. As I mentioned in the last newsletter, I was under a lot of stress last year, and I thought I would take a step back and gather myself again.
In practice, this looks like a goal a month, focusing on a different part of renewal. January was defining boundaries. I answered the following questions:
What do I have to accept?
What do I leave behind?
What do I adopt?
This helped me determine how to bring in the new year by making these identifications.
In February, I wanted to renew my sense of play. As I’ll go over in my highlights, I’ve been playing with code again. ChatGPT and other LLMs have renewed my interest in bespoke applications, i.e. one-off applications that serve a singular purpose. It’s renewed my sense of what I like to work on, even if it’s a selfish purpose to fulfill my curiosities.
I’ll go over March’s goal in the next newsletter, as I’m still in the middle of it.
Craft By Zen Highlights
Now that I have a routine of adding content to my website, here’s a recap of what I did in March.
Weekly Notes - Weekly notes continue! I’ve been using that as a scratch pad collecting what I’ve discovered over the past week. I’ve wanted a reflection process that slows down the hose of information presented to us daily. There’s too much endless scrolling, and by forcing myself to review what it is I’m looking at. And also, at a second glance, I realize what’s important and not important to share.
Case in point, here’s an article about a couple who reconnected 77 years later.
Recruiters - as recruiters reach out to me, I’ve been adding their job postings to my classifieds.
Curations - I haven’t been as active in adding more curations. I have plans on adding a micro-blog at some point that will distinguish longer posts and a free-form stream of thought. I love the examples like The stream and The Verge authors.
Projects / Experiments - I was messing around with ChatGPT to create a Guitar Fingering tool to help me quickly look up a chord. I wasn’t satisfied with what I found online, so I aided ChatGPT to build on it, although, at the end of the day, I took what it outputted as a template and finished it myself.
Lastly, I was putting together some small scripts to help me parse through my Google Contacts export and add them to my Obsidian vault as a personal CRM. I will have an article to follow up with how I use the personal CRM soon.
Thanks for all of the support from everyone! If you like these updates, you can always reply to these emails and let me know what’s going on in your life.