<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Josh Burgess</title>
    <description>The latest articles on Forem by Josh Burgess (@joshtheburgess).</description>
    <link>https://forem.com/joshtheburgess</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1125814%2F1279e733-87a2-4c3a-a60c-0e53e0358167.jpg</url>
      <title>Forem: Josh Burgess</title>
      <link>https://forem.com/joshtheburgess</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/joshtheburgess"/>
    <language>en</language>
    <item>
      <title>Accelerate Your Startup’s Growth</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Wed, 20 Aug 2025 21:42:44 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/accelerate-your-startups-growth-3gf3</link>
      <guid>https://forem.com/joshtheburgess/accelerate-your-startups-growth-3gf3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhutm7whtb68q70a9f893.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhutm7whtb68q70a9f893.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Joining an Accelerator — My experience in a 2025 Summer Cohort
&lt;/h4&gt;

&lt;p&gt;First I will start by saying I cannot thank my mentors (Bradley , Ketaki, and Kara) and fellow cohort participants enough for making this accelerator an environment that really forges ideas and supports founders.&lt;/p&gt;

&lt;p&gt;I will be writing some articles that go a little more in depth on some of the business side of a Startup in the future, like forecasting, valuations, building a pitch deck, Cap Table management, etc.&lt;/p&gt;

&lt;p&gt;This is a long article, and if I am being honest, it is more of my distilled and refined notes so I don’t forget the firehose of information that came from this experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  PreWork
&lt;/h3&gt;

&lt;p&gt;Picture this: You’re sitting across from an investor, palms sweaty, about to drop your market size estimates. You confidently declare your TAM is “the entire $317 billion SaaS market” because, hey, your app &lt;em&gt;could theoretically&lt;/em&gt; replace every piece of software ever built, right?&lt;/p&gt;

&lt;p&gt;Wrong. And now you’re being escorted out faster than someone who brings pineapple pizza to an Italian restaurant.&lt;/p&gt;

&lt;p&gt;Let’s fix that embarrassing scenario by mastering the startup holy trinity: &lt;strong&gt;Total Addressable Market (TAM)&lt;/strong&gt;, &lt;strong&gt;Serviceable Addressable Market (SAM)&lt;/strong&gt;, and &lt;strong&gt;Serviceable Obtainable Market (SOM)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The real question this should answer is: How big can your startup get?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TAM&lt;/strong&gt; is not the entire market size or size of the spend problem you are addressing (eg. Mudget is a SaaS company, total SaaS revenue according to Zylo.com was $317.55 billion… Mudget is &lt;em&gt;not&lt;/em&gt; trying to replace every SaaS product in the world).&lt;/p&gt;

&lt;p&gt;Instead, TAM is beautifully simple math: &lt;strong&gt;Total # of potential customers × Annual revenue per customer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This calculation can be all you need for a pitch deck, and it is important to show your work! Why did you choose the price of your product, what is the most detailed version of your customer you are actually targeting? There can be multiple customers too (Mudget is not just a client driven app, but also works with financial advisors to help them provide services beyond retirement focuses to their clients).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serviceable Addressable Market&lt;/strong&gt; is TAM’s more realistic cousin who went to business school and actually reads the fine print. SAM represents the slice of your TAM that genuinely aligns with what your product does and who you can realistically reach.&lt;/p&gt;

&lt;p&gt;For Mudget, this might mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial advisory firms with less than $100M in assets under management&lt;/li&gt;
&lt;li&gt;Households with 2+ people in specific geographic markets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of SAM as TAM after it’s had a reality check and a strong cup of coffee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serviceable Obtainable Market&lt;/strong&gt; is where the rubber meets the road. This is your honest-to-goodness estimation of what chunk of SAM you can realistically capture over the next 3–4 years.&lt;/p&gt;

&lt;p&gt;SOM should grow annually as you prove you’re not just another startup burning through investor money on ping pong tables and artisanal coffee. It’s your SAM capture rate from 2025–2028, assuming you execute flawlessly and the market doesn’t decide to completely pivot to something involving AI and blockchain (again).&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 1 — Idea Auditing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z17gc4q63wc7x9ssvyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z17gc4q63wc7x9ssvyv.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, you think you have a great idea…&lt;/p&gt;

&lt;p&gt;The Idea Audit phase is a test to really see the feasibility and potential in your argument. Maybe you are assuming that this is a problem, when in reality it is not.&lt;/p&gt;

&lt;p&gt;As a founder, you need to be open to pivoting your idea, because the road to success is never a straight line. The founders of AirBnB started off building something completely different before they came to the realization that matchmaking an air mattress provider to a traveler is a viable product idea people will pay for!&lt;/p&gt;

&lt;p&gt;This is a great example of questions for an &lt;strong&gt;idea audit&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the &lt;em&gt;specific&lt;/em&gt; problem you are solving? Who experiences this problem most intensely?&lt;/li&gt;
&lt;li&gt;How exactly do you solve this problem? Why is your solution better than the ones that exist? If there is not another solution, why might that be?&lt;/li&gt;
&lt;li&gt;Who is your ideal customer? Create a specific person and describe them in as much detail as possible. What motivates them to act now?&lt;/li&gt;
&lt;li&gt;What is the estimated size of your market? What percentage can you realistically capture in 3 years?&lt;/li&gt;
&lt;li&gt;Why now and why &lt;em&gt;you&lt;/em&gt;? What recent trend, technology, regulation, or event makes this the right time? What do your customers use or do today instead of what you are offering?&lt;/li&gt;
&lt;li&gt;Have you spoken to at least 10 customers? Summarize the insights. What are the most surprising insights negative and positive feedback? What have people paid for that indicates demand for this product?&lt;/li&gt;
&lt;li&gt;How will you make money? What do your customers need to believe in order to pay you money?&lt;/li&gt;
&lt;li&gt;Why hasn’t someone solved this at scale? Why might this fail? Do you have a plan B?&lt;/li&gt;
&lt;li&gt;Which part of your solution are you most attached to, but your customer could care less about? Are you solving a problem that needs to be fixed, or would be nice to fix? What is your biggest assumption that could kill your idea if you are wrong?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Week 2 — Customer Discovery &amp;amp; Market Validation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feblw57oyin1andmosjvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feblw57oyin1andmosjvc.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s talk &lt;strong&gt;Ideal Customer Profiles (ICP)&lt;/strong&gt;! ICPs are so important, because in order find &lt;strong&gt;product market fit (PMF)&lt;/strong&gt;, the goal of any founder, you need to know your audience.&lt;/p&gt;

&lt;p&gt;Getting out and talking to potential customers to validate your assumptions of what problem you are solving reassures you that you are building the right company, builds your waitlist, and creates the ICP you can market.&lt;/p&gt;

&lt;p&gt;It is important to have some questions that are similar to measure the qualitative results of your conversations, like gathering willingness to pay, competitors you may not know of, and how this problem effects others. You want to find the intersection of segment has an urgent + important problem, they have the budget for your solution, and it is underserved.&lt;/p&gt;

&lt;p&gt;Find the 1 or 2 things that help you break through the market noise if you are in a competitive landscape. Mudget has many competitors if you assess it at a high level, but when you really look at our focus, financial planning and goal setting, it narrows the competitive landscape.&lt;/p&gt;

&lt;p&gt;Your ICP should cover most if not all of these areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demographics &lt;/strong&gt; — age range, gender, education, location, income&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Psychographics&lt;/strong&gt;  — motivations, lifestyle, values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firmographics&lt;/strong&gt;  — company size, industry, revenue, purchase behaviors, growth, clientele&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral Traits&lt;/strong&gt;  — openness to technology, purchase habits, brand engagement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’ve got your ICP locked down, it’s time to bring them to life. Create a buyer persona — give them a name, a face, maybe even a backstory about their coffee preferences and weekend habits. This isn’t creative writing class; it’s strategic empathy that’ll guide everything from product features to marketing copy.&lt;/p&gt;

&lt;p&gt;This persona becomes the foundation for your conversion funnels, your marketing strategy, and those 2 AM decisions about which feature to build next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 3 — MVP &amp;amp; IP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feipdd3137bs1pxuo3ztg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feipdd3137bs1pxuo3ztg.png" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s talk about the &lt;strong&gt;Minimum Viable Product (MVP)&lt;/strong&gt; — and no, that doesn’t stand for “Most Valuable Player” or “My Very Perfect product.” Shout out to Michael Seibel for consistently preaching this gospel to founders who desperately need to hear it.&lt;/p&gt;

&lt;p&gt;Here’s the uncomfortable truth that makes every perfectionist founder break out in hives: &lt;strong&gt;You need to get something — anything — out there. Yesterday.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As an engineer, I get it. The urge to architect the “ideal” solution is stronger than the urge to check your phone after hearing a notification sound. Every fiber of your being screams, “But what if we just add one more feature? What if we optimize this algorithm? What if we rebuild the entire backend in a different framework that’s 0.3% more efficient?”&lt;/p&gt;

&lt;p&gt;Stop. Just stop.&lt;/p&gt;

&lt;p&gt;That perfectionist voice isn’t your friend — it’s the startup equivalent of that person who spends two hours choosing the perfect Netflix show and ends up falling asleep before watching anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airbnb’s first iteration&lt;/strong&gt; : Two air mattresses on the floor and a basic website that looked like it was designed by someone who’d just discovered HTML. Today? A hospitality empire that’s redefined how the world travels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Twitch (originally Justin.tv)&lt;/strong&gt;: Started as one guy broadcasting his entire life 24/7. The interface was clunky, the concept was weird, and most people thought it was just elaborate narcissism. Fast forward to today, and it’s the kingdom of gaming content worth billions.&lt;/p&gt;

&lt;p&gt;The lesson? Your MVP is supposed to be the awkward teenage phase of your product — braces, voice cracks, questionable fashion choices and all. The beauty isn’t in the perfection; it’s in the potential.&lt;/p&gt;

&lt;p&gt;Here’s where things get interesting: Once you put something — anything — in users’ hands, the real learning begins. Suddenly, you’re not guessing what people want; you’re &lt;em&gt;watching&lt;/em&gt; what they actually do.&lt;/p&gt;

&lt;p&gt;This is when the fun engineering stuff kicks in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt; your assumptions into submission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage analytics&lt;/strong&gt; that tell you stories your qualitative research only hinted at&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral patterns&lt;/strong&gt; that reveal what users actually care about (spoiler: it’s rarely what you think)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combine these quantitative insights with your earlier qualitative research, and boom — you’ve got a crystal ball for predicting which features will actually solve the problem you set out to tackle.&lt;/p&gt;

&lt;p&gt;Now, let’s briefly touch on &lt;strong&gt;Intellectual Property (IP)&lt;/strong&gt; — because nothing kills a startup faster than accidentally stepping on someone else’s patent landmine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Patent Reality Check&lt;/strong&gt; : Unless you’re training your own AI models, building revolutionary hardware, or inventing cold fusion in your garage, you probably don’t need patents. But you &lt;em&gt;do&lt;/em&gt; need to make sure you’re not accidentally recreating someone else’s patented invention. The good news? All this information is publicly available on the USPTO website (prepare for some light bedtime reading that’s only slightly more exciting than tax law).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trademark Tales&lt;/strong&gt; : Sure, trademarking your logo and company name can be helpful — just don’t hold your breath waiting for approval. The trademark process moves at the speed of government bureaucracy, which is somewhere between “glacial” and “geological.” Don’t be surprised if your startup gets acquired before your trademark paperwork clears. It happens more often than you’d think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade Secrets: The Coca-Cola Strategy&lt;/strong&gt; : More often than not, your competitive advantage won’t be something you patent — it’ll be something you keep locked away tighter than Fort Knox. Think Coca-Cola’s recipe, Google’s search algorithm, or your grandmother’s cookie formula. Enter &lt;strong&gt;Non-Disclosure Agreements (NDAs)&lt;/strong&gt;: your legal shield against loose lips sinking startup ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 4 — Networking
&lt;/h3&gt;

&lt;p&gt;Time to meet the four networking archetypes that every founder embodies at different times — think of it as your personality’s startup Swiss Army knife.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡The Observer&lt;/strong&gt; is the founder who treats networking events like an anthropologist studying a fascinating tribe. They’re the ones nursing the same drink all evening, positioned strategically near the snack table, absorbing every conversation within a 10-foot radius.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Their superpower&lt;/strong&gt; : They &lt;em&gt;actually listen&lt;/em&gt;. While everyone else is waiting for their turn to talk about their revolutionary blockchain-AI-crypto-whatever solution, The Observer is collecting intelligence like a startup CIA agent. They spot patterns, identify real problems, and understand market dynamics that louder personalities miss entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this works&lt;/strong&gt; : During customer discovery, competitive analysis, or when you need to understand the room before making your move. Sometimes the best networking happens when you’re not actively networking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡The Reactor&lt;/strong&gt; is the startup equivalent of that friend who’s always pleasant but never calls first. They’ll engage brilliantly when invited into conversations, contribute thoughtful insights when asked, but won’t bulldoze their way into the center of attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Their superpower&lt;/strong&gt; : They’re &lt;em&gt;genuinely responsive&lt;/em&gt; to what others need. Instead of leading with their pitch, they lead with curiosity about others’ challenges. This makes people feel heard, which is rarer than unicorns in the startup world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this works&lt;/strong&gt; : In partnership discussions, investor meetings where you need to demonstrate listening skills, or when building relationships that require trust before transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡The Initiator&lt;/strong&gt; walks into networking events like they’re hosting their own birthday party. They’re genuinely excited to meet new people, and that enthusiasm is more contagious than yawning in a board meeting. They’re the ones who’ll introduce themselves to the keynote speaker and somehow end up getting invited to coffee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Their superpower&lt;/strong&gt; : They create energy and momentum. In a world full of people staring at their phones, The Initiator’s genuine interest in others stands out like a neon sign. They’re natural community builders and relationship catalysts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this works&lt;/strong&gt; : At conferences, industry events, when building brand awareness, or when you need to create buzz around your startup. They’re the founders who get featured in “30 Under 30” lists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡The Director&lt;/strong&gt; approaches networking like they’re conducting a symphony orchestra. They don’t just meet people — they &lt;em&gt;strategically connect&lt;/em&gt; people. They’re the ones thinking three moves ahead, understanding how introducing Person A to Person B could benefit everyone, including their startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Their superpower&lt;/strong&gt; : They become &lt;em&gt;indispensable connectors&lt;/em&gt;. When you’re known as the person who knows people (and knows how to connect them meaningfully), you become the center of a valuable network web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this works&lt;/strong&gt; : When building strategic partnerships, creating industry influence, or when you need to position yourself as a thought leader. They’re the founders who end up on advisory boards and speaking at conferences.&lt;/p&gt;

&lt;p&gt;Here’s the beautiful truth that personality tests don’t tell you: &lt;strong&gt;You’re not condemned to one archetype forever.&lt;/strong&gt; You’re more like a startup shapeshifter, adapting your approach based on what the situation demands.&lt;/p&gt;

&lt;p&gt;Building a deep-tech product that requires months of heads-down development? Channel your inner Observer and focus on building rather than schmoozing. Launching a consumer app that needs rapid user acquisition? Time to unleash your Initiator energy and get out there spreading the word.&lt;/p&gt;

&lt;p&gt;The smartest founders recognize that different phases of their startup journey call for different networking energies. The goal isn’t to become someone you’re not — it’s to understand your natural strengths and deploy them strategically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 5 — Money, With a “Capital” M
&lt;/h3&gt;

&lt;p&gt;In partnership with: &lt;a href="https://grantx.com/" rel="noopener noreferrer"&gt;https://grantx.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s talk about everyone’s favorite startup topic: &lt;strong&gt;money&lt;/strong&gt;. Specifically, how to get it, when to get it, and why the fundraising process will simultaneously be the best and worst experience of your entrepreneurial life.&lt;/p&gt;

&lt;p&gt;Here’s the uncomfortable truth: &lt;strong&gt;Capital isn’t just important for startups — it’s oxygen.&lt;/strong&gt; And just like oxygen, you don’t think about it until you don’t have enough of it. Understanding how to raise funds and &lt;em&gt;when&lt;/em&gt; to raise them isn’t just a nice-to-have skill — it’s a founder survival requirement.&lt;/p&gt;

&lt;h4&gt;
  
  
  Crowdfunding: Democracy Meets Capitalism
&lt;/h4&gt;

&lt;p&gt;The modern equivalent of passing around a hat, except the hat is on the internet and potentially millions of people can throw money into it. &lt;strong&gt;Perfect for&lt;/strong&gt; : Consumer products with obvious appeal, social causes, or anything that photographs well on Kickstarter.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bootstrapping: The Ramen Noodle Route
&lt;/h4&gt;

&lt;p&gt;Also known as “funding your startup with your own blood, sweat, tears, and credit cards.” This is the ultimate founder flex — building something from nothing with sheer willpower and questionable financial decisions. &lt;strong&gt;Perfect for&lt;/strong&gt; : Service businesses, software with low upfront costs, or founders who enjoy the thrill of checking their bank balance every morning.&lt;/p&gt;

&lt;h4&gt;
  
  
  Friends and Family: Mixing Money with Relationships (What Could Go Wrong?)
&lt;/h4&gt;

&lt;p&gt;Your first taste of “professional” fundraising, where “professional” means texting your college roommate at 2 AM asking if they want to invest in your revolutionary dog-walking app. &lt;strong&gt;Perfect for&lt;/strong&gt; : Early validation and small amounts of capital. &lt;strong&gt;Reality check&lt;/strong&gt; : Thanksgiving dinner conversations will never be the same if your startup fails.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tax Credits: The Government’s Way of Saying “We Believe in You”
&lt;/h4&gt;

&lt;p&gt;Free money from Uncle Sam for doing things like R&amp;amp;D, hiring in specific locations, or developing green technology. It’s like finding a $20 bill in your old jeans, except the bill is potentially worth thousands and requires paperwork that would make an accountant weep. &lt;strong&gt;Perfect for&lt;/strong&gt; : Tech companies, manufacturing, or anything that helps politicians look good in press releases.&lt;/p&gt;

&lt;h4&gt;
  
  
  Grants: The Unicorn of Funding
&lt;/h4&gt;

&lt;p&gt;Non-dilutive funding that doesn’t require giving up equity? It sounds too good to be true because it often is. &lt;strong&gt;Perfect for&lt;/strong&gt; : Research-heavy startups, social impact companies, or founders who enjoy writing applications that read like PhD dissertations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Angel Investors: Your Startup’s Guardian Angels (With Spreadsheets)
&lt;/h4&gt;

&lt;p&gt;Wealthy individuals who write checks to startups because they either A) want to help entrepreneurs, B) want to make money, or C) both. &lt;strong&gt;Perfect for&lt;/strong&gt; : Early-stage companies that need both money and mentorship.&lt;/p&gt;

&lt;h4&gt;
  
  
  Venture Capital: The Big Leagues (Where Dreams Go to Scale or Die)
&lt;/h4&gt;

&lt;p&gt;Professional investors who manage other people’s money and are legally obligated to try to turn your $1 million investment into $100 million. They have fancy offices, complicated term sheets, and an uncanny ability to ask questions that make you question everything about your business model. &lt;strong&gt;Perfect for&lt;/strong&gt; : Companies with massive scalability potential.&lt;/p&gt;

&lt;p&gt;Rounds and Meanings:&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-Seed: The “Prove You’re Not Completely Insane” Round
&lt;/h4&gt;

&lt;p&gt;This is where you convince people (usually friends, family, and optimistic angels) to give you money based on little more than your PowerPoint presentation and infectious enthusiasm. &lt;strong&gt;Typical raise&lt;/strong&gt; : $100K-$1M (median valuation: $5.7M). &lt;strong&gt;What you need&lt;/strong&gt; : An MVP, founding team, and early signs of traction. In 2024’s tougher fundraising environment, even pre-seed startups often need some revenue. &lt;strong&gt;What you’re proving&lt;/strong&gt; : That your idea isn’t completely bonkers and you can execute at least the basics.&lt;/p&gt;

&lt;h4&gt;
  
  
  Seed: The “We Have Something Real” Round
&lt;/h4&gt;

&lt;p&gt;You’ve got a functional product, some users, and ideally a bit of revenue. Now you’re asking for real money from real investors to prove this thing can actually scale. &lt;strong&gt;Typical raise&lt;/strong&gt; : $500K-$5M (average: $3.3–3.5M, median valuation: $12M). &lt;strong&gt;What you need&lt;/strong&gt; : A fully functional product (beyond mockups and Figma files), market validation signals, and quantified value propositions. &lt;strong&gt;What you’re proving&lt;/strong&gt; : Product-market fit and that you can build a sustainable business model.&lt;/p&gt;

&lt;h4&gt;
  
  
  Series A: The “Time to Scale or Fail Spectacularly” Round
&lt;/h4&gt;

&lt;p&gt;You’ve proven your business model works on a small scale. Now VCs are writing big checks for you to prove it works on a big scale. The pressure is officially on. &lt;strong&gt;Typical raise&lt;/strong&gt; : $2M-$15M (median deal size dropped to $5M in 2024, though some sources report averages as high as $18.7M). &lt;strong&gt;What you need&lt;/strong&gt; : Viable product and business model, evidence of traction, and clear growth potential. Investors expect you to use this money to increase revenue substantially. &lt;strong&gt;What you’re proving&lt;/strong&gt; : You can scale efficiently and capture significant market share.&lt;/p&gt;

&lt;h4&gt;
  
  
  Series B, C, D…: The “Unicorn or Bust” Rounds
&lt;/h4&gt;

&lt;p&gt;Each subsequent round is about proving you can scale bigger, faster, and more efficiently. The stakes get higher, the valuations get bigger, and the expectations become increasingly unrealistic. &lt;strong&gt;Typical raises&lt;/strong&gt; : Series B averages $10M, Series C and beyond can range from $15M-$100M+. &lt;strong&gt;What you need&lt;/strong&gt; : Demonstrated product-market fit, understanding of customer acquisition costs and lifetime value, and proven ability to scale operations. &lt;strong&gt;What you’re proving&lt;/strong&gt; : You’re either the next big thing or really good at convincing people you are.&lt;/p&gt;

&lt;h4&gt;
  
  
  Initial Public Offering (IPO): The “We Made It (Maybe)” Moment
&lt;/h4&gt;

&lt;p&gt;The mythical exit where you ring the bell at the stock exchange and pretend you’re not terrified of quarterly earnings calls for the rest of your career. &lt;strong&gt;What you’re proving&lt;/strong&gt; : That public market investors are willing to bet their retirement funds on your continued success.&lt;/p&gt;

&lt;p&gt;Here’s what they don’t tell you in “How to Raise Venture Capital” blog posts: &lt;strong&gt;When you raise is often more important than how much you raise.&lt;/strong&gt; Raise too early, and you’ll give away too much equity for too little money. Raise too late, and you might run out of runway before reaching your next milestone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 6 — Financial Planning &amp;amp; Analysis
&lt;/h3&gt;

&lt;p&gt;In partnership with: &lt;a href="https://www.forecastr.co/" rel="noopener noreferrer"&gt;https://www.forecastr.co/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a truth that’ll make technical founders break out in a cold sweat: &lt;strong&gt;Smart founders know how to tell a compelling financial story, forecast with confidence, and stay capital-efficient while building toward scale.&lt;/strong&gt; And before you roll your eyes and mumble something about hiring a CFO later, consider this: Financial Planning &amp;amp; Analysis (FP&amp;amp;A) isn’t just accounting theater — it’s your startup’s crystal ball, health monitor, and investor translator all rolled into one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investors don’t just invest in great products — they invest in great businesses.&lt;/strong&gt; And great businesses are defined by their numbers, not just their features. Your FP&amp;amp;A tells the story of whether your startup is a rocket ship headed for the moon or a very expensive hobby.&lt;/p&gt;

&lt;p&gt;More importantly, solid financial analysis helps you answer the existential startup questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is your business model actually working?&lt;/li&gt;
&lt;li&gt;Do you need funding, or do you just &lt;em&gt;want&lt;/em&gt; funding?&lt;/li&gt;
&lt;li&gt;What happens to your runway if growth slows by 20%?&lt;/li&gt;
&lt;li&gt;Which marketing channels are actually profitable?&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Key Performance Indicators (KPIs):
&lt;/h4&gt;

&lt;h4&gt;
  
  
  The Customer Economics Trinity
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Customer Acquisition Cost (CAC)&lt;/strong&gt;: How much you spend to convince someone to become a customer. If you’re spending $500 to acquire customers who only pay you $50, you’re basically running a very expensive charity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifetime Value (LTV)&lt;/strong&gt;: The total revenue a customer generates over their entire relationship with your company. This is your customer’s “net worth” to your business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LTV:CAC Ratio&lt;/strong&gt; : The holy grail metric. You want this ratio to be at least 3:1 (ideally higher). If it’s lower, you’re basically paying people to use your product, which is unsustainable unless you’re subsidized by venture capital or have very patient investors.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Revenue Rhythm Section
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Monthly Recurring Revenue (MRR)&lt;/strong&gt;: Your predictable monthly income. For SaaS companies, this is like your startup’s heartbeat — steady and consistent means healthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual Recurring Revenue (ARR)&lt;/strong&gt;: MRR × 12, but also your ticket to serious investor conversations. ARR is the metric that makes VCs pay attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activation Rate&lt;/strong&gt; : The percentage of users who actually experience your product’s core value. A low activation rate means your onboarding process needs CPR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lead-to-Customer Conversion Rate&lt;/strong&gt; : How effectively your sales process turns prospects into paying customers. Low conversion rates often indicate product-market fit issues or pricing problems.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Survival Metrics
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Burn Rate&lt;/strong&gt; : How fast you’re spending money. Think of this as your startup’s metabolic rate — too high, and you’ll run out of energy (cash) before reaching your goals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cash Runway&lt;/strong&gt; : How long your current cash will last at your current burn rate. This is your startup’s expiration date if nothing changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Churn Rate&lt;/strong&gt; : The percentage of customers who stop paying you each month. High churn is like having a leaky bucket — no matter how fast you pour water (acquire customers) in, it all flows out the bottom.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Profitability Picture
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Gross Profit Margin&lt;/strong&gt; : Revenue minus the direct costs of delivering your product or service. This tells you if your core business model is fundamentally profitable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Net Profit Margin&lt;/strong&gt; : What’s left after all expenses. Positive net margins mean you’re actually making money, not just moving it around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return on Equity (ROE)&lt;/strong&gt;: How efficiently you’re using investor money to generate profits. Investors love seeing high ROE because it means their investment is working hard.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Customer Happiness Index
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Net Promoter Score (NPS)&lt;/strong&gt;: How likely customers are to recommend you. High NPS often correlates with low churn and high LTV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer Satisfaction (CSAT)&lt;/strong&gt;: Direct feedback on customer happiness. Unhappy customers become churned customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer Effort Score (CES)&lt;/strong&gt;: How easy it is for customers to get value from your product. Low effort = happy customers = lower churn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to Value (TTV)&lt;/strong&gt;: How quickly new customers experience your product’s core benefit. Faster TTV typically means better activation and lower churn.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Financial Health Check
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Debt-to-Equity Ratio&lt;/strong&gt; : How much debt you have compared to equity investment. Too much debt can strangle a growing startup, while the right amount can accelerate growth.&lt;/p&gt;

&lt;p&gt;Here’s where FP&amp;amp;A gets exciting (yes, really): &lt;strong&gt;These metrics don’t exist in isolation — they tell a story about your business.&lt;/strong&gt; A rising CAC paired with flat LTV tells the story of increasing competition or market saturation. Growing MRR with high churn suggests you’re good at acquiring customers but terrible at keeping them happy. The real magic happens when you start using these metrics for forecasting. What happens to your runway if you grow 50% faster than planned? What if a key marketing channel stops working? How does a 20% price increase affect your churn rate?&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 7 — GTM
&lt;/h3&gt;

&lt;p&gt;This is where you need both a &lt;strong&gt;Go-to-Market (GTM) Strategy&lt;/strong&gt; and a &lt;strong&gt;Go-to-Market Plan&lt;/strong&gt;. And no, they’re not the same thing, despite what every startup blog seems to think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your GTM Strategy&lt;/strong&gt; is your overarching vision and goals — the “why” and “what” of your market approach. The strategy gives you direction. It answers questions like: Who are we targeting? What’s our unique positioning? How do we differentiate from competitors? What’s our value proposition?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your GTM Plan&lt;/strong&gt; is your roadmap and execution tactics — the “how” and “when” of making your strategy reality. The plan turns that direction into motion. It includes specific channels, timelines, budgets, and measurable actions.&lt;/p&gt;

&lt;p&gt;Think of it this way: Strategy = Increase visibility among small business owners through content and partnerships. Plan = Launch a weekly blog, run a co-branded webinar series, and test LinkedIn ads targeting founders.&lt;/p&gt;

&lt;p&gt;The strategy focuses on goals and vision, whereas the plan focuses on execution and tactics.&lt;/p&gt;

&lt;h4&gt;
  
  
  Your GTM Strategy: The North Star for Market Domination
&lt;/h4&gt;

&lt;p&gt;A go-to-market strategy is a comprehensive plan that outlines how to launch a new product or expand into a new market, ensuring you launch to the right audience, with the right messaging, at the right time.&lt;/p&gt;

&lt;p&gt;Your GTM strategy should be research-backed and calculable. This isn’t a “trust your gut” moment — it’s a “prove your assumptions with data” moment. A well-crafted GTM strategy should identify a market problem and position the product as a solution, backed by evidence that your execution approach is not just viable, but profitable.&lt;/p&gt;

&lt;p&gt;Key strategic elements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Market positioning&lt;/strong&gt; : How you’ll differentiate in a crowded marketplace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target customer definition&lt;/strong&gt; : Your refined ICP (remember that work from Week 3?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value proposition&lt;/strong&gt; : The compelling reason customers should choose you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive advantage&lt;/strong&gt; : Your 1–2 secret weapons for breaking through market noise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel strategy&lt;/strong&gt; : Which routes to market align with your customer behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Your GTM Plan: Strategy Meets Reality
&lt;/h4&gt;

&lt;p&gt;While the marketing strategy lays the groundwork, the marketing plan follows a sequential path in its execution. Your plan translates strategic direction into specific, measurable actions with timelines and budgets attached.&lt;/p&gt;

&lt;p&gt;This is where you get tactical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Launch sequence&lt;/strong&gt; : What happens first, second, third?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel activation&lt;/strong&gt; : Specific platforms, partnerships, and distribution methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content calendar&lt;/strong&gt; : What you’ll create and when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget allocation&lt;/strong&gt; : How much you’ll spend where&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success metrics&lt;/strong&gt; : KPIs that prove your strategy is working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline&lt;/strong&gt; : Milestones and deadlines that keep execution on track&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, this has been a lot of information compressed into a short time. But here’s why it matters: &lt;strong&gt;The startups that systematically work through these elements — from TAM calculations to KPI tracking to GTM execution — are the ones that turn promising products into sustainable businesses.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shout Out to Mudget!
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Keep this awesome support going and sign up for seats on the waitlist while you still can! We are building the all-in-one application to help you feel in control of your finances and achieve those big goals you have.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.mudget.finance/waitlist" rel="noopener noreferrer"&gt;Mudget&lt;/a&gt;&lt;/p&gt;

</description>
      <category>business</category>
      <category>marketing</category>
      <category>apps</category>
      <category>startup</category>
    </item>
    <item>
      <title>Flipping Out: Feature Flags Without Frustration</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Fri, 23 May 2025 15:03:20 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/flipping-out-feature-flags-without-frustration-3pl7</link>
      <guid>https://forem.com/joshtheburgess/flipping-out-feature-flags-without-frustration-3pl7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqzpcw0852ulials7364.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqzpcw0852ulials7364.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Go Feature Flags with OpenFeature SDK in Kubernetes
&lt;/h4&gt;

&lt;p&gt;Ever wonder why you get access to a shiny new feature in an app before your best friend does? Is it because you’re cooler? More influential? Smarter? Well… maybe. But more likely, the devs are running a &lt;strong&gt;feature flag&lt;/strong&gt; experiment — and congrats, you’re in the test group.&lt;/p&gt;

&lt;p&gt;This magical control over what users see, when they see it, and how it behaves is often called &lt;strong&gt;feature toggling&lt;/strong&gt; , but let’s be real: “feature flags” just sounds cooler.&lt;/p&gt;

&lt;p&gt;And now, I have a confession to make — and please don’t stop reading after this:&lt;br&gt;&lt;br&gt;
 I think feature flags are &lt;em&gt;cool&lt;/em&gt;. Like, &lt;strong&gt;sunglasses-emoji cool&lt;/strong&gt;. 😎&lt;/p&gt;

&lt;p&gt;But not everyone agrees. Some developers hear “feature flags” and immediately break into cold sweats. I get it. You’ve probably seen flags that were never cleaned up, led to duplicated logic, or straight-up broke production in the weirdest ways. Feature flag trauma is real. 💀&lt;/p&gt;

&lt;p&gt;Fear not. Today, we’re diving into &lt;strong&gt;OpenFeature&lt;/strong&gt; and &lt;strong&gt;Go Feature Flags&lt;/strong&gt; (GoFF) — tools that’ll help you wield feature flags responsibly and maybe even have a little fun doing it.&lt;/p&gt;

&lt;p&gt;I can tell you were starting to sweat with the size of this scroll bar. The bottom half of this post is an overkill of a tutorial where you will use both OpenFeature and GoFF with Mongo, Kafka, and Pinot to show how cool feature flags can be.&lt;/p&gt;
&lt;h3&gt;
  
  
  🚩What is a Feature Flag?
&lt;/h3&gt;

&lt;p&gt;At its core, a feature flag is just a conditional switch in your app. Think:&lt;br&gt;&lt;br&gt;
 if flag_is_on { show new thing } else { show old thing }&lt;/p&gt;

&lt;p&gt;Sounds basic, right? But here’s the twist: flags can do &lt;em&gt;way&lt;/em&gt; more than just flip features on or off. You can roll things out to users based on roles (hello QA team 👋), geography, app version, or even the phase of the moon (not recommended, but hey, you do you).&lt;/p&gt;

&lt;p&gt;Usually, flags are evaluated by looking at request headers or context-specific data — things like user ID and account type.&lt;/p&gt;
&lt;h3&gt;
  
  
  🧰 Enter OpenFeature: The Great Unifier
&lt;/h3&gt;

&lt;p&gt;There are tons of awesome feature flag providers out there — LaunchDarkly, PostHog, ConfigCat, GitLab, Vercel, the list goes on.&lt;/p&gt;

&lt;p&gt;The problem? Each one comes with their own SDK, their own way of working, and their own learning curve. Switching providers can feel like changing banks — unnecessarily painful.&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href="https://openfeature.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenFeature&lt;/strong&gt;&lt;/a&gt; comes in. It’s like the universal remote for your feature flags. It gives you a consistent API, no matter what provider you’re using. Think of it as the Switzerland of feature flagging — neutral, helpful, and unexpectedly powerful.&lt;/p&gt;

&lt;p&gt;Let’s look at how cleanly you can toggle between providers using OpenFeature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
 "fmt"
 "context"

 "github.com/dhaus67/openfeature-posthog-go"
 "github.com/open-feature/go-sdk/openfeature"
 "github.com/posthog/posthog-go"
)

func main() {
 // Start by creating a PostHog client with your desired configuration.
 client, err := posthog.NewWithConifg("&amp;lt;your api key&amp;gt;", posthog.Config{})
 if err != nil {
  panic(err)
 }

 // Create the provider and register it.
 openfeature.SetProvider(openfeatureposthog.NewProvider(client))

 client := openfeature.NewClient("my-client")

 // The targeting key is required with this provider. It is used to evaluate with PostHog
 // whether for the specific user the feature is enabled or not.
 evalCtx := openfeature.NewEvaluationContext("&amp;lt;distinct-user-id&amp;gt;", map[string]interface{}{})

 secretFeature, err := client.BooleanValue(context.Background(), "secret", false, evalCtx)
 if err != nil {
  panic(err)
 }

 if secretFeature {
  fmt.Println("Secret feature is enabled")
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you wanted to switch to ConfigCat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
 "fmt"
 "context"

  sdk "github.com/configcat/go-sdk/v9"
  configcat "github.com/open-feature/go-sdk-contrib/providers/configcat/pkg"
 "github.com/open-feature/go-sdk/openfeature"

)

func main() {
 // Start by creating a ConfigCat client with your desired configuration.
 provider := configcat.NewProvider(sdk.NewClient("..."))
 openfeature.SetProvider(provider)

 client := openfeature.NewClient("my-client")

 // The targeting key is required with this provider. It is used to evaluate with PostHog
 // whether for the specific user the feature is enabled or not.
 evalCtx := openfeature.NewEvaluationContext("&amp;lt;distinct-user-id&amp;gt;", map[string]interface{}{})

 secretFeature, err := client.BooleanValue(context.Background(), "secret", false, evalCtx)
 if err != nil {
  panic(err)
 }

 if secretFeature {
  fmt.Println("Secret feature is enabled")
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how nothing about your flag logic had to change? That’s the OpenFeature magic. Your business logic stays the same even if your boss decides to save money by switching providers mid-quarter. 😅&lt;/p&gt;

&lt;p&gt;Oh — and you’re not locked into SaaS either. You can go DIY with YAML, env vars, or write your own provider if you’re feeling &lt;em&gt;extra&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why GoFF?
&lt;/h3&gt;

&lt;p&gt;If you are evaluating building your own vs a hosted company, take a look at Go Feature Flags (GoFF). It began as an OSS project specific to Golang; however, they doubled down on OpenFeature and now can provide for most of the popular languages. 💪&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8eajfx0oncza037uk8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8eajfx0oncza037uk8y.png" width="800" height="410"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://gofeatureflag.org/docs/concepts/architecture" rel="noopener noreferrer"&gt;https://gofeatureflag.org/docs/concepts/architecture&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GoFF lets you use a &lt;strong&gt;relay proxy&lt;/strong&gt; to evaluate flag states via YAML files stored pretty much anywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;Kubernetes ConfigMaps&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the cool bits don’t stop there. GoFF supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User bucketing&lt;/strong&gt; (great for A/B testing or canary rollouts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive rollouts&lt;/strong&gt; by time, percentage, or other logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics extraction&lt;/strong&gt; so you can see what’s being used and make data-driven decisions instead of vibe-based ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, &lt;strong&gt;GoFF + OpenFeature&lt;/strong&gt; gives you &lt;strong&gt;power&lt;/strong&gt; &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;flexibility&lt;/strong&gt; , without chaining you to a single vendor or requiring you to sell your soul for enterprise pricing.&lt;/p&gt;
&lt;h3&gt;
  
  
  💡Why Is This Data Useful?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“Data is the new oil.” — Clive Humby&lt;/p&gt;

&lt;p&gt;“Without big data analytics, companies are blind and deaf, wandering out onto the Web like deer on a freeway.” — Geoffrey Moore&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remember when being an “ &lt;strong&gt;expert&lt;/strong&gt; ” meant you could predict user behavior with confidence? Those were simpler times — back when you could rely on experience and intuition to guide product decisions, and stakeholders would trust your judgment completely.&lt;/p&gt;

&lt;p&gt;Well, congratulations. The internet has democratized failure, and now your customers get to vote on whether your expertise was worth the pixels it’s printed on. 🥲&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Feature Flags&lt;/strong&gt; become invaluable. They’re not just a deployment tool — they’re your &lt;strong&gt;experimentation infrastructure&lt;/strong&gt;. 💡Feature flags allow you to test hypotheses with real users, measure actual behavior, and make decisions based on evidence rather than educated guesses.&lt;/p&gt;

&lt;p&gt;Here’s why this matters: According to Microsoft, only about &lt;strong&gt;one-third&lt;/strong&gt; of ideas actually improve the metrics they’re designed to impact. That means &lt;em&gt;two-thirds of development effort could be better spent elsewhere&lt;/em&gt;. Feature flags help you identify winners before you’ve invested &lt;em&gt;months&lt;/em&gt; of development time.&lt;/p&gt;

&lt;p&gt;The beauty of feature flags lies in their flexibility. You can gradually roll out features to small user segments, measure impact, and either &lt;strong&gt;expand or rollback&lt;/strong&gt; based on &lt;strong&gt;real&lt;/strong&gt; performance data. This approach reduces risk dramatically — instead of betting the farm on a single launch, you’re making informed, incremental decisions.&lt;/p&gt;

&lt;p&gt;Because nothing — and I mean &lt;em&gt;nothing&lt;/em&gt; — hurts quite like spending three months crafting the perfect feature, complete with thoughtful animations and delightful micro-interactions, only to launch it and hear the deafening sound of… &lt;strong&gt;crickets&lt;/strong&gt;. It’s like preparing an elaborate dinner party and watching everyone order pizza instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xl8j8r29wi6abhmuku2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xl8j8r29wi6abhmuku2.gif" width="200" height="354"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Shoutout
&lt;/h3&gt;

&lt;p&gt;If you are looking for something to just easily start, &lt;strong&gt;PostHog&lt;/strong&gt; has a growing &lt;strong&gt;suite of tools&lt;/strong&gt; that let you focus on building a great product and leverage the power of gathering and processing data that can inform decisions. Using data let’s you find the mythical product market fit and excel your skills as a product focused engineer! I currently use them with my app  &lt;strong&gt;Mudget&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Join Mudget today!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mudget.finance" rel="noopener noreferrer"&gt;Mudget - Simplify Your Finances&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Tutorial
&lt;/h3&gt;

&lt;p&gt;This tutorial is going to be &lt;strong&gt;fun&lt;/strong&gt;! 🐐&lt;/p&gt;

&lt;p&gt;We are building a simple &lt;strong&gt;Go&lt;/strong&gt; application programming interface (API) to handle List Create Read Update and Delete (L’CRUD) requests for feature flags in &lt;strong&gt;MongoDB&lt;/strong&gt;. We will also pair this with a user interface (UI) written with &lt;strong&gt;Typescript&lt;/strong&gt; in the &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;&lt;strong&gt;Remix&lt;/strong&gt;&lt;/a&gt; framework. Essentially two UI pages will be available, one is a user experience with feature flags — obviously with &lt;strong&gt;OpenFeature&lt;/strong&gt;. The other is a feature flag admin page with flag key performance indicator (KPI) metrics which is collected and evaluated with the &lt;strong&gt;GoFF Relay Proxy&lt;/strong&gt;. The metrics have to be exported somewhere so we are going overkill and sending them to &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;,&lt;/strong&gt; an event stream. The admins will want a pretty UI for the metrics, so let’s consume the stream and push to the Remix app with websockets!&lt;/p&gt;

&lt;p&gt;You can reference my code &lt;a href="https://github.com/joshbrgs/medium-tutorials/tree/main/flipping-out" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Preparing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Kind (and kubetcl)&lt;/li&gt;
&lt;li&gt;Helm&lt;/li&gt;
&lt;li&gt;Golang&lt;/li&gt;
&lt;li&gt;Node (pnpm or npm with it)&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  The Kind Cluster
&lt;/h4&gt;

&lt;p&gt;First, we’ll use &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kind&lt;/strong&gt;&lt;/a&gt; — a tool for running local Kubernetes clusters inside Docker. Installing it with Go is easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install sigs.k8s.io/kind@v0.26.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, create a kind config file to make port forwarding easy (may need to port forward instead if you are using WSL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 1031
        hostPort: 1031
      - containerPort: 3001
        hostPort: 3001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a cluster like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind create cluster --name feature-analytics --config=kind-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this switches your &lt;strong&gt;kubectl context&lt;/strong&gt; to the new Kind cluster. You can verify it’s running by checking all pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pod -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  📅Deploying the Supporting Services with Helm
&lt;/h4&gt;

&lt;p&gt;We will be using &lt;strong&gt;MongoDB&lt;/strong&gt; to store the configuration of the feature flags which let’s the &lt;strong&gt;Go Feature Flag Relay&lt;/strong&gt; reference the current flag states when evaluating the flag for clients. We are using &lt;strong&gt;Kafka&lt;/strong&gt; as the event stream for our flag analytic exports.&lt;/p&gt;

&lt;p&gt;We will use this script to easily setup the helm charts and apply our values files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

# Add Helm repos
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add go-feature-flag https://charts.gofeatureflag.org/
helm repo update

# Install Mongo
helm install mongo bitnami/mongodb -f values/mongo-values.yaml

# Install Kafka
helm install kafka bitnami/kafka -f values/kafka-values.yaml

# Install GoFF Relay
helm install relay-proxy go-feature-flag/relay-proxy -f values/relay-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to ensure this shell script is executable like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mongo values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# values/mongo-values.yaml
# Authentication settings
auth:
  enabled: true
  rootPassword: password
  rootUser: root
  usernames:
    - example
  passwords:
    - flagged
  databases:
    - appConfig

# Persistent storage settings
persistence:
  enabled: true
  size: 4Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kafka values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# values/kafka-values.yaml
replicaCount: 1
auth:
  enabled: false

zookeeper:
  enabled: true

listeners:
  client:
    protocol: PLAINTEXT

advertisedListeners:
  - name: CLIENT
    address: kafka.default.svc.cluster.local
    port: 9092

service:
  type: ClusterIP

extraEnvVars:
  - name: KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE
    value: "true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relay values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# values/relay-values.yaml
relayproxy:
  # -- GO Feature Flag relay proxy configuration as string (accept template).
  # -- uri could be referenced as a secret in a production level scenario
  config: | # This is a configuration example for the relay-proxy
    listen: 1031
    pollingInterval: 1000
    startWithRetrieverError: false
    logLevel: info
    retriever:
      kind: mongodb
      uri: mongodb://root:password@mongo-mongodb:27017/
      database: appConfig
      collection: featureFlags
    exporters:
      kind: kafka
      kafka:
        topic: "go-feature-flag-events"
        addresses:
          - "kafka.default.svc.cluster.local:9092"

service:
  type: NodePort # -- Change to ClusterIP if port forwarding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And install them all with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beware that this is not production ready values, you will need to secure these deployments if you like this solution. Our new services will now be available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kafka Broker&lt;/strong&gt; (host): kafka.default.svc.cluster.local:9092&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Making a Simple(ish*) App and Deploying It
&lt;/h4&gt;

&lt;p&gt;Now my goal is to show off the cool feature flagging abilities, but it will involve a bit of follow along or just checking out my GitHub project I linked above.&lt;/p&gt;

&lt;p&gt;In an app/ directory, we will build out a Gin API with a few endpoints for our client to interact with. Let’s initialize our new app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd app
go mod init github.com/&amp;lt;username&amp;gt;/flipping-out
go install github.com/air-verse/air@latest
air init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And write our server, ideally in a non tutorial environment, you will not be hardcoding as much as I am here like the ports, username, passwords.&lt;/p&gt;

&lt;p&gt;This is the file structure I am following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Tree
├── cmd
│ └── server.go
├── Dockerfile
├── go.mod
├── go.sum
├── internal
│ ├── api
│ │ └── v1
│ │ ├── controllers.go
│ │ ├── routes.go
│ │ ├── websockets.go
│ │ └── welcomeController.go
│ ├── app
│ │ └── app.go
│ ├── kafka
│ │ └── consumer.go
│ ├── models
│ │ └── feature_flags.go
│ ├── repositories
│ │ └── repositories.go
│ └── services
│ ├── services.go
│ ├── websocket.go
│ └── welcome.go
├── main.go
└── manifests
    └── deploy.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main function just starts the server, which the main logic (establishing the clients, registering routes, and starting the Gin server) is located in the server.go file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.go
package main

import (
 "github.com/joshbrgs/flipping-out/cmd"
)

func main() {
 cmd.StartServer()
}

// cmd/server.go
package cmd

import (
 "context"
 "log"
 "time"

 "github.com/gin-contrib/cors"
 "github.com/gin-gonic/gin"
 v1 "github.com/joshbrgs/flipping-out/internal/api/v1"
 "github.com/joshbrgs/flipping-out/internal/app"
 gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg"
 of "github.com/open-feature/go-sdk/openfeature"
 "go.mongodb.org/mongo-driver/v2/mongo"
 "go.mongodb.org/mongo-driver/v2/mongo/options"
)

func StartServer() {
 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 defer cancel()

 // Creation of the GO Feature Flag provider to our relay proxy in kind
 provider, err := gofeatureflag.NewProvider(
  gofeatureflag.ProviderOptions{
   Endpoint: "http://go-feature-flag-relay-proxy:1031",
  })
 if err != nil {
  log.Fatalf("Failed to connect to Relay: %v", err)
 }

 // Setting the provider to the OpenFeature SDK
 err = of.SetProviderAndWait(provider)
 if err != nil {
  log.Fatalf("Failed set provider: %v", err)
 }
 ofClt := of.NewClient("my-openfeature-gin-client")

 // Connect to MongoDB
 mongoClient, err := mongo.Connect(options.Client().ApplyURI("mongodb://root:password@mongo-mongodb:27017"))
 if err != nil {
  log.Fatalf("Failed to connect to MongoDB: %v", err)
 }
 defer func() {
  if err := mongoClient.Disconnect(ctx); err != nil {
   panic(err)
  }
 }()

 // bootstrap services
 container := app.NewContainer(mongoClient, ofClt)

 // start application
 r := gin.Default()

 // CORS configuration
 r.Use(cors.New(cors.Config{
  AllowOrigins: []string{"http://localhost:5173"},
  AllowMethods: []string{"GET", "POST", "PATCH", "OPTIONS"},
  AllowHeaders: []string{"Origin", "Content-Type"},
  ExposeHeaders: []string{"Content-Length"},
  AllowCredentials: true,
  MaxAge: 12 * time.Hour,
 }))

 v1.RegisterRoutes(r, container)
 log.Println("Server started at :3001")

 r.Run(":3001")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is something I was trying out, a container that contains the dependencies for the other layers of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/app/app.go
package app

import (
 "github.com/joshbrgs/flipping-out/internal/repositories"
 "github.com/joshbrgs/flipping-out/internal/services"
 of "github.com/open-feature/go-sdk/openfeature"
 "go.mongodb.org/mongo-driver/v2/mongo"
)

type Container struct {
 MongoClient *mongo.Client
 FeatureClient *of.Client

 FeatureRepo repositories.FeatureFlagRepository
 FeatureService services.FeatureService
 WelcomeService services.WelcomeService
 WebsocketHub *services.Hub
}

func NewContainer(mongoClient *mongo.Client, featureClient *of.Client) *Container {
 repo := repositories.NewFeatureFlagRepository(mongoClient, "appConfig", "featureFlags")
 service := services.NewFeatureService(repo)
 welcomeService := services.NewWelcomeService()
 websocketHub := services.NewHub()

 return &amp;amp;Container{
  MongoClient: mongoClient,
  FeatureClient: featureClient,
  FeatureRepo: repo,
  FeatureService: service,
  WelcomeService: welcomeService,
  WebsocketHub: websocketHub,
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The routes handles registering v1 routes and handlers that are part of the controller, isolating the HTTP logic and feature flag evaluations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/api/v1/routes.go
package v1

import (
 "github.com/gin-gonic/gin"
 "github.com/joshbrgs/flipping-out/internal/app"
)

func RegisterRoutes(r *gin.Engine, c *app.Container) {
 api := r.Group("/v1")

 registerFlagRoutes(api, c)
 registerUserRoutes(api, c)
 registerWebsockets(api, c)
}

func registerFlagRoutes(r *gin.RouterGroup, c *app.Container) {
 api := r.Group("/flags")

 flagController := NewFlagController(c.FeatureService, c.FeatureClient)

 api.GET("", flagController.getFlagsHandler)
 api.GET("/:id", flagController.getFlagHandler)
 api.POST("", flagController.createFlagHandler)
 api.PATCH("/:id", flagController.updateFlagHandler)
 api.DELETE("/:id", flagController.deleteFlagHandler)
}

func registerUserRoutes(r *gin.RouterGroup, c *app.Container) {
 api := r.Group("/welcome")

 exampleController := NewWelcomeController(c.WelcomeService, c.FeatureClient)

 api.GET("", exampleController.getWelcomeHandler)
}

func registerWebsockets(r *gin.RouterGroup, c *app.Container) {
 api := r.Group("/ws")

 websocketController := NewWebsocketController(c.WebsocketHub)

 api.GET("", websocketController.HandleWebsocket)
}

// internal/api/v1/controllers.go
package v1

import (
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/joshbrgs/flipping-out/internal/models"
 "github.com/joshbrgs/flipping-out/internal/services"
 of "github.com/open-feature/go-sdk/openfeature"
)

type FlagController struct {
 service services.FeatureService
 flagClient *of.Client
}

func NewFlagController(service services.FeatureService, flagClient *of.Client) *FlagController {
 return &amp;amp;FlagController{service: service, flagClient: flagClient}
}

func (fc *FlagController) getFlagsHandler(c *gin.Context) {
 flags, err := fc.service.GetAllFlags(c.Request.Context())
 if err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch flags", "err_detailed": err.Error()})
  return
 }
 c.JSON(http.StatusOK, flags)
}

func (fc *FlagController) getFlagHandler(c *gin.Context) {
 id := c.Param("id")
 flag, err := fc.service.GetFlagByID(c.Request.Context(), id)
 if err != nil {
  c.JSON(http.StatusNotFound, gin.H{"error": "flag not found", "err_detailed": err.Error()})
  return
 }
 c.JSON(http.StatusOK, flag)
}

func (fc *FlagController) createFlagHandler(c *gin.Context) {
 var flag models.FeatureFlag
 if err := c.ShouldBindJSON(&amp;amp;flag); err != nil {
  c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
  return
 }
 if err := fc.service.CreateFlag(c.Request.Context(), flag); err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create flag", "err_detailed": err.Error()})
  return
 }
 c.JSON(http.StatusCreated, flag)
}

func (fc *FlagController) updateFlagHandler(c *gin.Context) {
 id := c.Param("id")
 var update models.FeatureFlag
 if err := c.ShouldBindJSON(&amp;amp;update); err != nil {
  c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
  return
 }
 if err := fc.service.UpdateFlag(c.Request.Context(), id, update); err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update flag", "err_detailed": err.Error()})
  return
 }
 c.JSON(http.StatusOK, gin.H{"message": "flag updated"})
}

func (fc *FlagController) deleteFlagHandler(c *gin.Context) {
 id := c.Param("id")
 if err := fc.service.DeleteFlag(c.Request.Context(), id); err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete flag", "err_detailed": err.Error()})
  return
 }
 c.JSON(http.StatusOK, gin.H{"message": "flag deleted"})
}

// internal/api/v1/welcomeController.go
package v1

import (
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/joshbrgs/flipping-out/internal/services"
 of "github.com/open-feature/go-sdk/openfeature"
)

type WelcomeController struct {
 service services.WelcomeService
 flagClient *of.Client
}

func NewWelcomeController(service services.WelcomeService, flagClient *of.Client) *WelcomeController {
 return &amp;amp;WelcomeController{service: service, flagClient: flagClient}
}

// Example of using a featureflag decoupled from the buisness logic
func (fc *WelcomeController) getWelcomeHandler(c *gin.Context) {
 welcomeMessage, _ := fc.flagClient.BooleanValue(c, "welcome-message", false, of.EvaluationContext{})
 user := c.GetHeader("x-example-header")

 if welcomeMessage {
  msg := fc.service.HelloWorld()
  c.JSON(http.StatusOK, msg)
 } else {
  msg := fc.service.HelloWorldAgain(user)
  c.JSON(http.StatusOK, msg)
 }
}

// internal/api/v1/websockets.go
package v1

import (
 "log"
 "net/http"
 "time"

 "github.com/gin-gonic/gin"
 "github.com/gorilla/websocket"
 "github.com/joshbrgs/flipping-out/internal/kafka"
 "github.com/joshbrgs/flipping-out/internal/services"
)

type WebsocketController struct {
 hub *services.Hub
}

func NewWebsocketController(hub *services.Hub) *WebsocketController {
 go hub.Run()
 go kafka.StartConsumer(hub)
 return &amp;amp;WebsocketController{hub: hub}
}

func (wc *WebsocketController) HandleWebsocket(c *gin.Context) {
 conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
 if err != nil {
  log.Printf("WebSocket upgrade error: %v", err)
  return
 }

 // Set connection limits and timeouts
 conn.SetReadLimit(512)
 conn.SetReadDeadline(time.Now().Add(60 * time.Second))
 conn.SetPongHandler(func(string) error {
  conn.SetReadDeadline(time.Now().Add(60 * time.Second))
  return nil
 })

 // Register connection
 wc.hub.Register(conn)

 ticker := time.NewTicker(30 * time.Second)
 defer ticker.Stop()

 // Handle websocket messages in goroutine
 go func() {
  defer func() {
   wc.hub.Unregister(conn)
   conn.Close()
  }()

  for {
   messageType, message, err := conn.ReadMessage()
   if err != nil {
    if websocket.IsUnexpectedCloseError(err,
     websocket.CloseGoingAway,
     websocket.CloseAbnormalClosure,
     websocket.CloseNoStatusReceived) {
     log.Printf("WebSocket unexpected close: %v", err)
    }
    break
   }

   switch messageType {
   case websocket.TextMessage:
    log.Printf("Received text message: %s", message)
   case websocket.BinaryMessage:
    log.Printf("Received binary message of length: %d", len(message))
   case websocket.CloseMessage:
    log.Println("Received close message")
    return
   }
  }
 }()

 // Ping loop
 go func() {
  defer ticker.Stop()
  for {
   select {
   case &amp;lt;-ticker.C:
    conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
    if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
     log.Printf("Ping error: %v", err)
     return
    }
   }
  }
 }()
}

var upgrader = websocket.Upgrader{
 CheckOrigin: func(r *http.Request) bool {
  return true
 },
 ReadBufferSize: 1024,
 WriteBufferSize: 1024,
 HandshakeTimeout: 45 * time.Second,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service Layer contains all the business logic and communication to other services, this services in particular does not contain much of that logic since we only have this one service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/services/services.go
package services

import (
 "context"
 "fmt"
 "log"

 "github.com/joshbrgs/flipping-out/internal/models"
 "github.com/joshbrgs/flipping-out/internal/repositories"
 "go.mongodb.org/mongo-driver/v2/bson"
)

type FeatureService interface {
 IsFeatureEnabled(ctx context.Context, name string) (bool, error)
 GetAllFlags(ctx context.Context) ([]models.FeatureFlag, error)
 GetFlagByID(ctx context.Context, id string) (*models.FeatureFlag, error)
 CreateFlag(ctx context.Context, flag models.FeatureFlag) error
 UpdateFlag(ctx context.Context, id string, update models.FeatureFlag) error
 DeleteFlag(ctx context.Context, id string) error
}

type featureService struct {
 repo repositories.FeatureFlagRepository
}

func NewFeatureService(repo repositories.FeatureFlagRepository) FeatureService {
 return &amp;amp;featureService{repo: repo}
}

func (s *featureService) IsFeatureEnabled(ctx context.Context, name string) (bool, error) {
 flags, err := s.repo.GetAll(ctx)
 if err != nil {
  return false, err
 }

 for _, flag := range flags {
  if flag.Flag == name {
   return flag.Variations.DefaultVar, nil
  }
 }

 return false, nil
}

func (s *featureService) GetAllFlags(ctx context.Context) ([]models.FeatureFlag, error) {
 return s.repo.GetAll(ctx)
}

func (s *featureService) GetFlagByID(ctx context.Context, id string) (*models.FeatureFlag, error) {
 idd, err := bson.ObjectIDFromHex(id)
 if err != nil {
  return nil, fmt.Errorf("Failed to convert ID. Err: %w", err)
 }
 return s.repo.GetByID(ctx, idd)
}

func (s *featureService) CreateFlag(ctx context.Context, flag models.FeatureFlag) error {
 return s.repo.Create(ctx, flag)
}

func (s *featureService) UpdateFlag(ctx context.Context, id string, update models.FeatureFlag) error {
 idd, err := bson.ObjectIDFromHex(id)
 if err != nil {
  return fmt.Errorf("Failed to convert ID. Err: %w", err)
 }
 model, err := s.GetFlagByID(ctx, id)
 if err != nil {
  return fmt.Errorf("Failed to get flag by ID. Err: %w", err)
 }

 log.Println(update)

 updateData := models.FeatureFlag{
  Flag: model.Flag,
  Variations: model.Variations,
  DefaultRule: update.DefaultRule,
 }

 log.Println(updateData)

 return s.repo.Update(ctx, idd, updateData)
}

func (s *featureService) DeleteFlag(ctx context.Context, id string) error {
 idd, err := bson.ObjectIDFromHex(id)
 if err != nil {
  return fmt.Errorf("Failed to convert ID. Err: %w", err)
 }
 return s.repo.Delete(ctx, idd)
}

// internal/services/welcome.go
package services

import "fmt"

type WelcomeService interface {
 HelloWorld() string
 HelloWorldAgain(user string) string
}

type welcomeService struct {
}

func NewWelcomeService() WelcomeService {
 return &amp;amp;welcomeService{}
}

func (s *welcomeService) HelloWorld() string {
 return "HelloWorld"
}

func (s *welcomeService) HelloWorldAgain(user string) string {
 return fmt.Sprintf("Hello there %s", user)
}

// internal/services/websocket.go
package services

import (
 "log"
 "sync"
 "time"

 "github.com/gorilla/websocket"
)

type FeatureEvent struct {
 Value bool `json:"value"`
}

type Hub struct {
 clients map[*websocket.Conn]bool
 register chan *websocket.Conn
 unregister chan *websocket.Conn
 broadcast chan interface{}
 mu sync.Mutex
 Done chan struct{}
 LastBroadcast time.Time
 BroadcastMu sync.Mutex
}

func NewHub() *Hub {
 return &amp;amp;Hub{
  clients: make(map[*websocket.Conn]bool),
  register: make(chan *websocket.Conn),
  unregister: make(chan *websocket.Conn),
  broadcast: make(chan interface{}),
  Done: make(chan struct{}),
 }
}

func (h *Hub) Run() {
 for {
  select {
  case conn := &amp;lt;-h.register:
   h.mu.Lock()
   h.clients[conn] = true
   h.mu.Unlock()
   log.Printf("Client connected. Total clients: %d", len(h.clients))

  case conn := &amp;lt;-h.unregister:
   h.mu.Lock()
   if _, ok := h.clients[conn]; ok {
    delete(h.clients, conn)
    conn.Close()
   }
   h.mu.Unlock()
   log.Printf("Client disconnected. Total clients: %d", len(h.clients))

  case message := &amp;lt;-h.broadcast:
   h.mu.Lock()
   clientCount := len(h.clients)
   if clientCount == 0 {
    h.mu.Unlock()
    continue
   }

   for conn := range h.clients {
    conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
    if err := conn.WriteJSON(message); err != nil {
     log.Printf("WebSocket write error: %v", err)
     conn.Close()
     delete(h.clients, conn)
    }
   }
   h.mu.Unlock()

  case &amp;lt;-h.Done:
   return
  }
 }
}

func (h *Hub) Broadcast(message interface{}) {
 select {
 case h.broadcast &amp;lt;- message:
 case &amp;lt;-time.After(5 * time.Second):
  log.Println("Broadcast timeout - hub may be blocked")
 }
}

func (h *Hub) Register(conn *websocket.Conn) {
 h.register &amp;lt;- conn
}

func (h *Hub) Unregister(conn *websocket.Conn) {
 h.unregister &amp;lt;- conn
}

func (h *Hub) Stop() {
 close(h.Done)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For supporting our services, we have a client for consuming the kafka stream too!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/kafka/consumer.go
package kafka

import (
 "context"
 "encoding/json"
 "log"
 "time"

 "github.com/joshbrgs/flipping-out/internal/services"
 "github.com/segmentio/kafka-go"
)

type FeatureEvent struct {
 Value bool `json:"value"`
}

func StartConsumer(h *services.Hub) {
 ctx := context.Background()

 reader := kafka.NewReader(kafka.ReaderConfig{
  Brokers: []string{"kafka.default.svc.cluster.local:9092"},
  Topic: "go-feature-flag-events",
  GroupID: "feature-analytics",
  MinBytes: 10e3,
  MaxBytes: 10e6,
  MaxWait: 1 * time.Second,
 })
 defer reader.Close()

 log.Println("Starting Kafka consumer...")

 for {
  select {
  case &amp;lt;-h.Done:
   log.Println("Stopping Kafka consumer...")
   return
  default:
   ctxWithTimeout, cancel := context.WithTimeout(ctx, 5*time.Second)

   m, err := reader.ReadMessage(ctxWithTimeout)
   cancel()

   if err != nil {
    log.Printf("Kafka read error: %v", err)
    time.Sleep(time.Second)
    continue
   }

   var event FeatureEvent
   if err := json.Unmarshal(m.Value, &amp;amp;event); err != nil {
    log.Printf("JSON unmarshal error: %v", err)
    continue
   }

   // Rate limit broadcasts to prevent spam
   h.BroadcastMu.Lock()
   if time.Since(h.LastBroadcast) &amp;gt; 100*time.Millisecond {
    h.LastBroadcast = time.Now()
    h.BroadcastMu.Unlock()

    log.Printf("Broadcasting event: %+v", event)
    h.Broadcast(event)
   } else {
    h.BroadcastMu.Unlock()
    // Skip this event due to rate limiting
   }
  }
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository is the data access layer. Database queries all happen with this layer so we can separate the logic from other layers, keeping this testable and evolvable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/repositories/repositories.go
package repositories

import (
 "context"
 "log"

 "github.com/joshbrgs/flipping-out/internal/models"
 "go.mongodb.org/mongo-driver/v2/bson"
 "go.mongodb.org/mongo-driver/v2/mongo"
)

type FeatureFlagRepository interface {
 GetAll(ctx context.Context) ([]models.FeatureFlag, error)
 GetByID(ctx context.Context, id bson.ObjectID) (*models.FeatureFlag, error)
 Create(ctx context.Context, flag models.FeatureFlag) error
 Update(ctx context.Context, id bson.ObjectID, update models.FeatureFlag) error
 Delete(ctx context.Context, id bson.ObjectID) error
}

type featureFlagRepository struct {
 collection *mongo.Collection
}

func NewFeatureFlagRepository(client *mongo.Client, dbName, collectionName string) FeatureFlagRepository {
 return &amp;amp;featureFlagRepository{
  collection: client.Database(dbName).Collection(collectionName),
 }
}

func (r *featureFlagRepository) GetAll(ctx context.Context) ([]models.FeatureFlag, error) {
 cursor, err := r.collection.Find(ctx, bson.M{})
 if err != nil {
  return nil, err
 }
 defer cursor.Close(ctx)

 var flags []models.FeatureFlag

 for cursor.Next(ctx) {
  var flag models.FeatureFlag

  err := cursor.Decode(&amp;amp;flag)
  if err != nil {
   // Log the raw BSON document to find the broken one
   var raw bson.M
   if rawErr := cursor.Decode(&amp;amp;raw); rawErr == nil {
    log.Printf("Failed to decode into FeatureFlag, raw: %+v", raw)
   }
   return nil, err
  }

  flags = append(flags, flag)

 }
 return flags, nil
}

func (r *featureFlagRepository) GetByID(ctx context.Context, id bson.ObjectID) (*models.FeatureFlag, error) {
 var flag models.FeatureFlag
 err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&amp;amp;flag)
 return &amp;amp;flag, err
}

func (r *featureFlagRepository) Create(ctx context.Context, flag models.FeatureFlag) error {
 _, err := r.collection.InsertOne(ctx, flag)
 return err
}

func (r *featureFlagRepository) Update(ctx context.Context, id bson.ObjectID, update models.FeatureFlag) error {
 updateData := bson.M{
  "flag": update.Flag,
  "variations": update.Variations,
  "defaultRule": update.DefaultRule,
 }

 log.Println(updateData)

 _, err := r.collection.UpdateByID(ctx, id, bson.M{"$set": updateData})
 return err
}

func (r *featureFlagRepository) Delete(ctx context.Context, id bson.ObjectID) error {
 _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id})
 return err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last, but certainly not least is our models! Structuring data is fun right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// internal/models/feature_flags.go
package models

import (
 "go.mongodb.org/mongo-driver/v2/bson"
)

type FeatureFlag struct {
 ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
 Flag string `bson:"flag" json:"flag"`
 Variations VariationSet `bson:"variations" json:"variations"`
 DefaultRule FeatureFlagRuleSet `bson:"defaultRule" json:"defaultRule"`
}

type VariationSet struct {
 DefaultVar bool `bson:"default_var" json:"default_var"`
 FalseVar bool `bson:"false_var" json:"false_var"`
 TrueVar bool `bson:"true_var" json:"true_var"`
}

type FeatureFlagRuleSet struct {
 Percentage map[string]int `bson:"percentage" json:"percentage"`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will write a simple Dockerfile to keep the backend portable and allow us to deploy it to the local cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Dockerfile
FROM golang:1.24 AS builder

WORKDIR /app

COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o app .

FROM alpine:latest

WORKDIR /root/

COPY --from=builder /app/app .

EXPOSE 3001

ENTRYPOINT ["./app"]

// .dockerignore
.air.toml
tmp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be the Kubernetes yamls that will be used to deploy this app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# manifests/deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flipping-out-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flipping-out-api
  template:
    metadata:
      labels:
        app: flipping-out-api
    spec:
      containers:
        - name: api
          image: flipping-out-api:1
          imagePullPolicy: Never # Because we'll load it locally into kind
          ports:
            - containerPort: 3001
---
apiVersion: v1
kind: Service
metadata:
  name: flipping-out-api
spec:
  selector:
    app: flipping-out-api
  ports:
    - protocol: TCP
      port: 3001
      targetPort: 3001
  type: ClusterIP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now let’s build the docker image locally and load it into our local cluster with a deployment!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t flipping-out-api:1 .
kind load docker-image flipping-out-api:1 --name feature-analytics
kubectl apply -f manifests/deploy.yml
# Then port forward this too
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  UI
&lt;/h4&gt;

&lt;p&gt;Now of course we will want to show off the beautiful abilities of flags via client side too! Go back to the root directory and create a directory next to app/ called client/ . This will contain our client side application, and I am choosing Remix, a powerful framework built on the React library. You will need to port forward the relay proxy for our app to use it.&lt;/p&gt;

&lt;p&gt;Get a Remix project going with Shadcn (because we are creative like that):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pnpm create remix@latest .

pnpm dlx shadcn@latest init

pnpm add recharts

pnpm dlx shadcn@latest add card button label switch input alert chart

// Add into tsconfig.json in paths
    "paths": {
      "~/*": [
        "./app/*"
      ],
      "@/*": [
        "./app/*"
      ]
    },

//
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s create our user experience page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/routes/user.tsx
import { useFlag } from "@openfeature/react-sdk";
import {
  Alert,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { useState, useEffect, useCallback } from "react"
import FeatureChart from "@/components/FeatureChart";
import { useWebSocket } from "../lib/socket.ts";

type RawEvent = {
  value: boolean;
};

type ChartData = {
  timestamp: number;
  percentTrue: number;
};

export default function Page() {
  const { value: showNewMessage } = useFlag('show-banner', false);
  const [message, setMessage] = useState&amp;lt;string&amp;gt;("");
  const [events, setEvents] = useState&amp;lt;RawEvent[]&amp;gt;([]);
  const [chartData, setChartData] = useState&amp;lt;ChartData[]&amp;gt;([]);

  // Stable callback that won't cause websocket reconnections
  const handleMessage = useCallback((event: RawEvent) =&amp;gt; {
    console.log("Received websocket event:", event);

    setEvents((prev) =&amp;gt; {
      const updated = [...prev, event].slice(-100); // last 100 events
      const trueCount = updated.filter((e) =&amp;gt; e.value).length;
      const percentTrue = Math.round((trueCount / updated.length) * 100);

      setChartData((prevChart) =&amp;gt; [
        ...prevChart.slice(-50), // Keep last 50 chart points
        { timestamp: Date.now(), percentTrue },
      ]);

      return updated;
    });
  }, []); // Empty dependency array - callback never changes

  useWebSocket(handleMessage);

  useEffect(() =&amp;gt; {
    fetch("http://localhost:3001/v1/welcome")
      .then((res) =&amp;gt; res.json())
      .then((data) =&amp;gt; setMessage(data))
      .catch((err) =&amp;gt; {
        console.error("Failed to fetch welcome message:", err);
        setMessage("Failed to load welcome message");
      });
  }, []);

  return (
    &amp;lt;div className="m-5 py-6"&amp;gt;
      &amp;lt;Alert className="p-5"&amp;gt;
        &amp;lt;AlertTitle&amp;gt;FeatureFlag Banner&amp;lt;/AlertTitle&amp;gt;
        {showNewMessage ? 
          &amp;lt;AlertDescription&amp;gt;Welcome to this OpenFeature-enabled React app!&amp;lt;/AlertDescription&amp;gt; :
          &amp;lt;AlertDescription&amp;gt;Welcome to this React app.&amp;lt;/AlertDescription&amp;gt;
        }
      &amp;lt;/Alert&amp;gt;

      &amp;lt;Alert&amp;gt;
        &amp;lt;AlertTitle&amp;gt;User welcome message&amp;lt;/AlertTitle&amp;gt;
        &amp;lt;AlertDescription&amp;gt;{message}&amp;lt;/AlertDescription&amp;gt;
      &amp;lt;/Alert&amp;gt;

      &amp;lt;div className="mt-6"&amp;gt;
        &amp;lt;h1 className="text-xl font-semibold mb-4"&amp;gt;Live Feature Flag Status&amp;lt;/h1&amp;gt;
        &amp;lt;p className="text-sm text-gray-600 mb-4"&amp;gt;
          Events processed: {events.length} | Latest: {events[events.length - 1]?.value ? 'True' : 'False'}
        &amp;lt;/p&amp;gt;
        &amp;lt;FeatureChart data={chartData} /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s build a way to control these feature flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/routes/_index.tsx
import type { MetaFunction } from "@remix-run/node";
import { useEffect, useState } from "react";
import { Form } from "@remix-run/react";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { FeatureFlag } from "~/types/feature-flags";

export const meta: MetaFunction = () =&amp;gt; {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};

export default function FeatureFlagsPage() {
  const [flags, setFlags] = useState&amp;lt;FeatureFlag[]&amp;gt;([]);
  const [newFlagName, setNewFlagName] = useState("");

  useEffect(() =&amp;gt; {
    fetch("http://localhost:3001/v1/flags")
      .then((res) =&amp;gt; res.json())
      .then((data) =&amp;gt; setFlags(data));
  }, []);

  const handleCreate = async (e: React.FormEvent) =&amp;gt; {
    e.preventDefault();

    await fetch("http://localhost:3001/v1/flags", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        flag: newFlagName,
        variations: {
          default_var: false,
          false_var: false,
          true_var: true,
        },
        defaultRule: {
          percentage: {
            false_var: 100,
            true_var: 0,
          },
        },
      }),
    });

    setNewFlagName("");
    const res = await fetch("http://localhost:3001/v1/flags");
    setFlags(await res.json());
  };

  const handleToggle = async (flag: FeatureFlag) =&amp;gt; {
    const newSplit = flag.defaultRule.percentage.true_var === 100
      ? { false_var: 100, true_var: 0 }
      : { false_var: 0, true_var: 100 };

    await fetch(`http://localhost:3001/v1/flags/${flag.id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ defaultRule: { percentage: newSplit } }),
    });

    const res = await fetch("http://localhost:3001/v1/flags");
    setFlags(await res.json());
  };

  return (
    &amp;lt;div className="max-w-4xl mx-auto mt-10 space-y-10"&amp;gt;
      &amp;lt;Card&amp;gt;
        &amp;lt;CardHeader&amp;gt;
          &amp;lt;CardTitle&amp;gt;Create New Feature Flag&amp;lt;/CardTitle&amp;gt;
        &amp;lt;/CardHeader&amp;gt;
        &amp;lt;CardContent&amp;gt;
          &amp;lt;form onSubmit={handleCreate} className="flex gap-4"&amp;gt;
            &amp;lt;div className="grid gap-2 w-full"&amp;gt;
              &amp;lt;Label htmlFor="name"&amp;gt;Flag Name&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="name"
                value={newFlagName}
                onChange={(e) =&amp;gt; setNewFlagName(e.target.value)}
                placeholder="e.g. new-admin-ui"
                required
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className="flex items-end"&amp;gt;
              &amp;lt;Button type="submit"&amp;gt;Create&amp;lt;/Button&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/form&amp;gt;
        &amp;lt;/CardContent&amp;gt;
      &amp;lt;/Card&amp;gt;

      &amp;lt;Card&amp;gt;
        &amp;lt;CardHeader&amp;gt;
          &amp;lt;CardTitle&amp;gt;Feature Flags&amp;lt;/CardTitle&amp;gt;
        &amp;lt;/CardHeader&amp;gt;
        &amp;lt;CardContent&amp;gt;
          &amp;lt;div className="space-y-4"&amp;gt;
            {flags &amp;amp;&amp;amp; flags.map((flag) =&amp;gt; (
              &amp;lt;div key={flag.id} className="flex items-center justify-between border-b pb-2"&amp;gt;
                &amp;lt;span className="text-sm font-medium"&amp;gt;{flag.flag}&amp;lt;/span&amp;gt;
                &amp;lt;Switch
                  checked={flag.defaultRule.percentage.true_var === 100}
                  onCheckedChange={() =&amp;gt; handleToggle(flag)}
                /&amp;gt;
              &amp;lt;/div&amp;gt;
            ))}
          &amp;lt;/div&amp;gt;
        &amp;lt;/CardContent&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need our supporting libraries for the feature flags and webhooks too&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/lib/featureFlagProvider.tsx
import { OpenFeature, OpenFeatureProvider } from "@openfeature/react-sdk";
import { GoFeatureFlagWebProvider } from "@openfeature/go-feature-flag-web-provider";

const goFeatureFlagWebProvider = new GoFeatureFlagWebProvider({
  endpoint: "http://localhost:1031"
});

// Set the initial context for your evaluations
OpenFeature.setContext({
  targetingKey: "user-1",
  admin: false
});

// Instantiate and set our provider (be sure this only happens once)!
// Note: there's no need to await its initialization, the React SDK handles re-rendering and suspense for you!
OpenFeature.setProvider(goFeatureFlagWebProvider);

// Enclose your content in the configured provider
export function OFWrapper({ children }: { children: React.ReactNode }) {
  return (
    &amp;lt;OpenFeatureProvider&amp;gt;
      {children}
    &amp;lt;/OpenFeatureProvider&amp;gt;
  );
}

// Be sure to remember to wrap this provider around your root of the app!

// app/lib/socket.ts
import { useEffect, useRef, useCallback } from "react";

export function useWebSocket(onMessage: (data: any) =&amp;gt; void) {
  const socketRef = useRef&amp;lt;WebSocket | null&amp;gt;(null);
  const reconnectTimeoutRef = useRef&amp;lt;any&amp;gt;(null);
  const reconnectAttempts = useRef(0);
  const maxReconnectAttempts = 5;

  // Memoize the onMessage callback to prevent unnecessary reconnections
  const stableOnMessage = useCallback(onMessage, []);

  const connect = useCallback(() =&amp;gt; {
    if (socketRef.current?.readyState === WebSocket.OPEN) {
      return; // Already connected
    }

    try {
      const socket = new WebSocket("ws://localhost:3001/v1/ws");

      socket.onopen = () =&amp;gt; {
        console.log("WebSocket connected");
        reconnectAttempts.current = 0;
      };

      socket.onmessage = (event) =&amp;gt; {
        try {
          const parsed = JSON.parse(event.data);
          stableOnMessage(parsed);
        } catch (error) {
          console.error("Failed to parse WebSocket message:", error);
        }
      };

      socket.onerror = (err) =&amp;gt; {
        console.error("WebSocket error:", err);
      };

      socket.onclose = (event) =&amp;gt; {
        console.log(`WebSocket closed: ${event.code} ${event.reason}`);
        socketRef.current = null;

        // Only reconnect if it wasn't a normal closure and we haven't exceeded max attempts
        if (event.code !== 1000 &amp;amp;&amp;amp; reconnectAttempts.current &amp;lt; maxReconnectAttempts) {
          const delay = Math.pow(2, reconnectAttempts.current) * 1000; // Exponential backoff
          console.log(`Attempting to reconnect in ${delay}ms (attempt ${reconnectAttempts.current + 1})`);

          reconnectTimeoutRef.current = setTimeout(() =&amp;gt; {
            reconnectAttempts.current++;
            connect();
          }, delay);
        }
      };

      socketRef.current = socket;
    } catch (error) {
      console.error("Failed to create WebSocket:", error);
    }
  }, [stableOnMessage]);

  useEffect(() =&amp;gt; {
    connect();

    return () =&amp;gt; {
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      if (socketRef.current) {
        socketRef.current.close(1000, "Component unmounting");
      }
    };
  }, [connect]);

  return {
    socket: socketRef.current,
    reconnect: connect
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome, now let’s run the cool new UI locally and see how it is interacting with our new server too!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pnpm run dev
// Make sure to portforward the gin pod, and relay so it
// is exposed to your local host network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can create a new flag show-banner which will actively change the banner, no reload needed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v3a74nrmj6dl0xm8guh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v3a74nrmj6dl0xm8guh.png" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Give me the Data
&lt;/h4&gt;

&lt;p&gt;To check the Kafka topic and messages we can use Kafka’s tooling like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -it kafka-controller-0 -- bash

# Lists the topics available
kafka-topics.sh --bootstrap-server localhost:9092 --list

# Describe topics
kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic go-feature-flag-events

# List the messages
kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic go-feature-flag-events \
--from-beginning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have visited the UI and waited until the collector sends a batch of messages to the Relay you should see some messages coming through! These messages are the same ones being consumed by the go consumer and pushed to our Remix app via websockets!&lt;/p&gt;

&lt;p&gt;In a more production based application, you would want to track these events in a time series database and display the results that way. You can also track custom events with these feature flags which is crucial to experimentation and evaluating your product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As my own test, comment if you like the tutorial portions of the blogs, and if you like the format of showing pretty much all files in the tutorial or just highlight the important ones?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Feature flags don’t have to be the stuff of engineering horror stories. With tools like OpenFeature and GoFF, you can keep your codebase clean, your feature rollouts chill, and your dev team sane(ish).&lt;/p&gt;

&lt;p&gt;So go ahead — flip some flags. &lt;strong&gt;Experiment. Test. Iterate.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Just promise me you’ll clean them up afterwards, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openfeature.dev/blog" rel="noopener noreferrer"&gt;Blog | OpenFeature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gofeatureflag.org/" rel="noopener noreferrer"&gt;Simple Feature Flagging for All | GO Feature Flag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://posthog.com/product-engineers/feature-flag-benefits-use-cases" rel="noopener noreferrer"&gt;Why use feature flags? Benefits, types and use cases, explained - PostHog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ai.stanford.edu/~ronnyk/ExPThinkWeek2009Public.pdf" rel="noopener noreferrer"&gt;https://ai.stanford.edu/~ronnyk/ExPThinkWeek2009Public.pdf&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>productmanagement</category>
      <category>go</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>The Start of Something Big</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Wed, 09 Apr 2025 16:31:02 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/the-start-of-something-big-4b94</link>
      <guid>https://forem.com/joshtheburgess/the-start-of-something-big-4b94</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft089plnj0dz7d2g3r8dr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft089plnj0dz7d2g3r8dr.png" width="365" height="200"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://mudget.finance" rel="noopener noreferrer"&gt;https://mudget.finance&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  START OF A STARTUP
&lt;/h4&gt;

&lt;h4&gt;
  
  
  How to Incorporate, Taxes, and Other Founder Essentials
&lt;/h4&gt;

&lt;p&gt;I am going to start a series of articles that are a little less technical and a little more business and personal — though don’t worry, I will still be sending out some sick tech tutorial blogs — just with these sprinkled in the mix. I like building things, and this time I am building something as a founder. Along the way, I’ll be documenting the lessons learned as I’m founding my company, &lt;a href="https://mudget.finance" rel="noopener noreferrer"&gt;Mudget&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Out of the gate, being a founder means wearing a lot of hats. You are the C suite and boots on the ground, so get ready to test your marketing skills, your technical prowess, and your business acumen. But the hardest part is just taking the chance. So many ideas and companies are not built because people fail to take the first step — incorporating the business.&lt;/p&gt;

&lt;p&gt;But ah, the age-old question: &lt;strong&gt;LLC vs. C-Corp vs. S-Corp — Which Business Structure Will Make You the Next SaaS Unicorn?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve found that if you’re starting a SaaS company, picking the right business structure isn’t just about taxes (though Uncle Sam definitely has a say). It’s about &lt;strong&gt;fundraising, liability, paperwork, and long-term scalability&lt;/strong&gt;. So let’s break it down in a way that won’t make you wish you’d never taken the leap.&lt;/p&gt;

&lt;p&gt;Quick disclaimer ~ I am not a lawyer or CPA, but wanted to put together a digestible, leagalease free guide for a new entrepreneur. If you have nuanced questions, please seek out a good lawyer for these.&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ LLC (Limited Liability Company) — The Chill Indie Hacker’s Dream
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;An LLC is the “low-commitment relationship” of business structures. It’s flexible, easy to set up, and lets you keep your business and personal assets separate. Best of all — you can choose how you’re taxed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;✅ &lt;a href="https://www.irs.gov/businesses/small-businesses-self-employed/limited-liability-company-llc" rel="noopener noreferrer"&gt;&lt;strong&gt;Simple &amp;amp; Flexible&lt;/strong&gt;&lt;/a&gt; — &lt;a href="https://gouchevlaw.com/3-must-documents-llc/" rel="noopener noreferrer"&gt;Less paperwork&lt;/a&gt;, fewer legal hoops.&lt;br&gt;&lt;br&gt;
✅ &lt;a href="https://www.irs.gov/businesses/small-businesses-self-employed/llc-filing-as-a-corporation-or-partnership" rel="noopener noreferrer"&gt;&lt;strong&gt;Taxation Options&lt;/strong&gt;&lt;/a&gt; — Can be taxed as a sole proprietorship, partnership, &lt;a href="https://www.wolterskluwer.com/en/expert-insights/llc-electing-s-corp-tax-status-an-option-you-may-not-know-you-have" rel="noopener noreferrer"&gt;S-Corp&lt;/a&gt;, or even a C-Corp.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Limited Liability&lt;/strong&gt;  — Your personal yacht is safe (once you buy one).&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Not VC-Friendly&lt;/strong&gt;  — Investors dislike LLCs. They want &lt;strong&gt;Delaware C-Corps&lt;/strong&gt; (more on that later).&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Self-Employment Taxes&lt;/strong&gt;  — If taxed as a sole proprietorship, &lt;strong&gt;expect to pay&lt;/strong&gt; &lt;a href="https://www.irs.gov/businesses/small-businesses-self-employed/self-employment-tax-social-security-and-medicare-taxes#:~:text=tax%20rate%20is-,15.3%25,-.%20The%20rate%20consists" rel="noopener noreferrer"&gt;&lt;strong&gt;15.3%&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;in self-employment tax&lt;/strong&gt; on profits. (as of Feb 2025)&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Ownership Restrictions&lt;/strong&gt;  — Some tax advantages (like S-Corp status) are harder to get with an LLC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;p&gt;📌 &lt;strong&gt;Bootstrapped SaaS founders&lt;/strong&gt; who don’t plan to raise venture capital anytime soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ C-Corp (C Corporation) — The Venture Capital Darling
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;A C-Corp is the full send. It’s &lt;strong&gt;built for scale&lt;/strong&gt; , preferred by investors, and lets you issue different stock classes. But, it comes with &lt;strong&gt;double taxation&lt;/strong&gt;  — which sounds as bad as it is. 💸&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Investor Magnet&lt;/strong&gt;  — &lt;a href="https://carta.com/learn/startups/private-companies/c-corp-vs-llc/" rel="noopener noreferrer"&gt;VCs &lt;strong&gt;only&lt;/strong&gt; invest in C-Corps&lt;/a&gt; (especially Delaware C-Corps).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Stock Incentives&lt;/strong&gt;  — Issue stock options to employees (hello, talent retention!).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Limited Liability&lt;/strong&gt;  — Protects founders from lawsuits.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Low Corporate Tax Rate&lt;/strong&gt;  — 21% flat rate (thanks, &lt;a href="https://www.irs.gov/newsroom/tax-cuts-and-jobs-act-a-comparison-for-businesses" rel="noopener noreferrer"&gt;2017 Tax Cuts and Jobs Act&lt;/a&gt;).&lt;br&gt;&lt;br&gt;
✅ &lt;a href="https://carta.com/learn/startups/tax-planning/qsbs/" rel="noopener noreferrer"&gt;&lt;strong&gt;QSBS&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;Exemption&lt;/strong&gt;  — If you hold stock for 5 years, you may avoid capital gains tax on up to $10M when selling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Double Taxation&lt;/strong&gt;  — The company pays &lt;strong&gt;corporate tax (21%)&lt;/strong&gt;, and dividends are taxed again on personal returns.&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Heavy Paperwork&lt;/strong&gt;  — Required annual meetings, board minutes, and more.&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Regulatory Overhead&lt;/strong&gt;  — &lt;a href="https://www.delawareinc.com/after-forming-your-company/corporate-compliance/#:~:text=The%20state%20of%20Delaware%20imposes,possibly%20administratively%20dissolve%20that%20company." rel="noopener noreferrer"&gt;Strict compliance&lt;/a&gt;, especially for &lt;strong&gt;Delaware C-Corps&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;p&gt;📌 &lt;strong&gt;VC-backed SaaS startups&lt;/strong&gt; planning to raise millions and go public.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Do All the Cool Startups Pick Delaware?
&lt;/h3&gt;

&lt;p&gt;If you’ve ever peeked at where tech giants are incorporated, you’ll notice one thing: &lt;strong&gt;Delaware.&lt;/strong&gt; Even if a startup is based in California, Texas, or New York, it’s probably a &lt;strong&gt;Delaware C-Corp&lt;/strong&gt;. Why? Because &lt;strong&gt;Delaware is basically the VIP lounge for corporations&lt;/strong&gt;  — it has the best legal protections, tax perks (sometimes), and makes investors feel warm and fuzzy inside.&lt;/p&gt;

&lt;p&gt;But is it always the right choice?&lt;/p&gt;

&lt;h3&gt;
  
  
  Delaware C-Corp: The Startup &amp;amp; VC Favorite
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Why It’s The GOAT for Startups
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Investor-Approved&lt;/strong&gt;  — VCs expect it. If you plan to raise money, &lt;strong&gt;Delaware is practically a non-negotiable&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Stock Flexibility&lt;/strong&gt;  — You can issue &lt;a href="https://carta.com/learn/equity/common-stock-vs-preferred-stock/" rel="noopener noreferrer"&gt;&lt;strong&gt;preferred stock&lt;/strong&gt;&lt;/a&gt; (essential for investor deals) and &lt;strong&gt;stock options&lt;/strong&gt; (for employees).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Business-Friendly Laws&lt;/strong&gt;  — Delaware’s corporate law is &lt;strong&gt;fast, clear, and company-friendly&lt;/strong&gt;. The &lt;a href="https://courts.delaware.gov/chancery/" rel="noopener noreferrer"&gt;&lt;strong&gt;Court of Chancery&lt;/strong&gt;&lt;/a&gt; (special business court) lets you &lt;strong&gt;skip juries&lt;/strong&gt; and get rulings from expert judges.&lt;br&gt;&lt;br&gt;
✅ &lt;a href="https://blog.founderscpa.com/why-do-startups-form-delaware-c-corporations" rel="noopener noreferrer"&gt;&lt;strong&gt;Tax Benefits (Sort Of)&lt;/strong&gt; &lt;/a&gt;— No corporate income tax on &lt;strong&gt;out-of-state revenue&lt;/strong&gt;. No &lt;strong&gt;sales tax, investment income tax, or tax on intangible assets&lt;/strong&gt; (which SaaS companies love).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Easier Mergers &amp;amp; Acquisitions&lt;/strong&gt;  — If you dream of being acquired for billions, Delaware law makes it &lt;strong&gt;faster and smoother&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Downsides of Delaware C-Corp
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Extra Fees &amp;amp; Paperwork&lt;/strong&gt;  — You’ll need to register as a &lt;strong&gt;foreign corporation&lt;/strong&gt; in your home state, meaning you’ll pay &lt;strong&gt;two sets of fees&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
❌ &lt;a href="https://financefiles.delaware.gov/docs/corp_franchise.pdf" rel="noopener noreferrer"&gt;&lt;strong&gt;Franchise Tax Can Be Costly&lt;/strong&gt;&lt;/a&gt; — Delaware charges an annual &lt;strong&gt;franchise tax&lt;/strong&gt; that can go up to &lt;strong&gt;$200,000+&lt;/strong&gt; for large companies.&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Not Ideal for Small Bootstrapped Companies&lt;/strong&gt;  — If you’re not planning to raise funding, a home-state C-Corp (or even an LLC) might be &lt;strong&gt;simpler and cheaper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Yes, it costs more upfront, but &lt;strong&gt;it saves you headaches later&lt;/strong&gt; when you inevitably need to raise funds, issue stock, or avoid a messy legal battle. If you’re serious about growing a startup &lt;strong&gt;beyond six figures&lt;/strong&gt; , a Delaware C-Corp is the &lt;strong&gt;smartest long-term move&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’re bootstrapping &lt;strong&gt;indefinitely&lt;/strong&gt; and don’t want investors, then sure — stay local and save some cash. But if there’s even a &lt;strong&gt;1% chance&lt;/strong&gt; you’ll take on funding, just start with &lt;strong&gt;Delaware C-Corp&lt;/strong&gt; from day one. Your future VC term sheet will thank you. 💸&lt;/p&gt;

&lt;p&gt;If you’re raising VC money, remember:&lt;br&gt;&lt;br&gt;
🔥 &lt;strong&gt;They need huge exits&lt;/strong&gt;  — If your startup won’t hit $100M+, they won’t care.&lt;br&gt;&lt;br&gt;
🔥 &lt;strong&gt;They expect aggressive growth&lt;/strong&gt;  — VC-backed startups must scale &lt;strong&gt;fast&lt;/strong&gt; or die trying.&lt;br&gt;&lt;br&gt;
🔥 &lt;strong&gt;They own part of your company&lt;/strong&gt;  — If you raise a lot, you could lose control over decisions. I will write about Cap tables, a tool to understand ownership, if I ever need to make one or if enough people are interested.&lt;/p&gt;

&lt;p&gt;So before taking VC money, ask yourself: &lt;strong&gt;Do I want a billion-dollar company, or do I want control over a profitable business?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VC is a rocket ship 🚀 — fun if you win, but &lt;strong&gt;brutal if you’re not built for it.&lt;/strong&gt; If you don’t want to play that game, bootstrap or find angel investors instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  3️⃣ S-Corp (S Corporation) — The Tax Hack for Small Founders
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;An S-Corp is like a C-Corp’s frugal younger brother. It’s structured like a corporation but &lt;strong&gt;avoids double taxation&lt;/strong&gt; because profits pass through to owners. However, it has ownership restrictions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;✅ &lt;a href="https://stripe.com/resources/more/what-is-an-s-corp-what-businesses-need-to-know#:~:text=C%20corps%20pay%20corporate%20income,because%20only%20shareholders%20pay%20taxes." rel="noopener noreferrer"&gt;&lt;strong&gt;No Double Taxation&lt;/strong&gt;&lt;/a&gt; — Profits pass through to your personal tax return.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Lower Self-Employment Tax&lt;/strong&gt;  — You can pay yourself a “reasonable salary” and take the rest as distributions (which aren’t hit with self-employment taxes).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Limited Liability&lt;/strong&gt;  — Keeps your personal assets safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons for a SaaS Company
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Ownership Limits&lt;/strong&gt;  — Max 100 shareholders, and they must be &lt;strong&gt;U.S. citizens or residents&lt;/strong&gt;. No VCs allowed.&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Less Attractive to Investors&lt;/strong&gt;  — If you plan to raise money, you’ll need to &lt;strong&gt;convert to a C-Corp&lt;/strong&gt; anyway.&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;More Paperwork Than an LLC&lt;/strong&gt;  — Still need payroll, tax filings, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;p&gt;📌 &lt;strong&gt;Solo SaaS founders&lt;/strong&gt; making ~$80K+ in profit who want tax savings but &lt;strong&gt;don’t need VC money&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to Incorporate
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Research and decide on the business structure (…you just did this 🐐)&lt;/li&gt;
&lt;li&gt;Choose a business name that does not infringe on another business. You should look into available domain names, trademarks, and your state usually has a platform where you can check this (you can find &lt;a href="https://file.dos.pa.gov/search/business" rel="noopener noreferrer"&gt;PA&lt;/a&gt;’s here)&lt;/li&gt;
&lt;li&gt;The process varies by state, but you will need to register the name of your business, and you can trademark it for good measure at &lt;a href="https://www.uspto.gov/" rel="noopener noreferrer"&gt;United States Patent &amp;amp; Trademark Office&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Appoint a registered agent responsible for receiving legal documents on behalf of your corporation&lt;/li&gt;
&lt;li&gt;Draft and file articles of incorporation, the official documents that establish your business as a corporation in your state!&lt;/li&gt;
&lt;li&gt;Get an employer identification number (EIN), this is essentially the business’ social security number, you need this for the next step&lt;/li&gt;
&lt;li&gt;Open a Business Bank account that is used only for business transactions (don’t give anyone a reason to take your new yacht)&lt;/li&gt;
&lt;li&gt;Depending on your business you may need federal, state, and local permits and licenses to operate — if so, obtain them before doing business&lt;/li&gt;
&lt;li&gt;Bookkeeping and Budgeting is essential to reduce stress (Look to Mudget in the future for a B2B offering on this 🦄)&lt;/li&gt;
&lt;li&gt;Go do business and change the world!&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Using Services
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Not sponsored — yet! Get in touch 📲&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are incorporating, using an online service can give you ease of mind on compliance and be quick for checking all the boxes. Your tradeoff is the expense they tack on for providing you this service. These will offer solutions for a lot of the steps needed when incorporating and making your business official.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.zenbusiness.com/" rel="noopener noreferrer"&gt;ZenBusiness&lt;/a&gt; — Easy to use, worry-free compliance at a low cost
&lt;/h4&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://stripe.com/atlas" rel="noopener noreferrer"&gt;Stripe Atlas&lt;/a&gt; — Plenty of integrations to other services like AngelList and Carta (great for SaaS)
&lt;/h4&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.doola.com/" rel="noopener noreferrer"&gt;Doola&lt;/a&gt; — Super easy to use and has a service market place for finding great startup tools
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Final Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LLCs are great until you want VC money.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S-Corps are ideal for solo founders looking for tax efficiency.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-Corps are mandatory if you want to raise millions and IPO.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delaware C-Corp is the gold standard&lt;/strong&gt; for tech startups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re still undecided, just start with an &lt;strong&gt;LLC taxed as an S-Corp&lt;/strong&gt; , then convert to a C-Corp if investors come knocking. Easy pivot, fewer headaches. Since this is a side project at the time of writing, I am going the chill-indie-hacker route and incorporating as an LLC taxed as sole proprietor. You can always incorporate later or change how you are taxed.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://app.mudget.finance/waitlist" rel="noopener noreferrer"&gt;Of course, shameless plug, join my waitlist for Mudget!&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://mudget.finance" rel="noopener noreferrer"&gt;Mudget - Simplify Your Finances&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Also, not sponsored, but Carta, AngelList, and Stripe are great resources for anyone interested in the nuances.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.angellist.com/learn" rel="noopener noreferrer"&gt;Education Center | AngelList&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://carta.com/learn/startups/" rel="noopener noreferrer"&gt;https://carta.com/learn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stripe.com/en-pl/resources/more/what-is-a-delaware-c-corp" rel="noopener noreferrer"&gt;What is a Delaware C corp? | Stripe&lt;/a&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>founders</category>
      <category>business</category>
      <category>programming</category>
    </item>
    <item>
      <title>Smooth *k8s* Operator</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Mon, 03 Mar 2025 14:32:37 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/smooth-k8s-operator-3k1b</link>
      <guid>https://forem.com/joshtheburgess/smooth-k8s-operator-3k1b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4df5uvsff1ehiji20xs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4df5uvsff1ehiji20xs.png" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;AI Generated with Canva — Prompted with Golang Gopher and Kubernetes #Glosnibetes&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Creating a Custom Kubernetes Operator in Golang
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Kubernetes is a platform for building platforms. It’s a better place to start; not the endgame. — Kelsey Hightower&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes: The Overlord of Containers (But in a Good Way!)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s be real — running containerized apps in production without Kubernetes is like herding cats. Sure, you can manage a few containers with Docker Compose, but the moment you scale up, things start slipping through the cracks. Enter Kubernetes, the ultimate traffic cop, janitor, and executive assistant for your containers.&lt;/p&gt;

&lt;p&gt;But Kubernetes isn’t just about keeping things running — it’s about running them &lt;em&gt;smartly&lt;/em&gt;. And when standard Kubernetes resources aren’t enough, you level up with &lt;strong&gt;Operators&lt;/strong&gt;  — your personal automation minions that extend Kubernetes’ brainpower to handle complex app logic.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll dive into what Kubernetes does, why it exists (hint: because ops teams like sleep), and how Operators fit into the grand scheme of things. And because we like to get our hands dirty, we’ll build our own Operator using &lt;a href="https://book.kubebuilder.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kubebuilder&lt;/strong&gt;&lt;/a&gt; and Go. Ready? Let’s roll.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Kubernetes?
&lt;/h3&gt;

&lt;p&gt;Imagine you run a taco truck. Business is booming, and now you’ve got &lt;strong&gt;50 taco trucks&lt;/strong&gt; across the city. Amazing, right? But suddenly, you have &lt;strong&gt;new problems&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who makes sure each truck has enough ingredients?&lt;/li&gt;
&lt;li&gt;What if a truck breaks down?&lt;/li&gt;
&lt;li&gt;How do you handle long lines at some locations while others are empty?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the kind of chaos that happens when running containerized applications at scale. And just like you’d need &lt;strong&gt;a fleet manager&lt;/strong&gt; to coordinate all your taco trucks, you need &lt;strong&gt;Kubernetes&lt;/strong&gt; to manage your containers.&lt;/p&gt;

&lt;p&gt;Kubernetes makes sure:&lt;br&gt;&lt;br&gt;
✅ Your containers are running and healthy.&lt;br&gt;&lt;br&gt;
✅ New containers spin up when traffic surges.&lt;br&gt;&lt;br&gt;
✅ Everything is deployed and updated smoothly (no taco shortages!).&lt;/p&gt;

&lt;p&gt;In short, Kubernetes is like your personal fleet manager — but for containers instead of tacos. 🌮&lt;/p&gt;
&lt;h3&gt;
  
  
  Okay, This is Complex. How Can I Use It?
&lt;/h3&gt;

&lt;p&gt;Kubernetes is your personal fleet manager — cool. But now you are staring at the command line tools, YAML files, and architecture diagrams that resemble last night’s spaghetti dinner rather than the microservices you’d like to deploy. So what now?&lt;/p&gt;

&lt;p&gt;Using Kubernetes doesn’t mean you need a PhD in &lt;strong&gt;Clusterology™&lt;/strong&gt;. Here’s the &lt;strong&gt;simplified game plan&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;1️⃣ &lt;strong&gt;Tell Kubernetes what you want&lt;/strong&gt;  — You define how many taco staff (containers) you need, the truck (pod), what ingredients they should have (config + storage + secrets), the address they are at (service), and where they should be placed (scheduling). There are much more &lt;strong&gt;Kinds&lt;/strong&gt; (the names in the parenthesis) you can make or define in the sprawling amount of YAML files.&lt;/p&gt;

&lt;p&gt;2️⃣ &lt;strong&gt;Hand over the keys&lt;/strong&gt;  — Kubernetes takes your instructions (YAML files), does its magic, and keeps everything running. If a truck (pod) crashes? Kubernetes replaces it. Too much demand? Kubernetes scales up the trucks to meet that demand.&lt;/p&gt;

&lt;p&gt;3️⃣ &lt;strong&gt;Profit.&lt;/strong&gt; (Okay, not literally, but your app stays online, and that’s priceless.)&lt;/p&gt;

&lt;p&gt;And the best part? &lt;strong&gt;You don’t have to do everything manually.&lt;/strong&gt; Kubernetes has Operators (which we’ll dive into soon) that act like &lt;strong&gt;robot assistants&lt;/strong&gt; , automating even the trickiest tasks for you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Let’s Build
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49sag9p7pcuiljzek4gx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49sag9p7pcuiljzek4gx.png" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Tooling
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;Golang&lt;/a&gt; 1.23+&lt;/li&gt;
&lt;li&gt;&lt;a href="https://book.kubebuilder.io/quick-start" rel="noopener noreferrer"&gt;Kubebuilder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;Kind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubectl *this can be installed with &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://k9scli.io/" rel="noopener noreferrer"&gt;k9s&lt;/a&gt; *one of those dandy CLI tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Local Cluster
&lt;/h4&gt;

&lt;p&gt;First, we’ll use &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kind&lt;/strong&gt;&lt;/a&gt; — a tool for running local Kubernetes clusters inside Docker. Installing it with Go is easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install sigs.k8s.io/kind@v0.26.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, create a local cluster with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this switches your &lt;strong&gt;kubectl context&lt;/strong&gt; to the new Kind cluster. You can verify it’s running by checking all pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pod -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The Operator Pattern Explained
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;em&gt;operator pattern&lt;/em&gt; aims to capture the key aim of a human operator who is managing a service or set of services. Human operators who look after specific applications and services have deep knowledge of how the system ought to behave, how to deploy it, and how to react if there are problems.&lt;/p&gt;

&lt;p&gt;People who run workloads on Kubernetes often like to use automation to take care of repeatable tasks. The operator pattern captures how you can write code to automate a task beyond what Kubernetes itself provides. — &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/" rel="noopener noreferrer"&gt;Kubernetes Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Operators are built using &lt;strong&gt;Custom Resource Definitions (CRDs)&lt;/strong&gt; and &lt;strong&gt;controllers&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRD (Custom Resource Definition)&lt;/strong&gt; → Defines a new type of Kubernetes resource. (E.g., TacoOrder)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controller&lt;/strong&gt; → Watches for changes and takes action. (E.g., If TacoOrder increases, scale up more taco-making pods!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operators let you &lt;strong&gt;codify human operational knowledge&lt;/strong&gt; into Kubernetes, making it self-managing. Whether you’re running &lt;strong&gt;PostgreSQL, Kafka, or a taco stand&lt;/strong&gt; , Operators help Kubernetes handle complex workloads autonomously.&lt;/p&gt;

&lt;p&gt;Want to build one? &lt;strong&gt;Let’s roll up our sleeves and build the “Taco Operator” with Golang’s Kubebuilder!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir taco-operator &amp;amp;&amp;amp; cd taco-operator
kubebuilder init --domain tacos.io --repo github.com/myuser/taco-operator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a Go module and sets up the necessary files for our Operator.&lt;/p&gt;

&lt;h4&gt;
  
  
  CRDs
&lt;/h4&gt;

&lt;p&gt;Since Kubernetes doesn’t natively understand tacos (yet), we need to teach it by defining a &lt;strong&gt;Custom Resource Definition (CRD)&lt;/strong&gt;. This tells Kubernetes what a “Taco” is and what fields it should have.&lt;/p&gt;

&lt;p&gt;Run the following command to create a new API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubebuilder create api --group food --version v1alpha1 --kind TacoOrder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saying yes to create resources and create controller, we have can customize our CRDs and implement out the new API in &lt;em&gt;api/v1alpha1/tacoorder_types.go&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// TacoOrderSpec defines the desired state of TacoOrder
type TacoOrderSpec struct {
 // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
 // Important: Run "make" to regenerate code after modifying this file

 // Type of taco (e.g., al pastor, carne asada, veggie)
 Type string `json:"type,omitempty"`

 // Quantity of tacos to be made
 Quantity int `json:"quantity,omitempty"`

 // Extra toppings like guac, salsa, or cheese
 Extras []string `json:"extras,omitempty"`
}

// TacoOrderStatus defines the observed state of TacoOrder
type TacoOrderStatus struct {
 // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
 // Important: Run "make" to regenerate code after modifying this file
 Served int `json:"served,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// TacoOrder is the Schema for the tacoorders API
type TacoOrder struct {
 metav1.TypeMeta `json:",inline"`
 metav1.ObjectMeta `json:"metadata,omitempty"`

 Spec TacoOrderSpec `json:"spec,omitempty"`
 Status TacoOrderStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// TacoOrderList contains a list of TacoOrder
type TacoOrderList struct {
 metav1.TypeMeta `json:",inline"`
 metav1.ListMeta `json:"metadata,omitempty"`
 Items []TacoOrder `json:"items"`
}

func init() {
 SchemeBuilder.Register(&amp;amp;TacoOrder{}, &amp;amp;TacoOrderList{})
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty nifty we only needed to change a couple of types right! Now let’s generate the CRDs and apply them to the cluster!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make generate
make manifests
kubectl apply -k config/crd/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm this is applied with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get crd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The Controller — Operator Logic
&lt;/h4&gt;

&lt;p&gt;Now that Kubernetes understands tacos, we need to write the logic that “cooks” them. This happens in the controller. 🌮&lt;/p&gt;

&lt;p&gt;Modify internal/controller/tacoorder_controller.go to add business logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
 "context"
 "fmt"

 "k8s.io/apimachinery/pkg/runtime"
 ctrl "sigs.k8s.io/controller-runtime"
 "sigs.k8s.io/controller-runtime/pkg/client"
 "sigs.k8s.io/controller-runtime/pkg/log"

 foodv1alpha1 "github.com/joshbrgs/taco-operator/api/v1alpha1"
)

// TacoOrderReconciler reconciles a TacoOrder object
type TacoOrderReconciler struct {
 client.Client
 Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=food.tacos.io,resources=tacoorders,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=food.tacos.io,resources=tacoorders/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=food.tacos.io,resources=tacoorders/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the TacoOrder object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/reconcile
func (r *TacoOrderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 _ = log.FromContext(ctx)

 // TODO(user): your logic here
 var order foodv1alpha1.TacoOrder
 if err := r.Get(ctx, req.NamespacedName, &amp;amp;order); err != nil {
  return ctrl.Result{}, client.IgnoreNotFound(err)
 }

 // "Cooking" the tacos by updating the status
 order.Status.Served = order.Spec.Quantity
 fmt.Printf("🌮 Serving %d %s tacos with extras: %v\n", order.Spec.Quantity, order.Spec.Type, order.Spec.Extras)

 if err := r.Status().Update(ctx, &amp;amp;order); err != nil {
  return ctrl.Result{}, err
 }

 return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *TacoOrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
 return ctrl.NewControllerManagedBy(mgr).
  For(&amp;amp;foodv1alpha1.TacoOrder{}).
  Complete(r)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, build and run the Operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make docker-build IMG=my-registry/taco-operator:0.0.1
#Loads the image to your local node so you do not need a remote repo!
kind load docker-image my-registry/taco-operator:0.0.1
make deploy IMG=my-registry/taco-operator:0.0.1
kubectl get pod -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cooking some Tacos
&lt;/h4&gt;

&lt;p&gt;Now for the fun part — placing a taco order using kubectl! 🎉&lt;/p&gt;

&lt;p&gt;Create a tacoorder.yaml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: food.tacos.io/v1alpha1
kind: TacoOrder
metadata:
  name: my-taco-order
spec:
  type: al_pastor
  quantity: 3
  extras:
    - guacamole
    - extra cheese
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f tacoorder.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s take a look at the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl logs -l control-plane=controller-manager -n taco-operator-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🌮 Serving 3 al_pastor tacos with extras: [guacamole extra cheese]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! Kubernetes just made tacos. 🔥&lt;/p&gt;

&lt;h4&gt;
  
  
  Cleanup
&lt;/h4&gt;

&lt;p&gt;To delete your CRDs from the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make uninstall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Undeploy the controller to the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make undeploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We’ve turned our cluster into a fully operational taco stand.&lt;/p&gt;

&lt;p&gt;But this is just the &lt;strong&gt;appetizer&lt;/strong&gt;  — you can expand on this by adding &lt;strong&gt;alerts for low taco supply&lt;/strong&gt; , &lt;strong&gt;custom dashboards in Grafana for taco trends&lt;/strong&gt; , or even &lt;strong&gt;AI-driven recommendations&lt;/strong&gt; for the next taco flavor of the month.&lt;/p&gt;

&lt;p&gt;So whether you’re running a real microservice or just having fun with Kubernetes, remember: &lt;strong&gt;orchestrating applications is a lot like making tacos&lt;/strong&gt;  — it’s all about balance, automation, and a little bit of spice. 🌶️&lt;/p&gt;

&lt;p&gt;Now go forth and deploy, because the world needs more &lt;strong&gt;deliciously managed infrastructure!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://book.kubebuilder.io/" rel="noopener noreferrer"&gt;Introduction - The Kubebuilder Book&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Idea to Implementation in Minutes</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Thu, 13 Feb 2025 21:58:07 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/from-idea-to-implementation-in-minutes-18o3</link>
      <guid>https://forem.com/joshtheburgess/from-idea-to-implementation-in-minutes-18o3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv68u6ouhvx2cw1b2tzor.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv68u6ouhvx2cw1b2tzor.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Generated by FLUX.1 [Dev] by Black-Forest-Labs&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Building an IDP
&lt;/h4&gt;
&lt;h4&gt;
  
  
  Backstage, Cookiecutter, and ArgoCD AppSets
&lt;/h4&gt;

&lt;p&gt;Have you ever had a brilliant idea for a new service, only to spend hours (or days) wrangling YAML files, setting up pipelines, and questioning your life choices? You’re not alone.&lt;/p&gt;

&lt;p&gt;Building software should feel like creating something exciting — not like filling out endless paperwork. That’s where tools like &lt;strong&gt;Backstage&lt;/strong&gt; , &lt;strong&gt;Cookiecutter&lt;/strong&gt; , and &lt;strong&gt;ArgoCD&lt;/strong&gt; come in. They take the &lt;em&gt;boring&lt;/em&gt; out of boilerplate and help you go from concept to deployment without losing your sanity.&lt;/p&gt;

&lt;p&gt;In this workshop, we’ll break down how these tools work together to streamline development, automate infrastructure, and get your projects running in no time. Whether you’re an engineer drowning in microservices or a team lead trying to enforce best practices without sounding like a dictator, this guide has something for you.&lt;/p&gt;

&lt;p&gt;Let’s dive in and turn that idea into a working app — &lt;em&gt;fast&lt;/em&gt;. 🚀&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Backstage?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A fantastic developer experience leads to happy, creative and productive engineers. Our belief is that engineers should not have to be experts in various infrastructure tools to be productive. Infrastructure should be abstracted away so that you can return to building and scaling, quickly and safely. ~ Backstage&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://backstage.io/" rel="noopener noreferrer"&gt;Backstage&lt;/a&gt; is an open-source developer portal created by Spotify to tame the chaos of infrastructure sprawl. It provides a &lt;strong&gt;single, centralized&lt;/strong&gt; interface where teams can &lt;strong&gt;catalog, document, and manage&lt;/strong&gt; their software efficiently.&lt;/p&gt;

&lt;p&gt;Originally built to handle the explosion of microservices and fragmented documentation inside Spotify, Backstage ensures &lt;strong&gt;consistency, security, and discoverability&lt;/strong&gt; while still giving engineering teams autonomy. Since its open-sourcing in 2020, major companies like &lt;strong&gt;Netflix, American Airlines, and Toyota&lt;/strong&gt; have adopted it to &lt;strong&gt;streamline developer experience and boost productivity&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Cookiecutter?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Scale company best practices and save developer time with repeatable templates your whole team can use ~ Cookiecutter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://cookiecutter.io" rel="noopener noreferrer"&gt;Cookiecutter&lt;/a&gt; is an open-source &lt;strong&gt;command-line tool&lt;/strong&gt; that lets developers &lt;strong&gt;generate new projects from predefined templates&lt;/strong&gt; , reducing manual setup and enforcing best practices across teams.&lt;/p&gt;

&lt;p&gt;It’s perfect for organizations that want to &lt;strong&gt;maintain uniform project structures, enforce coding standards, and eliminate boilerplate&lt;/strong&gt;. Whether you’re spinning up &lt;strong&gt;backend services, frontend apps, or infrastructure configurations&lt;/strong&gt; , Cookiecutter helps teams &lt;strong&gt;move fast without sacrificing consistency&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is ArgoCD?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Get More Done with Kubernetes&lt;/p&gt;

&lt;p&gt;Open source tools for Kubernetes to run workflows, manage clusters, and do GitOps right. ~ Argo Project&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/" rel="noopener noreferrer"&gt;ArgoCD&lt;/a&gt; is a &lt;strong&gt;GitOps-based continuous delivery (CD) tool for Kubernetes&lt;/strong&gt;. It ensures that the desired application state &lt;strong&gt;defined in Git&lt;/strong&gt; matches what’s actually running in your Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;This enables:&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automated deployments&lt;/strong&gt;  — No manual kubectl commands needed.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Rollback &amp;amp; sync&lt;/strong&gt;  — Easily revert to a stable version if something breaks.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Version control &amp;amp; auditability&lt;/strong&gt;  — Every change is tracked in Git.&lt;/p&gt;

&lt;p&gt;By automating Kubernetes deployments with &lt;strong&gt;ArgoCD&lt;/strong&gt; , teams can &lt;strong&gt;ship faster, reduce downtime, and keep configurations predictable&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Build Your Own Personal IDP with Kubernetes
&lt;/h3&gt;

&lt;p&gt;This tutorial is a bit longer than my usual ones, but trust me — it’s a fun concept!&lt;/p&gt;

&lt;p&gt;We’ll be building a &lt;strong&gt;personal Identity Provider (IDP)&lt;/strong&gt;, which can help you:&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Reduce boilerplate&lt;/strong&gt; across personal projects&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Test authentication workflows&lt;/strong&gt; in a local Kubernetes cluster&lt;/p&gt;

&lt;p&gt;If you want to take this a step further, &lt;strong&gt;ArgoCD&lt;/strong&gt; is a powerful tool that lets you &lt;strong&gt;define everything declaratively&lt;/strong&gt;  — including repositories, clusters, and secrets — all in YAML. This approach is ideal for &lt;strong&gt;production use cases&lt;/strong&gt; where &lt;strong&gt;consistency and automation&lt;/strong&gt; are key.&lt;/p&gt;
&lt;h3&gt;
  
  
  What You’ll Need (sorry, it’s a bit more than usual!):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Access to a &lt;strong&gt;Unix-based&lt;/strong&gt; operating system, such as Linux, macOS or &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/" rel="noopener noreferrer"&gt;Windows Subsystem for Linux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;GitHub profile&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker &amp;amp; kubectl&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go 1.16+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm &amp;amp; yarn&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python3&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Familiarity with the  &lt;strong&gt;CLI&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A text editor of your choice ( &lt;strong&gt;I use Neovim btw!&lt;/strong&gt; )&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive in! 🚀&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up Kind and Cluster Connectivity
&lt;/h3&gt;

&lt;p&gt;First, we’ll use &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kind&lt;/strong&gt;&lt;/a&gt; — a tool for running local Kubernetes clusters inside Docker. Installing it with Go is easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install sigs.k8s.io/kind@v0.26.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, create a local cluster with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this switches your &lt;strong&gt;kubectl context&lt;/strong&gt; to the new Kind cluster. You can verify it’s running by checking all pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pod -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up ArgoCD
&lt;/h3&gt;

&lt;p&gt;Now that our cluster is up and running, let’s install &lt;strong&gt;ArgoCD&lt;/strong&gt;  — a tool that simplifies &lt;strong&gt;GitOps-based deployments&lt;/strong&gt;. We’ll use ArgoCD’s &lt;strong&gt;quickstart example&lt;/strong&gt; to apply the necessary Kubernetes manifests.&lt;/p&gt;

&lt;p&gt;First, create the &lt;strong&gt;argocd&lt;/strong&gt; namespace and deploy ArgoCD with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is a &lt;strong&gt;local&lt;/strong&gt; setup, we need to expose the &lt;strong&gt;ArgoCD API server&lt;/strong&gt; by forwarding a port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward svc/argocd-server -n argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ArgoCD can then be accessed using &lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Username is  &lt;strong&gt;admin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retrieve your password with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you’re all set to start managing deployments with ArgoCD!&lt;/p&gt;

&lt;h4&gt;
  
  
  Backstage &amp;amp; SealedSecrets
&lt;/h4&gt;

&lt;p&gt;Time to create our first app — your personal internal development portal! We will be using Backstage in our local Kind cluster, but first, we need to make the Backstage app, and the manifests for our K8s cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @backstage/create-app@latest 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the frontend and backend for Backstage and may take a little bit. The documentation explains the file structure as this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;app-config.yaml: Main configuration file for the app. See &lt;a href="https://backstage.io/docs/conf/" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;catalog-info.yaml: Catalog Entities descriptors. See &lt;a href="https://backstage.io/docs/features/software-catalog/descriptor-format" rel="noopener noreferrer"&gt;Descriptor Format of Catalog Entities&lt;/a&gt; to get started.&lt;/p&gt;

&lt;p&gt;package.json: Root package.json for the project. &lt;em&gt;Note: Be sure that you don’t add any npm dependencies here as they probably should be installed in the intended workspace rather than in the root.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;packages/: Lerna leaf packages or “workspaces”. Everything here is going to be a separate package, managed by lerna.&lt;/p&gt;

&lt;p&gt;packages/app/: A fully functioning Backstage frontend app that acts as a good starting point for you to get to know Backstage.&lt;/p&gt;

&lt;p&gt;packages/backend/: We include a backend that helps power features such as &lt;a href="https://backstage.io/docs/auth/" rel="noopener noreferrer"&gt;Authentication&lt;/a&gt;, &lt;a href="https://backstage.io/docs/features/software-catalog/" rel="noopener noreferrer"&gt;Software Catalog&lt;/a&gt;, &lt;a href="https://backstage.io/docs/features/software-templates/" rel="noopener noreferrer"&gt;Software Templates&lt;/a&gt; and &lt;a href="https://backstage.io/docs/features/techdocs/" rel="noopener noreferrer"&gt;TechDocs&lt;/a&gt; amongst other things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next you will want to create a new repository in your GitHub account and connect it to this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init
git remote add origin git@github.com:&amp;lt;your-username-here&amp;gt;/my-backstage.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we will want to create a nice CI workflow so every time you update backstage and tag it, the Docker image will be built and shipped to your GitHub Container Registry. This should be made in_ .github/workflows/build.yml_&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build and Push Backstage Image

on:
  push:
    tags:
      - '*'

jobs:
  build-and-push:
    name: Build and Push Image
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --immutable

      - name: Compile TypeScript
        run: yarn tsc

      - name: Build Backstage backend
        run: yarn build:backend

      - name: Log in to GitHub Container Registry
        run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin

      - name: Extract tag name
        id: tag
        run: echo "TAG=${GITHUB_REF#refs/tags/}" &amp;gt;&amp;gt; $GITHUB_ENV

      - name: Build Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository_owner }}/backstage:${{ env.TAG }} -f packages/backend/Dockerfile .

      - name: Push Docker image
        run: |
          docker push ghcr.io/${{ github.repository_owner }}/backstage:${{ env.TAG }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s watch this workflow in action by pushing the new Backstage app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add . &amp;amp;&amp;amp; git commit -m "initial backstage with workflow" &amp;amp;&amp;amp; git push &amp;amp;&amp;amp; git tag v0.0.1 &amp;amp;&amp;amp; git push origin v0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a couple minutes you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8epxf0j6p4ab9skhe00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8epxf0j6p4ab9skhe00.png" width="503" height="745"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Your new hosted published Backstage image!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next we will primarily be following the &lt;a href="https://backstage.io/docs/deployment/k8s" rel="noopener noreferrer"&gt;Kubernetes deployment docs from Backstage&lt;/a&gt;. It is a pretty in depth tutorial, so I have prepared the manifests in my Backstage repo for you to copy; and, if you do, change line 18 in &lt;em&gt;manifests/base/backstage.yml&lt;/em&gt; to your container registries path instead so your k8s Pod uses your backstage image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir manifests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this URL: &lt;a href="https://github.com/joshbrgs/my-backstage/tree/main/manifests/" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/my-backstage/tree/main/manifests/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can go to &lt;a href="https://download-directory.github.io/" rel="noopener noreferrer"&gt;https://download-directory.github.io/&lt;/a&gt; and download my manifests for Backstage. Then unzip it into your new content directory.&lt;/p&gt;

&lt;p&gt;Next, you will want to edit your &lt;em&gt;app-config.yaml&lt;/em&gt; — which I removed the contents for prod so I just have this for simple setups. You will want to patch it with these values so the backend uses our Postgres instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app-config.yaml
app:
  baseUrl: http://localhost:7077

organization:
  name: MyOrganization

backend:
  baseUrl: http://localhost:7077
  listen:
    port: 7007
  cors:
    origin: http://localhost:7077
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}

auth:
  # see https://backstage.io/docs/auth/ to learn about auth providers
  providers:
    # See https://backstage.io/docs/auth/guest/provider
    # Doing this to allow guest access, no signing in needed,
    # but you should not do this for production use cases
    guest:
      dangerouslyAllowOutsideDevelopment: true

catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, API, Resource, Location]
  locations:
    # Local example data, file locations are relative to the backend process, typically `packages/backend`
    - type: file
    # The file path needs modified to be direct path due to the files being in a different location in the Docker container
      target: ./examples/entities.yaml

    # Local example template
    - type: file
    # The file path needs modified to be direct path due to the files being in a different location in the Docker container
      target: ./examples/template/template.yaml
      rules:
        - allow: [Template]

    # Local example organizational data
    - type: file
    # The file path needs modified to be direct path due to the files being in a different location in the Docker container
      target: ./examples/org.yaml
      rules:
        - allow: [User, Group]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After these changes, you will need to rebuild the image, and our workflow does this for you as soon as you tag it! So make sure to specify the new tag in the deployment yaml.&lt;/p&gt;

&lt;p&gt;Now we are not done yet, because we still need to set the user and password for Postgres and give Backstage a GitHub token, but storing secrets in Git is very insecure unless you take measures to encrypt it. That's why we will be using Bitnami’s SealedSecrets to store the encrypted secret in Git. This &lt;a href="https://medium.com/@abdullah.devops.91/how-to-use-sealed-secrets-in-kubernetes-b6c69c84d1c2" rel="noopener noreferrer"&gt;article&lt;/a&gt; explains Sealed Secrets more in depth.&lt;/p&gt;

&lt;p&gt;We first need the Kubeseal client tool, which we can get via Homebrew on Mac.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install github.com/bitnami-labs/sealed-secrets/cmd/kubeseal@main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the controller into our cluster so it can decrypt the secrets. Make sure to check if you have the latest version &lt;a href="https://github.com/bitnami-labs/sealed-secrets/releases" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.28.0/controller.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, fill in &lt;em&gt;manifests/base/postgres-secrets.yaml&lt;/em&gt; with a password and username of your choice in base64. *Make sure not to commit this*&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "backstage" | base64
echo "posgres-password" | base64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make a GitHub Token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Git Account Settings &amp;gt; Developer Settings &amp;gt; Personal Access Tokens tab &amp;gt; Tokens (classic)&lt;/p&gt;

&lt;p&gt;The token requires these permissions:&lt;/p&gt;

&lt;p&gt;Reading software components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reading organization data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read:org&lt;/li&gt;
&lt;li&gt;read:user&lt;/li&gt;
&lt;li&gt;user:email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Publishing software templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repo&lt;/li&gt;
&lt;li&gt;workflow (if templates include GitHub workflows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then of course base64 encode this and place it as the value of GITHUB_TOKEN in &lt;em&gt;manifests/base/backstage-secrets.yaml.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next we use kubeseal to encrypt our secrets and these can be committed in Git!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubeseal &amp;lt; ./manifests/base/backstage-secrets.yml &amp;gt; ./manifests/base/backstage-sealedsecret.yml
kubeseal &amp;lt; ./manifests/base/postgres-secrets.yml &amp;gt; ./manifests/base/postgres-sealedsecret.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can watch Argo in action!&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting up ArgoCD and GitHub
&lt;/h4&gt;

&lt;p&gt;Now that we can access the ArgoCD UI, navigate to the settings panel on the left menu and click ‘Repositories’. We can setup a connection to our GitHub account and let ArgoCD deploy apps in our cluster!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4txtecyjfmulwzmo3jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4txtecyjfmulwzmo3jj.png" width="800" height="331"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Use an https connection to avoid SSH setup, though you can configure that in a more complex setup[&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next we setup an application on the main screen by hitting the “Create Application” button. Name the application and select the default project. Then you should choose the git repository we just added and the manifests directory as the path parameter. Use the default cluster (this kind cluster) and backstage as the namespace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbxgt7skcsn8mkoak1uc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbxgt7skcsn8mkoak1uc.png" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see your new backstage application pop up as out of sync, when you manually sync it, it will deploy to your local cluster! Check it out by port forwarding the service like we did ArgoCD this time on &lt;a href="http://localhost:7077" rel="noopener noreferrer"&gt;http://localhost:7077&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward svc/backstage -n backstage 7077:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Integrating Cookiecutter and Backstage
&lt;/h4&gt;

&lt;p&gt;Now let’s show off Backstage’s software template skills. We need to modify our backstage app to use Cookiecutter instead of the built in templating engine — as Cookiecutter is a bit more powerful of a project. Then we will be able to use this template to create a basic React app and deploy it to the cluster!&lt;/p&gt;

&lt;p&gt;To install Cookiecutter in our scenario, we will need to add it to our docker image in &lt;em&gt;packages/backend/Dockerfile&lt;/em&gt; just under line 30 the previous install dependency_._&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#previous install ...

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y python3 python3-pip
RUN pip3 install cookiecutter

#before USER Node declared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backstage uses “plugins” to extend functionality to the main application, and Cookiecutter is needed as a plugin to correctly parse the template when using it in our software templates. We will add the plugin by following the &lt;a href="https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-cookiecutter/README.md" rel="noopener noreferrer"&gt;readme&lt;/a&gt; steps in Backstage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-cookiecutter

// In packages/backend/src/index.ts
const backend = createBackend();
// ...
backend.add(import('@backstage/plugin-scaffolder-backend'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-cookiecutter'));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create our Cookiecutter template that will let us easily spin up a React app in &lt;em&gt;examples/template/content/ .&lt;/em&gt; I have a complete template available in &lt;a href="https://github.com/joshbrgs/my-backstage/tree/main/examples/template/content" rel="noopener noreferrer"&gt;my repository&lt;/a&gt; that essentially consists of the basic create-react-app command, manifests, a GitHub workflow modified with Cookiecutter jinja syntax, and manifests for a Pull Request to our Backstage app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd examples/template/
rm -rf content/
mkdir -p content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this URL: &lt;a href="https://github.com/joshbrgs/my-backstage/tree/main/examples/template/content" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/my-backstage/tree/main/examples/template/content&lt;/a&gt;/&lt;/p&gt;

&lt;p&gt;You can go to &lt;a href="https://download-directory.github.io/" rel="noopener noreferrer"&gt;https://download-directory.github.io/&lt;/a&gt; and download my specific template for React using Cookiecutter. Then unzip it into your new content directory.&lt;/p&gt;

&lt;p&gt;Now we can edit the example template (examples/template/template.yaml) backstage provided us to create a boilerplate React App with Cookiecutter and an action to create a pull request to our Backstage’s manifest directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
  name: react-template
  title: React Template
  description: A React template that creates a basic React app with Cookiecutter
spec:
  owner: user:guest
  type: service

  # These parameters are used to generate the input form in the frontend, and are
  # used to gather input data for the execution of the template.
  parameters:
    - title: Let's React to something
      required:
        - project_name
        - github_username
      properties:
        project_name:
          title: Name
          type: string
          description: Unique name of the app
          ui:autofocus: true
          ui:options:
            rows: 5
        github_username:
          title: Github Username
          type: string
          description: Your github username
          ui:autofocus: true
          ui:options:
            rows: 5

  # These steps are executed in the scaffolder backend, using data that we gathered
  # via the parameters above.
  steps:
    # Each step executes an action, in this case one templates files into the working directory.
    - id: fetch-base
      name: Fetch Base
      action: fetch:cookiecutter
      input:
        url: ./content
        values:
          project_name: ${{ parameters.project_name }}
          github_username: ${{ parameters.github_username }}

    # This step publishes the contents of the working directory to GitHub.
    - id: publish
      name: Publish
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: This is ${{ parameters.project_name }}
        repoUrl: github.com?repo=app-${{ parameters.project_name|replace(" ", "-")|replace("_", "-")|replace(".", "-")|lower|trim }}&amp;amp;owner=${{ parameters.github_username }}
        sourcePath: ./app-${{ parameters.project_name|replace(" ", "-")|replace("_", "-")|replace(".", "-")|lower|trim }}
        defaultBranch: main
        repoVisibility: public
    # The final step is to register our new component in the catalog.
    - id: register
      name: Register
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'
        optional: true

    - id: app
      name: ArgoCD App
      action: publish:github:pull-request
      input:
        repoUrl: github.com?repo=my-backstage&amp;amp;owner=${{ parameters.github_username }}
        branchName: argocd-app-${{ parameters.project_name|replace(" ", "-")|replace("_", "-")|replace(".", "-")|lower|trim }}
        targetBranchName: main
        title: Add ArgoCD App for ${{ parameters.project_name }}
        description: |
          This PR adds an ArgoCD App for ${{ parameters.project_name }}.
          It will deploy the app to the `argocd` namespace.
        commitMessage: Add ArgoCD App for ${{ parameters.project_name }}
        sourcePath: ./manifests/
        targetPath: ./manifests/
        update: true
      output:
        remoteUrl: ${{ steps['app'].output.remoteUrl }}

  # Outputs are displayed to the user after a successful execution of the template.
  output:
    links:
      - title: Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then save the project and tag a new version for Backstage to be published with our new template!&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Backstage
&lt;/h4&gt;

&lt;p&gt;Lets log into Backstage and try out our new software template in the “Create” Tab. Click “Choose” on your React template and fill out the form with your app name and GitHub username and review your inputs in the next step. When you hit create you should now be able to see the component service diagram and in your GitHub account you can see a new repository with a React app in it! The GitHub workflow would have kicked off and built the image for this app too. In your backstage repository, you will find a PR for an application kind for ArgoCD.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4isiejp2z3dm1bci2g9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4isiejp2z3dm1bci2g9.png" width="800" height="543"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ArgoCD App PR from Backstage&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and merge this and watch Argo pick up what happened and deploy your new app in the Kind cluster!&lt;/p&gt;

&lt;p&gt;You can also port forward your new app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward svc/&amp;lt;your service name&amp;gt;-service -n argocd 8090:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0rs9uwxlq1ez7xw0aey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0rs9uwxlq1ez7xw0aey.png" width="800" height="509"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My new app … an-app&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now you are ready to ship fast!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In a real-world production setup, we’d tweak our configurations to be more battle-ready. ArgoCD should flex in High Availability mode, rock a declarative setup, and keep things secure with SAML or OAuth authentication. Backstage? Same deal — OAuth or SAML for auth, a clean separation between backend and frontend for better scaling, and a managed database instead of a container for some extra horsepower. And while bundling Cookiecutter templates with the app is handy, keeping them in a separate repo makes version control and managing bigger templates way smoother.&lt;/p&gt;

&lt;p&gt;Standardizing project templates and operations isn’t just a nice-to-have — it’s a game-changer. It gives developers the freedom to choose their tools while making sure everything stays aligned with company standards. Imagine Backstage as your Internal Developer Platform (IDP), packed with CDKs for infrastructure magic, streamlined Day 2 ops like incident management and user offboarding, and a turbocharged feedback loop that gets changes out the door faster. The result? Happier devs, smoother workflows, and a team that ships like pros. 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://backstage.io/" rel="noopener noreferrer"&gt;Backstage Software Catalog and Developer Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Argo CD - Declarative GitOps CD for Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cookiecutter.io/" rel="noopener noreferrer"&gt;Cookiecutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>platformengineering</category>
      <category>argocd</category>
      <category>backstage</category>
      <category>automation</category>
    </item>
    <item>
      <title>The Buzz About Platform Engineering</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Wed, 22 Jan 2025 14:42:00 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/the-buzz-about-platform-engineering-2n8k</link>
      <guid>https://forem.com/joshtheburgess/the-buzz-about-platform-engineering-2n8k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ijbiol8t1uswhkjtzbm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ijbiol8t1uswhkjtzbm.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Generated by FLUX.1 [Dev] by Black-Forest-Labs&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS re: Invent 2024 Recap
&lt;/h4&gt;

&lt;p&gt;A month has gone by already, and I am sitting at my desk finally distilling the whirlwind of information that comes from a great conference like AWS re:Invent. Rather than revisit widely discussed announcements, because I know there are plenty of great authors who have already covered those subjects, I’ll share the key trends and practical lessons I gleaned from the sessions and chalk talks I attended.&lt;/p&gt;
&lt;h3&gt;
  
  
  First Big Trend: Generative AI (GenAI)
&lt;/h3&gt;

&lt;p&gt;Bet you cannot guess it — Generative AI (GenAI). I don’t think it comes as much of a shock to you based on the title graphics, but I am a big fan of generative artificial intelligence. It has been improving at an astounding rate, and there are many use cases for it in various industries to help augment how we currently work. Think of it as a more advanced autocomplete since it is really just guessing at the next sequence of tokens that will best complete your prompt.&lt;/p&gt;
&lt;h4&gt;
  
  
  Foundation Models
&lt;/h4&gt;

&lt;p&gt;Amazon Bedrock is the highlighted service in this domain. It helps users of this service start developing with GenAI within minutes. It is an Amazon-managed serverless service that gives you access to leading AI companies’ Foundation Models (FMs). You can test which model best fits your use case and then continue to privately customize it with fine-tuning or using Retrieval Augmented Generation (RAG). To add to this RAG technique, Bedrock offers Knowledge Bases. It is better to read how AWS describes this product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Knowledge Bases for Amazon Bedrock automates the complete RAG workflow, including ingestion, retrieval, prompt augmentation, and citations, removing the need for you to write custom code to integrate data sources and manage queries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Worried about what an AI agent might say to a customer? Guardrails for Amazon Bedrock can help you establish predefined answers or omissions if you want to guard a user from trying to abuse the AI agent.&lt;/p&gt;

&lt;p&gt;One of the niceties that Bedrock provides is the wide range of FMs from AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI, and more, all available to interact with under a simple API.&lt;/p&gt;
&lt;h4&gt;
  
  
  Serverless Approach
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1esoj3dlxqnq7sth2s22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1esoj3dlxqnq7sth2s22.png" width="800" height="316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Simple Serverless GenAI application&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Remember when I said Bedrock was serverless a minute ago? When you combine that with all the other fun serverless services from AWS, you can create a pretty easily managed infrastructure with GenAI capabilities. I linked the same workshop I participated in down in the references if you would like to try it yourself. The UI is served via S3 behind a CDN, while the API Gateway takes the request from the client and forwards it to the appropriate Lambda function. This function contains our HTTP handler and business logic to convert the prompt into the desired form when interacting with our FM. Then the response can be sent back to the client as a payload or via stream if we wanted something similar to a chatbot experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  Second Trend: Platform Engineering
&lt;/h3&gt;

&lt;p&gt;This could be tied solely to the path I carved at re:Invent, but I was surprised to see how many sessions were talking about Platform Engineering. A lot of the higher-level technical talks I attended discussed the ability to support standardized development without needing to sacrifice the autonomy of your teams. The below graphic points toward the trend of more autonomy meaning harder-to-control standardization and vice versa. Platform Engineering teams try to solve this dilemma by providing standardized templates and tools for teams to self-service from.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg5ebar2oic4wnb2fchr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg5ebar2oic4wnb2fchr.png" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56pj9xo8yfiaqi6qozz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56pj9xo8yfiaqi6qozz4.png" width="800" height="664"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How Platform Engineering can help achieve both Autonomy with Standardization&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This, of course, was a journey to get to the point of the Platform Engineering team’s role in a company. Traditional Ops had the role of hardware gurus and change management sticklers, while developers would compile programs to try and solve problems for the business or communities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqmv2ywsduwvl9ey0cgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqmv2ywsduwvl9ey0cgs.png" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we started introducing the shift-left movement, where Agile and DevOps began to shine a light on innovative techniques for shipping a product and its changes quickly. “asCode” solutions for infrastructure, integration, configuration, and policy (IaC, CasC, PAC) started taking the scene as they enabled developers to quickly configure how their apps should be running and delivered. The caveat to this approach was the fact that all these new tools were extra load on the developer to learn and maintain, and the management of the teams did not know how to standardize the tooling that the teams used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjvtoxtqu0hl8i9qy60h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjvtoxtqu0hl8i9qy60h.png" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key to this was the development of Platform Engineering teams. Teams of software engineers and architects help build and approve blueprints of “asCode” solutions and boilerplate projects that meet the standards of their organization’s needs. These allow the application teams to choose the right tools for the right job. All the previous overhead is abstracted into a self-serve system with a single pane of glass for management to easily control and evaluate the health of their teams.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nbk25qvx08o0jd11ivy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nbk25qvx08o0jd11ivy.png" width="800" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Evolution of “Shift Left” to Platform Engineering&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  GitOps
&lt;/h4&gt;

&lt;p&gt;Now, the DevOps movement was covered pretty quickly in the last segment, and this topic is a derivative of that — GitOps. GitOps is still catching on in the industry as it shows a great way of controlling the state of your environments, whether it is runtimes, orchestrators, infrastructure, or cloud providers. The Git in GitOps refers to the technology behind the popular version control system, where one can make changes to the file, and it keeps a history of the changes in a version control system. GitOps is the practice of keeping the whole state recorded and mutable at the fingertips of the developer. All one needs to do is commit and push to the remote, where some program would react to the change and reconcile it. My favorite GitOps toolset comes from the Argo Project!&lt;/p&gt;
&lt;h4&gt;
  
  
  Backstage
&lt;/h4&gt;

&lt;p&gt;The Platform Engineering team’s main deliverable is an app referred to as the Internal Development Portal (IDP), which many organizations can create from scratch. It automates the process of interacting with other tools that the organization uses. One open-source project under the CNCF attempts to provide the community with a framework to build their own development portals — Backstage by Spotify. Backstage is a React app that has a vast amount of plugins to fit your needs and is extensible so you can make the application look and feel like it was born out of your company’s creative team. Backstage was a popular technology mentioned by big-name companies from HelloFresh to Toyota. They explained how it fast-tracked solutions to empower their teams and increased the ease of onboarding new engineers to the company.&lt;/p&gt;
&lt;h4&gt;
  
  
  GenAI Agents
&lt;/h4&gt;

&lt;p&gt;Toyota talked about the new way they are using GenAI agents in Backstage to help with tasks like consulting on solutions and even debugging the deployed solutions. The agent in Chaufer (their Backstage app’s name) uses the tech docs and catalogs as context through Retrieval Augmented Generation techniques. This allows the agent to know specifics about the software being used and current deployments. Developers can use it as a handy search for “What’s the best software template to use to build a three-tier web application?” and Chaufer will respond with suggestions and links to the templates hosted in Backstage.&lt;/p&gt;
&lt;h4&gt;
  
  
  Blueprints
&lt;/h4&gt;

&lt;p&gt;After someone was explaining the concept of Platform Engineering being a blueprint team, it clicked on how I can easily explain the benefit of a team such as this. The blueprints are the software templates that other teams use. When you use tooling like Terraform, Cookiecutter, and Argo, it allows you to streamline ideas to deployed concepts faster than developers could before. You can have apps and projects integrated into your ecosystems from day one. This allows experimentation and faster innovations as you enable your developers the autonomy to create features and keep them happily challenged while providing standardized quality.&lt;/p&gt;
&lt;h3&gt;
  
  
  Closing Thoughts
&lt;/h3&gt;

&lt;p&gt;AWS re:Invent 2024 reiterated the importance of developer experience and operational excellence in driving innovation. GenAI is no longer a novelty but a critical enabler of transformation, from content creation to real-time analytics. Meanwhile, Platform Engineering paves the way for frictionless collaboration, aligning business needs with technical execution.&lt;/p&gt;

&lt;p&gt;As we look to the future, the challenge isn’t just adopting these trends — it’s leveraging them strategically. Are your teams ready to embrace these innovations to not only stay competitive but to lead in an era defined by agility and intelligent systems?&lt;/p&gt;
&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;My Own Notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are some of the sessions and labs I went to, chalk talks are not recorded:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/0eGaa7EQkxM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://catalog.workshops.aws/eks-saas-gitops/en-US" rel="noopener noreferrer"&gt;Workshop Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/getting-started/hands-on/build-serverless-web-app-lambda-amplify-bedrock-cognito-gen-ai/" rel="noopener noreferrer"&gt;Build a Serverless Web Application using Generative AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/MEr5maa1aK4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/AAwa5gc1XNA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/tC8UEe51hgU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>platformengineering</category>
      <category>softwaredevelopment</category>
      <category>aws</category>
      <category>genaiforbusiness</category>
    </item>
    <item>
      <title>Scaling Team’s Ability to Collaborate Efficiently</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Mon, 18 Nov 2024 11:32:38 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/scaling-teams-ability-to-collaborate-efficiently-3pk4</link>
      <guid>https://forem.com/joshtheburgess/scaling-teams-ability-to-collaborate-efficiently-3pk4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatxv3gt74ahju5cbb5fc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatxv3gt74ahju5cbb5fc.png" width="560" height="300"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from Micro Frontends post on martinfowler.com by Cam Jackson&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Architecting Distributed Systems
&lt;/h4&gt;
&lt;h4&gt;
  
  
  Micro-Frontends with Module Federation
&lt;/h4&gt;

&lt;p&gt;Creating a user interface that provides an exceptional user experience is challenging. You want your product to be memorable for the right reasons, but as your team and codebase grow, maintaining that positive impression can become difficult. Developers may inadvertently interfere with each other’s work, client-side state management can be overused — leading to performance issues and crashes on smaller devices — and overall developer experience can decline, causing team morale to drop.&lt;/p&gt;

&lt;p&gt;This scenario is common, largely due to the tools many teams rely on being treated as “Golden Hammers” — where every problem starts to look like a nail. One such tool many teams have grown accustomed to is the Single Page Application (SPA) libraries and frameworks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Single Page Application (SPA)
&lt;/h3&gt;

&lt;p&gt;A Single Page Application (SPA) is a way to serve one index.html, along with CSS and JavaScript files, to a browser (the client). It provides the full functionality of an application—handling form submissions, data changes, and URL navigation—without needing to request additional HTML or JavaScript files from the server. This is where the power of JavaScript and the virtual Document Object Model (DOM) comes into play. The virtual DOM enables fast rendering by only updating the parts of the document that have changed.&lt;/p&gt;

&lt;p&gt;For a deeper dive into this concept, check out this&lt;a href="https://medium.com/devinder/react-virtual-dom-vs-real-dom-23749ff7adc9" rel="noopener noreferrer"&gt;article comparing the virtual DOM and the traditional DOM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F875%2F0%2AOWjHCnHAPexE4lyP" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F875%2F0%2AOWjHCnHAPexE4lyP" width="875" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Popular SPA libraries like React, and frameworks such as Vue, Svelte, and Angular, dominate web development today. They excel in state management and interactivity. In React, for example, you might write something like this to handle state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const Counter = () =&amp;gt; {
  const [count, setCount] = useState(0)

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count+1)}&amp;gt;Increase&amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern can also allow us to call an API endpoint, retrieve data, and save it in state. However, as the app grows and we store more data in the same manner, we risk too much data being held in the browser's memory, which can strain the user's system. Chrome tabs can show you how much memory each page is consuming, often in megabytes. Teams can start modifying APIs other portions of the app rely on and cause downstream faults in the UI.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;micro frontends&lt;/strong&gt; shine. Inspired by microservices on the backend, the idea behind micro frontends is to break down the frontend monolith into smaller, independent applications that function cohesively in the user interface. Instead of relying on one massive codebase, each team owns and deploys a specific part of the frontend — say, checkout, user profile, or product display.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Micro Frontends Address SPA Limitations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Improved Scalability and Team Autonomy&lt;/strong&gt; : Each micro frontend operates independently, so developers can deploy new features or fixes without worrying about breaking the entire application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimized Resource Management&lt;/strong&gt; : By only loading the necessary parts of the application, micro frontends can reduce browser memory usage, particularly beneficial for users on low-powered devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technology Flexibility&lt;/strong&gt; : Teams aren’t locked into one SPA framework. They can choose the best tools for each module, such as React for a highly interactive component or vanilla JavaScript for simpler parts.&lt;/p&gt;

&lt;p&gt;Continuing with the example above of an e-commerce site, we can structure our frontends to accommodate flexibility in technology and improve collaboration through well-defined APIs and communication channels. Teams have to be able to communicate effectively when working in separate Software Development Lifecycles (SDLCs) yet building an app that looks and feels cohesive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ipssxvwrkt429kbms08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ipssxvwrkt429kbms08.png" width="800" height="516"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Checkout domain can be handled by a team or couple developers and so on.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you have decided to try out a micro-frontend approach, you have the choice of figuring out how to implement it. This is your decision overall, but there are some common patterns and approaches that may help your choice of implementation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Micro-Frontend Patterns
&lt;/h3&gt;

&lt;p&gt;There are several key patterns in micro-frontend architecture, each with unique approaches to organizing and deploying frontend code.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Module Federation
&lt;/h3&gt;

&lt;p&gt;Module Federation is a pattern introduced with Webpack 5 (&lt;em&gt;thank you&lt;/em&gt; &lt;a href="https://scriptedalchemy.medium.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Zack Jackson&lt;/em&gt;&lt;/a&gt;), allowing JavaScript applications to share modules at &lt;em&gt;runtime&lt;/em&gt;. This enables applications to dynamically load code from other apps, making it ideal for micro-frontends where separate teams work on distinct parts of an application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each micro frontend can expose components or modules, allowing other parts of the app to dynamically import them at runtime.&lt;/li&gt;
&lt;li&gt;Teams can update specific micro frontends independently without requiring full app redeployment.&lt;/li&gt;
&lt;li&gt;Module Federation supports version management, so if two components need different versions of the same library, they can load the appropriate version separately&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Iframe-Based Isolation
&lt;/h3&gt;

&lt;p&gt;Using iframes is one of the simplest approaches to micro frontends. Each micro frontend is loaded within an iframe, providing natural isolation since iframes are sandboxed already (throwback to integrating checkout forms, technically you implemented a micro-frontend, congrats).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each feature or micro-frontend serves as an independent application and is embedded in the main app using iframes.&lt;/li&gt;
&lt;li&gt;Communication between the main app and iframes typically occurs via postMessage or a shared API.&lt;/li&gt;
&lt;li&gt;This method ensures strong encapsulation, as each micro frontend has its own DOM and CSS context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited styling and UX customization due to iframe boundaries.&lt;/li&gt;
&lt;li&gt;Can impact performance, especially with multiple iframes on the same page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applications with loosely connected micro frontends, like content from third-party services or specialized tools, do not need heavy integrations like some of the other patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Web Components
&lt;/h3&gt;

&lt;p&gt;Web Components provide a standardized way to create encapsulated, reusable components using HTML, CSS, and JavaScript. They are supported natively in most modern browsers, allowing teams to build micro frontends independently of specific frameworks. These are great for modular design systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each team creates frontend components as custom elements, which can be registered and used throughout apps.&lt;/li&gt;
&lt;li&gt;Styles and logic within Web Components are encapsulated, so each micro frontend has its own CSS and JavaScript environment.&lt;/li&gt;
&lt;li&gt;Framework-agnostic, so each team can choose its preferred tech stack.&lt;/li&gt;
&lt;li&gt;Allows for strong encapsulation with shadow DOM, making it easier to prevent style and script conflicts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building complex applications with Web Components can be challenging without dedicated tooling and libraries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. Client-Side Composition
&lt;/h3&gt;

&lt;p&gt;In client-side composition, the main application orchestrates and loads micro frontends directly in the browser, often through APIs or dynamic imports. Think about separate routes being for the different SPAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main application makes API calls to load micro frontends at runtime based on routes or other user interactions.&lt;/li&gt;
&lt;li&gt;Often combined with Module Federation or Web Components to dynamically import the right code.&lt;/li&gt;
&lt;li&gt;Client-side routing libraries, like React Router, can help determine which micro frontend to load based on URL or application state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds complexity to the client-side logic.&lt;/li&gt;
&lt;li&gt;It can increase initial load times if not optimized with lazy loading or code splitting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  5. Server-Side Composition (Edge-Side Includes)
&lt;/h3&gt;

&lt;p&gt;In server-side composition, each micro frontend is rendered on the server before being delivered to the client. Techniques like Edge-Side Includes (ESI) are used to assemble different parts of the app at the CDN or server level, reducing client-side load. This is useful for content-heavy applications where micro frontends contain static or infrequently updated content, like blogs and news sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server or CDN pulls in HTML fragments from different micro frontends and assembles them into a single response for the client.&lt;/li&gt;
&lt;li&gt;Faster initial load times and improved SEO, as content is rendered on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires additional server setup and configuration.&lt;/li&gt;
&lt;li&gt;It is not ideal for highly interactive or state-heavy applications, as updates rely on server-side changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these patterns suits different needs, depending on your application’s architecture, team structure, and desired level of independence for each micro frontend, they can set your frontend up for large-scale loads while keeping a good user experience and dev experience.&lt;/p&gt;

&lt;p&gt;These do not have to be for large complex teams, a great example of this is in HelloFresh’s retelling of implementing micro-frontends:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At first, we found development speed to be a bit slow. Since this approach requires you to own your entire project E2E, the learning curve can be steep. But after cresting the curve we were able to push out projects like never before; the new &lt;a href="https://www.hellofresh.com/shop/" rel="noopener noreferrer"&gt;HelloFresh shop&lt;/a&gt; for example took 3 people 4 weeks to roll out. Our developers also claim increased confidence and breadth of knowledge after adopting this approach. ~ &lt;a href="https://engineering.hellofresh.com/front-end-microservices-at-hellofresh-23978a611b87" rel="noopener noreferrer"&gt;Front-end Microservices at HelloFresh&lt;/a&gt;; &lt;a href="https://medium.com/@pepijns?source=post_page---byline--23978a611b87--------------------------------" rel="noopener noreferrer"&gt;Pepijn Senders&lt;/a&gt; Published in &lt;a href="https://engineering.hellofresh.com/?source=post_page---byline--23978a611b87--------------------------------" rel="noopener noreferrer"&gt;HelloTech&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;We are going to be implementing a module federation pattern for our front-end microservices. Let's create an application that is separated by domains and load the modules or components in at &lt;em&gt;runtime&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;You will need some specific tooling installed if you would like to run this demo locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Local Development Demo
&lt;/h4&gt;

&lt;p&gt;I was inspired by &lt;a href="https://module-federation.io/practice/overview.html" rel="noopener noreferrer"&gt;Module Federation&lt;/a&gt;. They have great guides and examples and how to set up your apps to use Module Federation with plenty of Frameworks that use Webpack, Vite, or RsPack.&lt;/p&gt;

&lt;p&gt;Let's make a Host app (React) and a Remote app (Vue).&lt;/p&gt;

&lt;p&gt;To get started we can run a handy script from Jack Herrington npx create-mf-app for both the Vue app and the React Host app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let’s create a Card element in the host
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'
import ReactDOM from 'react-dom/client'

import './index.scss'

const App = () =&amp;gt; (
      &amp;lt;div className="flex items-center justify-center min-h-screen bg-gray-200"&amp;gt;
      &amp;lt;div className="relative p-8 bg-white border-4 border-black shadow-[8px_8px_0_0_rgba(0,0,0,1)]"&amp;gt;
        &amp;lt;h1 className="text-2xl font-bold text-black"&amp;gt;Neo Brutalist Card&amp;lt;/h1&amp;gt;
        &amp;lt;p className="mt-4 text-black"&amp;gt;
          This is an example of a Neo Brutalist card styled with bold borders,
          stark contrasts, and a playful shadow effect.
        &amp;lt;/p&amp;gt;

      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
)
const rootElement = document.getElementById('app')
if (!rootElement) throw new Error('Failed to find the root element')

const root = ReactDOM.createRoot(rootElement as HTMLElement)

root.render(&amp;lt;App /&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now this looks nice, but we need a button that does some mind-blowing functionality. I needed to add Vue and HTML to the tailwind config for the styling to work in the app. In the remote app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//src/counter.vue

&amp;lt;template&amp;gt;
    &amp;lt;button
      @click="increment"
      class="relative px-8 py-4 text-2xl font-bold text-black bg-white border-4 border-black shadow-[8px_8px_0_0_rgba(0,0,0,1)] hover:shadow-[12px_12px_0_0_rgba(0,0,0,1)] active:translate-x-2 active:translate-y-2 active:shadow-[4px_4px_0_0_rgba(0,0,0,1)] transition-transform duration-150"
    &amp;gt;
      Counter: {{ count }}
    &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from "vue";

export default {
  setup() {
    const count = ref(0);

    const increment = () =&amp;gt; {
      count.value++;
    };

    return {
      count,
      increment,
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
/* No additional styles needed; everything is styled with Tailwind CSS */
&amp;lt;/style&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class="flex items-center justify-center min-h-screen bg-gray-200"&amp;gt;
    &amp;lt;NeoBrutalistButton /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import NeoBrutalistButton from "./counter.vue";

export default {
  components: {
    NeoBrutalistButton,
  },
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the app with npm run startand see this awesome functionality. Now we need to use the actual Module Federation! We need to do some things with Vue first, creating a mounting component due to the rendering being different than how React renders, then we export it in rspack.config.js for our host app to consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//counterMounter.ts
import { createApp } from "vue";

import Counter from "./counter.vue";

export default (el) =&amp;gt; {
   createApp(Counter).mount(el);
}

//rspack.config.js
imports blah blah

const deps = require("./package.json").dependencies;
...

    plugins: [
        new VueLoaderPlugin(),
        new rspack.container.ModuleFederationPlugin({
            name: "remote",
            filename: "remoteEntry.js",
            exposes: {
                "./Counter": "./src/counterMounter",
            },
            shared: {
                ...deps,
            },
        }),

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now restart the dev server. There should be a new exposed file at &lt;a href="http://localhost:9000/remoteEntry.js" rel="noopener noreferrer"&gt;http://localhost:9000/remoteEntry.js&lt;/a&gt; and this is what our host app consumes!&lt;/p&gt;

&lt;p&gt;So in our host rspack.config.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    plugins: [
        new rspack.container.ModuleFederationPlugin({
            name: "host",
            filename: "remoteEntry.js",
            exposes: {},
            remotes: {
                remote: "remote@http://localhost:9000/remoteEntry.js",
            },
            shared: {
                react: { eager: true },
                "react-dom": { eager: true },
                "react-router-dom": { eager: true },
            },
        }),
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in our host application with updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/App.tsx
import './index.scss'
import Counter from "remote/Counter"

const App = () =&amp;gt; {
    const ref = useRef(null);

    useEffect(() =&amp;gt; {
        Counter(ref.current)
    }, [])

    return(
      &amp;lt;div className="flex items-center justify-center min-h-screen bg-gray-200"&amp;gt;
      &amp;lt;div className="relative p-8 bg-white border-4 border-black shadow-[8px_8px_0_0_rgba(0,0,0,1)]"&amp;gt;
        &amp;lt;h1 className="text-2xl font-bold text-black"&amp;gt;Neo Brutalist Card&amp;lt;/h1&amp;gt;
        &amp;lt;p className="my-4 text-black"&amp;gt;
          This is an example of a Neo Brutalist card styled with bold borders,
          stark contrasts, and a playful shadow effect.
        &amp;lt;/p&amp;gt;
        &amp;lt;div ref={ref} /&amp;gt; 
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
)};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get the complete code in my repo &lt;a href="https://github.com/joshbrgs/medium-tutorials" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/medium-tutorials&lt;/a&gt; in the micro-frontends directory.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploying
&lt;/h4&gt;

&lt;p&gt;With each being an independently deployable app, and the host consuming the modules, anything new with the remote app will change that portion of the host app at runtime! Teams can work in independent code bases and have their software development lifecycles while users see one cohesive application. You can try this for yourself locally or deploy to s3. Change one application and reload the page after it is deployed (or saved #HMR locally), you will see the changes immediately!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F200%2F0%2Ar1WGt9UgKhpF48nO" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F200%2F0%2Ar1WGt9UgKhpF48nO" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Engineering an application is a game of trade-offs. Each pattern has its pros and cons, and it comes down to figuring out which tradeoffs you make that will allow you to keep the extensibility of your programs without putting you into a corner that impedes your progress. Micro-frontends and their patterns may be a good fit to keep some options open for your UI’s extensibility, however, the common trade-off is sacrificing the speed of delivery shortly when you are setting up the architecture. I hope this introduced an easy way to incorporate micro-frontends into your next project!&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/microservices/" rel="noopener noreferrer"&gt;What are Microservices? | AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/articles/micro-frontends.html" rel="noopener noreferrer"&gt;Micro Frontends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infracloud.io/blogs/introduction-to-microfrontends/" rel="noopener noreferrer"&gt;Introduction to Microfrontends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://module-federation.io/guide/start/index.html" rel="noopener noreferrer"&gt;Introduction - Module federation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agile</category>
      <category>frontend</category>
      <category>react</category>
      <category>vue</category>
    </item>
    <item>
      <title>Stop Wasting Working Software</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Thu, 12 Sep 2024 19:55:31 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/stop-wasting-working-software-4bfe</link>
      <guid>https://forem.com/joshtheburgess/stop-wasting-working-software-4bfe</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz31coj68msbilu0wst0r.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz31coj68msbilu0wst0r.jpeg" width="800" height="1183"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from ByteByteGo&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Architecting Distributed Systems
&lt;/h4&gt;
&lt;h4&gt;
  
  
  Creating a Software Ecosystem with API Gateways, Service Meshes, and Message Queues Utilizing a Strangler Fig Pattern
&lt;/h4&gt;

&lt;p&gt;The field of software engineering is dynamic and constantly evolving. As we progress, the way we design and develop new systems also changes rapidly. Cloud Native systems are appealing to companies because of the pay-per-use pricing model, the agility it gives to release features fast, ease of scalability, and evolvability. This choice to lift and shift workloads to the cloud can also involve moving Legacy (applications or services that still need to be updated to fit the current engineering architecture of choice) systems to the cloud, which can hold great importance to the business. If we need to improve this system, it can be frustrating and challenging for engineers to revisit legacy code that might be inflexible due to its initial design. This can lead to headaches and delays in improving the services. The initial thought is — rebuild it and wait for the new one to be done. What if this does not have to be the case? This article is going to cover the Strangler Fig Pattern, Domain-Driven-Design to plan out application teams and services, and how choice of technology can help decoupling a large-scale system. I will be building out a sample project to help follow along with some of these concepts, and by the end of this article, you will have these tools running locally in a simple sample project.&lt;/p&gt;
&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;p&gt;If you would like to follow along with the tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker and optionally &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Clone my repo &lt;a href="https://github.com/joshbrgs/medium-tutorials/tree/stop-wasting-software" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/medium-tutorials/tree/stop-wasting-software&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Optionally &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Why do I want a Software Ecosystem?
&lt;/h4&gt;

&lt;p&gt;A software ecosystem is a pattern to reuse backend systems and services to help bootstrap the development of newer applications and features. If your company has a production-vetted service to process payments, why would you create a whole new one just yet?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;…every piece of knowledge must have a single, unambiguous, authoritative representation within a system. ~ The Pragmatic Programmer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As an engineer, you want to be a good steward of technology and not let broken windows (out of date code or functionality that does not need to exist) into your code base; so, you will want to re-engineer older services and phase them out in favor of your more optimized solution. An advantage of a well-architected ecosystem of services, you can A/B test the service to gradually introduce it. Let’s also be true to ourselves, it is often fast and easier to just start from scratch than it is to surgically remove important pieces of legacy code.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Strangler Fig Pattern
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m4qj2ct07mfdwmh9zno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m4qj2ct07mfdwmh9zno.png" width="800" height="453"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;AWS Prescriptive Guidance&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As business grows and your market’s interest change, you may want to release a new and improved application, which gives you a couple of common choices — cut-over rewrite or the strangler fig pattern. As engineers, we love rewriting legacy software to utilize new technologies (fka brownfield projects).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most important reason to consider a strangler fig application over a cut-over rewrite is reduced risk. A strangler fig can give value steadily and the frequent releases allow you to monitor its progress more carefully. ~ &lt;a href="https://martinfowler.com/bliki/StranglerFigApplication.html" rel="noopener noreferrer"&gt;Strangler Fig Application&lt;/a&gt; — Martin Fowler, 29 June 2004&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As the above illustration shows, the pattern is used to modernize legacy systems and keep applications decoupled, allowing a dev team to update pieces of the application without causing application wide issues. With low coupling, one can scale only the most intensive pieces of your application and plan how to engineer for failures within your application. The further benefits this microservice architecture achieves is polyglot programs and polyglot storage — the right tools for the right job.&lt;/p&gt;
&lt;h4&gt;
  
  
  Domain-Driven-Design
&lt;/h4&gt;

&lt;p&gt;Domain-Driven Design (DDD) can be useful while designing a system, especially when introducing new technologies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Domain-Driven Design(DDD) is a collection of principles and patterns that help developers craft elegant object systems. Properly applied it can lead to software abstractions called domain models. These models encapsulate complex business logic, closing the gap between business reality and code. ~ &lt;a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/best-practice-an-introduction-to-domain-driven-design" rel="noopener noreferrer"&gt;Microsoft Learn: Best Practice — An Introduction To Domain-Driven Design&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;bad-inc.net is a fictional application that has recently gained a lot of popularity due to its innovative features, user-friendly applications, and seamless user experience for villainous deeds. It is a social board where villains can upload nemesis to see who they should watch out for and — buy merch. This has resulted in millions of daily active users (DAU), which has caused the company’s Engineering department to grow substantially. However, the department has started to face challenges in keeping up with demand and success. They are finding it difficult to release features promptly and distribute work effectively across the department. Initially, they tried dividing teams by large applications, but this did not solve the issue. They noticed multiple teams trying to solve the same problems and caused too-many-cooks-in-the-kitchen situation. Therefore, they have decided to apply Domain-Driven Design (DDD) principles to reorganize their teams and systems for better efficiency and productivity.&lt;/p&gt;

&lt;p&gt;To start, we define bounded contexts that delineate areas of interest. Here are the contexts we are concerned with in this article:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users
&lt;/li&gt;
&lt;li&gt;Notifications
&lt;/li&gt;
&lt;li&gt;Nemesis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small teams are formed when engineers pick the context they would enjoy contributing to, or fill in needed positions for those teams, this is when contexts become products. We have 2 teams at Bad, inc. (Team Evil and Team Platypus) which means a couple of teams may have more than one product to work on. This will decouple the application, encourage engineers to innovate solutions, and allow teams to work at their own pace, unaffected by other teams, to an extent. Here is the overview of the projects and services without implementation details. The dotted borders represent the bounded context, where the service should only care about meeting the needs of such context, the service is declared inside. The dotted arrows represent asynchronous communication across contexts, while the full arrows represent synchronous communication such as gRPC or REST.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff06lcnibrg565qvyhbzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff06lcnibrg565qvyhbzb.png" width="800" height="767"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DDD lets the engineers decide what implementations may be best like microservices vs monoliths&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After applying DDD, we have disparate systems, so how do these stitch together to create the application the users want? Let’s take a closer look at the technology that can be implemented to help integrate these systems!&lt;/p&gt;
&lt;h4&gt;
  
  
  API Gateways
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpj6mf6ci9t3gnp94dhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpj6mf6ci9t3gnp94dhp.png" width="545" height="491"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;API Gateways from Cloud Providers like AWS extend capabilities to allow interactions directly to DBs and many other services without intermediaries!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Application Programming Interface (API) Gateways act as a central Uniform Resource Identifier (URI) for your backend services. This allows client-side apps to interface with the single URI to communicate with the services needed, the Gateway will direct the requests to the service that is declared for that route such as api.mygateway.com/service1. If you have some experience with load balancers, ingresses, or reverse proxies, this behavior seems similar, and it is; however, API Gateways use service discovery techniques to decouple the Gateway from the services and tend to be more feature-rich.&lt;/p&gt;

&lt;p&gt;API Gateways often provide other benefits such as Authentication and Authorization. You can verify JSON Web Tokens (JWT) that help identify the user sending this request to the API Gateway. Instead of concerning other services with business logic for Auth, or using another service to verify the credentials, the Gateway lifts this burden and keeps services focused on a single point of responsibility.&lt;/p&gt;

&lt;p&gt;Other common API Gateway abilities are:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate Limiting
&lt;/li&gt;
&lt;li&gt;Caching responses
&lt;/li&gt;
&lt;li&gt;Circuit Breaking, route requests to the next best service if one is down
&lt;/li&gt;
&lt;li&gt;Built-in logging and traffic traces for debugging the application
&lt;/li&gt;
&lt;li&gt;Protocol conversion, allowing RPC, SOAP, and GraphQL protocols to be used together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest advantage of an API Gateway is that it decouples your system and even allows building &lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig" rel="noopener noreferrer"&gt;strangler patterns&lt;/a&gt; around older systems to convert them to more modern tech stacks!&lt;/p&gt;
&lt;h4&gt;
  
  
  Communication Between Services and Message Queues
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9yecnh5rmshbb55b89w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9yecnh5rmshbb55b89w.png" width="800" height="300"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image From Shoham Roditi on &lt;a href="https://memphis.dev/" rel="noopener noreferrer"&gt;https://memphis.dev/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An API Gateway allows outside communication to your backend system, but what if Team Evil needs its backend services to communicate with Team Platypus’s services? This can create a coupling between the services and dependency on two contexts. Instead, we can use Message Queues to ensure our services can stay decoupled. Message Queues and Message Brokers are two useful asynchronous tools for service-to-service communication, both involve event producers and have event consumers, but queues rely on the consumer to poll the event and remove it from the queue when processed, while brokers will push the event to a consumer. This pattern is commonly known as a publisher/subscriber pattern.&lt;/p&gt;

&lt;p&gt;A common practice is to create REST endpoints for your services and enable your services to call them directly. Although there is nothing wrong with this approach, you will need to start thinking about your services’ ability to handle the load of numerous service requests for the same task and create synchronous communication between your backend services. This can lead to coupled services, where if one service goes down, your app can be crippled.&lt;/p&gt;

&lt;p&gt;This is a topic for another article, but another potential tool for this situation is a Stream. Streams also use the pub/sub pattern and can be used for real-time applications. A couple of popular choices would be Kafka and AWS Kinesis. These Streams can be highly performant and scalable with the cost of overhead.&lt;/p&gt;
&lt;h4&gt;
  
  
  A Local Look At The Tools in Action
&lt;/h4&gt;

&lt;p&gt;I created a small application that is overly complex, where in a real-life scenario you may want to make this as a monolith, but for this demonstration, I wanted to show off these fun tools to help decouple services! We will not get too in-depth with Go or the business logic behind the application. If you are interested, please check out my GitHub repo and other helpful resources like &lt;a href="https://go.dev/doc/tutorial/workspaces" rel="noopener noreferrer"&gt;go.dev&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/joshbrgs/medium-tutorials.git
cd medium-tutorials
cd stop-wasting-working-software

cp .env.example .env

docker compose up -d rabbitMq
docker compose up -d kong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have just spun up some docker containers for a message queue (Rabbit MQ) and an API gateway (Kong). You can check these out now on &lt;a href="http://localhost:15672" rel="noopener noreferrer"&gt;http://localhost:15672&lt;/a&gt; (password and user are both “guest” by default) and &lt;a href="http://localhost:8002" rel="noopener noreferrer"&gt;http://localhost:8002&lt;/a&gt; respectively. You will not see much traffic at first because we have not started up the app completely! Run this command one last time and see how Kong uses autodiscovery for the ports and services, and meanwhile, the applications that need the queue, have the appropriate connection made within their code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the complete application should be running in docker containers. Communication will happen when you start to use the API.&lt;/p&gt;

&lt;p&gt;The applications using gRPC could also use gRPC with the API Gateway. I have the containers running as private workloads for internal communication only, no public user can use the gateway to invoke the services. In a production scenario, the endpoints of these services would not be exposed like they are with Docker at the moment, only the gateway. Use Postman or another service that can communicate with an API to interact with Bad Inc.’s new application!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to use something like&lt;/em&gt; &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Postman&lt;/em&gt;&lt;/a&gt; &lt;em&gt;instead if you do not want to just curl the app. I have included a Postman collection with the repository! Just import it and it will even handle storing the JWT in the header for you.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Getting a 401 due to not being authorized, the Gateway uses our Auth service as the
#keeper to guard our APIs from the Heros
curl http://localhost:8000/notify

#Create User
curl -X POST http://localhost:8000/create-user -H 'Content-Type: application/json'
   -d '{"username":"mrmean","password":"my_password"}'

#Login 
curl http://localhost:8000/login -u "mrmean:my_password"

#Replace the token part with the JWT returned back from logging in

#Create Nemesis
curl -X POST http://localhost:8000/nemesis -H "Authorization: Bearer {token}" -H 'Content-Type: application/json' -d '{"Id": 1, "Nemesis": "Perry Bond", "Power": 8}'

#List Nemesis
curl http://localhost:8000/nemesis -H "Authorization: Bearer {token}"

#List Notifications
curl http://localhost/notify -H "Authorization: Bearer {token}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you watch the RabbitMq’s dashboard, you should be able to find that the application is populating the queue. This is giving an example of Protocol conversion of http to gRPC, a central Auth service protecting routes unless authenticated, and the services using a message queue for inter service communication (you can message over JSON payloads not just strings). Feel free to look at Kong’s UI and investigate possibilities an API Gateway can do for you!&lt;/p&gt;

&lt;h4&gt;
  
  
  Bonus: Service Meshes
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokfwjrqgubov4zz3vt8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokfwjrqgubov4zz3vt8v.png" width="800" height="453"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Picture from blog By William Morgan on CNCF&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Service Meshes are similar to API Gateways in that they can provide circuit breaker patterns, tracing, and traffic management rules; however, unlike API Gateways, Service Meshes have a central controller layer and a distributed data layer, reducing the likelihood of a Single Point of Failure. By leveraging the network, Service Meshes make service-to-service communication easier, and easily enable mTLS (mutual Transport Layer Security), a security measure that businesses usually prefer. While Service Meshes can create a large amount of complexity, it can also provide significant benefits. It’s important to note that we don’t have to choose between Service Meshes, Message Queues, and API Gateways. Sometimes scenarios may call for all of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Architecting decoupled applications can be a difficult game of trade-offs. Tooling has constantly evolved in the industry trying to lift the burden of interservice communication and empowering teams to develop evolvable and scalable services. Message Queues help provide a pub/sub pattern for your services to communicate without necessarily depending on each other, while API Gateways and Meshes provide an orchestration of traffic flow between your services. If you want some services with less overhead, the majority of Cloud Providers offer these services as well, which fully integrate into the other services like Lambda functions and Elastic Kubernetes Service!&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/BoundedContext.html" rel="noopener noreferrer"&gt;bliki: Bounded Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.uber.com/blog/kafka-async-queuing-with-consumer-proxy/" rel="noopener noreferrer"&gt;Enabling Seamless Kafka Async Queuing with Consumer Proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://memphis.dev/blog/what-is-a-message-queue/" rel="noopener noreferrer"&gt;Memphis.dev - What is a message queue?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.traefik.io/traefik/" rel="noopener noreferrer"&gt;Traefik Proxy Documentation - Traefik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rabbitmq.com/getstarted.html" rel="noopener noreferrer"&gt;RabbitMQ Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.konghq.com/gateway/latest/" rel="noopener noreferrer"&gt;Kong Gateway | Kong Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-decomposing-monoliths/strangler-fig.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-decomposing-monoliths/strangler-fig.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>domaindrivendesign</category>
      <category>microservices</category>
      <category>docker</category>
      <category>api</category>
    </item>
    <item>
      <title>Demystifying AI of Your Own</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Tue, 23 Jul 2024 01:56:11 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/demystifying-ai-of-your-own-p8f</link>
      <guid>https://forem.com/joshtheburgess/demystifying-ai-of-your-own-p8f</guid>
      <description>&lt;h4&gt;
  
  
  Adopting RAG AI apps — Spoiler: Your Data is Still Your Own
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AhXTOi0pJ5X2b2AGO" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AhXTOi0pJ5X2b2AGO" width="1024" height="895"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Mohamed Nohassi on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Artificial intelligence would be the ultimate version of Google. The ultimate search engine that would understand everything on the web. It would understand exactly what you wanted, and it would give you the right thing. We’re nowhere near doing that now. However, we can get incrementally closer to that, and that is basically what we work on. — Larry Page&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Artificial Intelligence (AI) has revolutionized numerous industries, and 2024 is shaping up to be a pivotal year for Retrieval Augmented Generation (RAG) due to its rapid adoption and widespread discussion. In this article, I aim to explain the significance of RAG and provide a brief tutorial on how to implement RAG using a local Large Language Model (LLM), ensuring your data remains secure. LLMs, like &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;GPT-4&lt;/a&gt;, have revolutionized many applications, from customer service chatbots to complex data analysis. Yet, their limitations highlight the need for advancements like RAG.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why RAG?
&lt;/h3&gt;

&lt;p&gt;To understand RAG, it’s crucial to grasp how AI, particularly Large Language Models (LLMs), are trained and the limitations they face. Machine Learning, a discipline that has been evolving for decades, encompasses AI as a subset. AI’s recent focus has been on LLMs due to their remarkable ability to extract patterns from data and predict answers to input questions. These models use context to find the best fit for a query; however, if they lack sufficient context, they may confidently provide incorrect answers. This phenomenon occurs because LLMs are trained on a fixed dataset. Once an LLM is released, it stops training, which means it doesn’t stay updated with current events and might not provide specialized responses. When LLMs generate inaccurate or nonsensical answers with undue confidence, this is known as a hallucination.&lt;/p&gt;

&lt;p&gt;Hallucinations may be harmless and amusing when we’re testing how a Chat Agent will respond, but if you’re trying to quickly understand a new team proposal or a law under consideration, incorrect details from an LLM can harm your reputation. This is where Retrieval Augmented Generation (RAG) comes in to mitigate the hallucination issues inherent in LLMs. Retraining an LLM can be both time-consuming and expensive, but RAG offers a more efficient solution.&lt;/p&gt;

&lt;p&gt;RAG is a process where an LLM consumes documents and files as context to help answer queries with up-to-date data and provide more specialized responses. For example, &lt;a href="https://gemini.google.com/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; won’t know your Q3 sales information if it isn’t publicly available and included in its training data. RAG addresses hallucinations by enriching your query with relevant, current context.&lt;/p&gt;
&lt;h3&gt;
  
  
  I Know ChatGPT but I Am Scared To Give It My Data
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Every Business is a Software Business — Watts S. Humphrey&lt;/p&gt;

&lt;p&gt;If you’re not paying for the product, then you’re the product — The Social Dilemma.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s understandable to be cautious about freely sharing data, as it drives our business decision-making and distinguishes our business. Data is the product nowadays; it’s the story and presentation we craft that drive sales. One common misconception is that any LLM will automatically understand your data and take control out of your hands.&lt;/p&gt;

&lt;p&gt;However, cloud providers like GCP, AWS, and Azure offer base models and the capability to bring your own models, running them within your virtual private cloud (VPC). This approach allows you to continue training your models and ensures that your data stays secure and private, without exposure to unauthorized parties. Implementing a solution with an API Gateway can further safeguard your data. By leveraging these tools, your business can harness the power of LLMs without compromising data security.&lt;/p&gt;
&lt;h3&gt;
  
  
  RAGing on Your Setup
&lt;/h3&gt;

&lt;p&gt;In this tutorial, we will be building your own RAG agent. The code is available at my GitHub: &lt;a href="https://github.com/joshbrgs/medium-tutorials" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/medium-tutorials&lt;/a&gt;. This project consists of a model created with Python, LangChain, and Chroma, which will be provided via a REST API. It’s okay if you don’t fully understand this tech stack; I will walk you through the model part below.&lt;/p&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Getting and Testing an LLM
&lt;/h4&gt;

&lt;p&gt;Large Language Models (LLMs) have been such a hot-button topic lately, and most people do not know that some of these models are open-source software allowing people to modify and experiment with them. Models are trained and deployed to Model Zoos. Popular Zoos are &lt;a href="https://huggingface.co/models" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; and &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. I like both, but in this article, we will be using Ollama.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;https://ollama.com/&lt;/a&gt; and click download&lt;/li&gt;
&lt;li&gt;Unzip the download and click on the Ollama.app&lt;/li&gt;
&lt;li&gt;Follow the prompts to install the cli&lt;/li&gt;
&lt;li&gt;Now you ollama run llama3&lt;/li&gt;
&lt;li&gt;If you see &amp;gt;&amp;gt;&amp;gt;, then you can type whatever to prompt your new local LLM!&lt;/li&gt;
&lt;li&gt;Let’s try “How do you calculate a company’s valuation?” This should give you a bunch of information regarding this topic :P&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you were to ask it for my EBITDA, it would give information on how to calculate it and where to look for the numbers but need help to do it for you because there is no way it knows what your documents look like. Let’s start using this LLM in code!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/joshbrgs/medium-articles.git
cd demystifying-ai
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would like, I have a file that is already set up with the functionality needed, but if you want to experiment on your own you can follow along step by step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from langchain_ollama import ChatOllama

llm = ChatOllama(model="llama3", tempurature=0)
res = llm.invoke("What is my EBITDA?")
print(res)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Your Documents, splitting, and Encoding Them
&lt;/h4&gt;

&lt;p&gt;The key aspect of an RAG application is to provide your documents as context to the LLM, ensuring that the responses are personalized to you. Documents can be large and difficult to manage, especially when linking multiple ones together. This is where splitting and encoding come into play. Splitting involves breaking a document into smaller pieces of text, while encoding is the process of converting a sequence of characters into a specialized format. Think of it like coordinates for an LLM (e.g., Sonic the Hedgehog might be closely related to Delta Sonic Carwash because of the word “Sonic”). Most LLMs have a specific encoding mechanism, so OpenAI’s method will differ from Llama3’s.&lt;/p&gt;

&lt;p&gt;In our example, I will be using financial statements from Robinhood’s 2023 annual report, if you would like to follow along you can download them from &lt;a href="https://investors.robinhood.com/financials/annual-reports/default.aspx" rel="noopener noreferrer"&gt;here&lt;/a&gt;. There is a lot of information in this pdf. One could modify the application to allow users to upload their documentation or retrieve it from a Database, or an instance of Office 365 if you feel ambitious.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

file_path = "./ey-eu-ai-act-political-agreement-overview-february-2024.pdf"
loader = PyPDFLoader(file_path)

docs = loader.load()

print(len(docs))

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

print(len(splits))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setting Up a Vector Database
&lt;/h4&gt;

&lt;p&gt;We were able to split up the file into smaller parseable chunks that are embedded, but to not need to do this step over and over, we can make sure to store the embeddings in a vector database. This enables the retrieval of information needed to answer your question. We will be using an open-source vector database called &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

file_path = "./ey-eu-ai-act-political-agreement-overview-february-2024.pdf"
loader = PyPDFLoader(file_path)

docs = loader.load()

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

embeddings = OllamaEmbeddings(model="llama3")

vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)

retriever = vectorstore.as_retriever()

# query it
query = "What is the 2023 net Profit"
search = vectorstore.similarity_search(query)

# print results
print(search[0].page_content)
print(search[0])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Testing Out Our RAG Assistant
&lt;/h4&gt;

&lt;p&gt;Now we can use this model to ask detailed questions about The European Artificial Intelligence Act document by EY. This information is distilled and questions are answered easily from our very own documents without the help of a Google Search, this LLM is all local and will work without the internet. I have taken the liberty to refactor our above code into something a little more usable and understandable. You can now call it with the first argument being the document of choice and second, your prompt! In the repository, I have broken these out into further functions. You can find these files in the git repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from pdf_loader import PDFLoader
from document_splitter import DocumentSplitter
from embeddings_generator import EmbeddingsGenerator
from rag_agent import RAGAgent
from utils import create_prompt_template
from langchain_ollama import ChatOllama

def main():
    file_path = input("Please enter the path to the PDF file: ")
    user_prompt = input("Please enter your prompt for the LLM: ")

    pdf_loader = PDFLoader(file_path)
    documents = pdf_loader.load_documents()

    splitter = DocumentSplitter()
    splits = splitter.split_documents(documents)

    embeddings_generator = EmbeddingsGenerator()
    retriever = embeddings_generator.generate_embeddings(splits)

    llm = Ollama(model="llama3")
    prompt_template = create_prompt_template()

    rag_agent = RAGAgent(retriever, llm, prompt_template)
    results = rag_agent.get_answer(user_prompt)

    print(results["answer"])

    for document in results["context"]:
        print(document)
        print()

if __name__ == " __main__":
    main()

from langchain_community.document_loaders import PyPDFLoader

class PDFLoader:
    def __init__ (self, file_path):
        self.file_path = file_path

    def load_documents(self):
        loader = PyPDFLoader(self.file_path)
        return loader.load()

from langchain_text_splitters import RecursiveCharacterTextSplitter

class DocumentSplitter:
    def __init__ (self, chunk_size=1000, chunk_overlap=200):
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap

    def split_documents(self, documents):
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap)
        return text_splitter.split_documents(documents)

from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings

class EmbeddingsGenerator:
    def __init__ (self, model="llama3"):
        self.model = model

    def generate_embeddings(self, documents):
        embeddings = OllamaEmbeddings(model=self.model)
        vectorstore = Chroma.from_documents(documents=documents, embedding=embeddings)
        return vectorstore.as_retriever()

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

class RAGAgent:
    def __init__ (self, retriever, llm, prompt_template):
        self.retriever = retriever
        self.llm = llm
        self.prompt_template = prompt_template

    def create_rag_chain(self):
        question_answer_chain = create_stuff_documents_chain(self.llm, self.prompt_template)
        return create_retrieval_chain(self.retriever, question_answer_chain)

    def get_answer(self, prompt):
        rag_chain = self.create_rag_chain()
        return rag_chain.invoke({"input": prompt})

from langchain_core.prompts import ChatPromptTemplate

def create_prompt_template():
    system_prompt = (
        "You are an assistant for question-answering tasks. "
        "Use the following pieces of retrieved context to answer "
        "the question. If you don't know the answer, say that you "
        "don't know. Use three sentences maximum and keep the "
        "answer concise."
        "\n\n"
        "{context}"
    )
    return ChatPromptTemplate.from_messages(
        [
            ("system", system_prompt),
            ("human", "{input}"),
        ]
    )

python3 main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you enjoyed this tutorial and building this RAG application, I highly suggest looking at LangChain documentation and How To Guides. They are full of rich content and go more in-depth than I do in my quick tutorial. You can add to this by creating a UI where a user can prompt the Model, upload their documentation, and more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;RAG applications can create new business opportunities and enhance your products and understanding of your data. They can generate new material based on your existing content, create personalized chat agents, vet support issues, and distill information for your C-suite. With cloud providers offering hosting solutions for your LLMs, more businesses can adopt this innovative technology and set themselves apart from the competition.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;https://chatgpt.com/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ollama</category>
      <category>langchain</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>Building Your Keyboard</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Tue, 11 Jun 2024 15:57:05 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/building-your-keyboard-go</link>
      <guid>https://forem.com/joshtheburgess/building-your-keyboard-go</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0h8oydlarsfah42jmot8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0h8oydlarsfah42jmot8.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Completed Corne Keyboard&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Journey in Assembling My Corne Keyboard
&lt;/h4&gt;

&lt;h3&gt;
  
  
  What’s a Corne Keyboard?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The Corne keyboard is a split keyboard with 3x6 column staggered keys and 3 thumb keys, based on &lt;a href="https://github.com/MakotoKurauchi/helix" rel="noopener noreferrer"&gt;Helix&lt;/a&gt;. Crkbd stands for Corne Keyboard. ~ Foostan, GitHub&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This keyboard design is &lt;a href="https://github.com/foostan/crkbd" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; and developed by Foostan, a well-known developer in the keyboard community. The design can be modified from the 42-key 6-column layout to a 36-key 5-column layout; however, if you choose this you cannot add the columns back as you need to snap them off of your PCB.&lt;/p&gt;

&lt;p&gt;I also needed some soldering refreshers and found a couple of videos about &lt;a href="https://www.youtube.com/watch?v=-qk-ulz05J8" rel="noopener noreferrer"&gt;soldering&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=OWNAjwhwzro" rel="noopener noreferrer"&gt;flux&lt;/a&gt;, and &lt;a href="https://www.youtube.com/watch?v=bG7yW9FigJA" rel="noopener noreferrer"&gt;desoldering&lt;/a&gt; to be helpful.&lt;/p&gt;

&lt;h4&gt;
  
  
  What you need:
&lt;/h4&gt;

&lt;p&gt;Tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwjZ-9uVsp-GAxXlNwgFHRG0BWAYABAIGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz18M-IzeIhgTbB10qjWIwPCKDjYkt5i3L36rxonafCvhb5mOZkspm48aAowxEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2kqZN0hYfb3VY4_OKJpxwBV34t9yW199EbvWFJEUcVPgFMAUuZf1KxE-6AlC13tmH4kMdASBb6Qpfz_duMEfKrJXr0L49FjlROZ-0aparw4xs8y0&amp;amp;sig=AOD64_02k2Id5ngRRICxppBWrjP6SDyztQ&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwjA19aVsp-GAxWbCnkGHdONCEoQww8oAnoECAYQDA&amp;amp;adurl=" rel="noopener noreferrer"&gt;Soldering Iron&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/shopping/product/14222052879336959065?q=amazon+solder+0.5mm&amp;amp;sca_esv=ec0f97d633954ec1&amp;amp;rlz=1C1GCEU_enUS1101US1101&amp;amp;sxsrf=ADLYWIL7tCRWGpPXgz2qvgAhGE6tB0Klbw:1716316786657&amp;amp;ictx=111&amp;amp;biw=1536&amp;amp;bih=791&amp;amp;dpr=1.25&amp;amp;prds=eto:7733697689470881708_0,pid:10832563362551640735&amp;amp;sa=X&amp;amp;ved=0ahUKEwjU95Cusp-GAxXjrYkEHSEUAOIQ8wIIoRA" rel="noopener noreferrer"&gt;Solder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Liquid-Dropper-Soldering-Electrical-Electronics/dp/B0CN29BZKV/ref=zg_bs_g_8107037011_d_sccl_19/141-8726821-7254918?psc=1" rel="noopener noreferrer"&gt;Flux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwjehtjDpqGGAxXlNggFHbfNCgMYABABGgJtZA&amp;amp;ase=2&amp;amp;gclid=CjwKCAjwr7ayBhAPEiwA6EIGxDJS4-KTJx6pyZPkSNN9OhoLRLLh4sgdaByapdZBPgCKMwGm68ip1RoC0l8QAvD_BwE&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2istNnqIGUtqPw8-KG4oh3M_I1WhyEROgF56T0gTXb9d3wZ_MdhMGQebxCx4IA9j3q0NyhJFnJS8N3ehwUtiO--cBmrBSASreqBole3ssVzwye2M&amp;amp;sig=AOD64_1ozVjQ6Ju3u1QSkRgDFXQl4JBJdA&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwj2y9LDpqGGAxX_tokEHVB-CbgQ9aACKAB6BAgGEBY&amp;amp;adurl=" rel="noopener noreferrer"&gt;Isopropyl Alcohol&lt;/a&gt;, to clean the flux&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwj3ruTxtZ-GAxWFNAgFHceYBaoYABAFGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz18uTfEMx92hKI40neR0ZNFySAE1oApqlVvuWHlsviWgSESFSIHTeisaAvSlEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2H-vg-kaFnpzTS4vZl1Eg0MNTRoBUmcZrSsUjWo5dUQSMqx16kViOhi-Dq-2RmV4lys2rG9yn0N7AEhh_AHlZW36t9K7w598rm4rwZtT5Fw-OXi8&amp;amp;sig=AOD64_16ub9LxK2G4NnU7glSHcKLWq-Bfg&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwid_t3xtZ-GAxUJk4kEHTM3DHIQ9aACKAB6BAgFECs&amp;amp;adurl=" rel="noopener noreferrer"&gt;Tweezers&lt;/a&gt;, I like the reverse grip&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional, yet helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/Solder-Desoldering-Remover-Soldering-Circuit/dp/B0BR4F7XQF?source=ps-sl-shoppingads-lpcontext&amp;amp;ref_=fplfs&amp;amp;psc=1&amp;amp;smid=A14WJ9O8JEFZO0" rel="noopener noreferrer"&gt;Solder Wick Braid Copper Wire&lt;/a&gt; (for undoing your mistakes)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwjW-cncsp-GAxVCMwgFHdgBBlgYABADGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz1_YA_7FYTzWa37gsi9mKYUlqcaN8wfmxraYq54fGGcFy3OnvMZe3DgaAtm_EALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2MgrDpzUf5g5U1dYfpRnMAv6tMCzkN6MmySAwN8muaYHp9oQiVM0aaT699-mVKM3-Ykt-BQ3nM26J2ZCO3m5WmtHoS06_cBxULKDKBzWGPrsXtGk&amp;amp;sig=AOD64_2mX0UhgBqsvvgm8z-IHo5z0j9n-A&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwj2yMTcsp-GAxXsvokEHXTbDpoQ9aACKAB6BAgGEA8&amp;amp;adurl=" rel="noopener noreferrer"&gt;Solder Pump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwjJzKDqsp-GAxWFNwgFHWMoBbkYABAIGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz1_riS_Qx7--PD99A5pLbaHSk7ipOOQKo1IwsUevfMcxbzKV1-N4Cr4aAt_fEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2FWD4Xd3LE0jvc_3ILJcJpPr3xM2AaXgA_3oj_zJIprb0LIrCl9vGH5y8opIoVFehcuOFXY6Zy4O8D1NRFkSeG3KxhXw-0HjbSUXkV-CATrE2OlE&amp;amp;sig=AOD64_2UNB75aG9MHBlerk8SdYtRwSwzxA&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwjiq5vqsp-GAxWwjIkEHc2sCtoQ9aACKAB6BAgGECQ&amp;amp;adurl=" rel="noopener noreferrer"&gt;Solder Mat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwjP5632sp-GAxXmSf8BHeDbAr8YABANGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz1-_j7J35l54212ZTTaVM5_1u-rMTqCAJKNeBUL9Bphu19Xs6mI4OWUaAjPTEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD268PI1DFv6Iiv3ierdq80WQdMw78V1klRVfP3rVtR--Oy83uacPYSF0kf34jj8ns5pBlCIXbRdEuQl-6i8zDdhfX7yP-3KakevoMrsl7JGj6mY2k&amp;amp;sig=AOD64_2sR_mT_G1ypUIT0Nb2-bPf0_ayFw&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwjytqj2sp-GAxUqhIkEHRhDAf4Q9aACKAB6BAgCEFU&amp;amp;adurl=" rel="noopener noreferrer"&gt;Safety Glasses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwik-uiEs5-GAxXXNggFHfwWAf4YABAIGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz1_Kwjs-Q3D1oKSmuYQQCpVbkLHNlQPqPzsPf4Mv2r5fILHyChMt3f0aAt5DEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2lzII50IU4iAvR2TKt_SXsupTOu00AKzjqd16f3AqHwB70pPBSPLbd0jGsYcb2ktRnoMdqLZ1oTUvefoWilxUAAGYgW0O5xT4HgAeMRWdPUOWDuw&amp;amp;sig=AOD64_2m1e4AN_qhPjbqsuxv9rCBx5LIjg&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwjk1uKEs5-GAxWMkYkEHaC1COgQ9aACKAB6BAgEEBY&amp;amp;adurl=" rel="noopener noreferrer"&gt;Solder Ventilation Fan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Scotch-Heat-Resistant-Listed-Certified-Electrical/dp/B001AXD0EY?source=ps-sl-shoppingads-lpcontext&amp;amp;ref_=fplfs&amp;amp;psc=1&amp;amp;smid=ATVPDKIKX0DER" rel="noopener noreferrer"&gt;Electrical Tape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwiaotactp-GAxXmSf8BHeDbAr8YABAKGgJtZA&amp;amp;ase=2&amp;amp;gclid=Cj0KCQjwjLGyBhCYARIsAPqTz1_Oas8tL7cg2MYmW4YDCNNWRVRcKT-kpp5ldu9gmTti7T2vydYSyxYaAssbEALw_wcB&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2_IsBa0Vs4WAFkiFWvdTn6YwtZTjU5vnGGdQv2kARC0UH5332bxXkpT2u_Vszky0VODj025wBrw5MJLJ3VqRTnGsNm4twkN1Ncma5o5To9Gv44dI&amp;amp;sig=AOD64_1QHa5YM2aDzyV0sm0sjwuDgG7Ylw&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;ved=2ahUKEwigxdCctp-GAxXjrYkEHSEUAOIQ9aACKAB6BAgDEA8&amp;amp;adurl=" rel="noopener noreferrer"&gt;Magnifying Lens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.googleadservices.com/pagead/aclk?sa=L&amp;amp;ai=DChcSEwiU89WvzqaGAxXMXkcBHSdEDcMYABALGgJxdQ&amp;amp;ase=2&amp;amp;gclid=CjwKCAjw9cCyBhBzEiwAJTUWNTYCdksksUo2KxwEQQDhaoOTHeXC0tybhgDTdC9Bw2_uJ4boP9C0GxoCBVEQAvD_BwE&amp;amp;ei=b7NQZtTrDYOk5NoPqbaF2A4&amp;amp;ohost=www.google.com&amp;amp;cid=CAESVeD2e-86Quh2IT8ljbhFaFy1JKK9xCE_-AqsexYpc-I6hG5nx4AIkzeZ-koJ-A5MI_MlHFnYppYxvQJsfzlGG2SxZPMXrYqfseLAdxC0WsYWSdRjxBE&amp;amp;sig=AOD64_0xOL5IX-7wiy7N_KMx82WO4vdLlg&amp;amp;ctype=5&amp;amp;q=&amp;amp;nis=4&amp;amp;sqi=2&amp;amp;ved=2ahUKEwiU38-vzqaGAxUDElkFHSlbAesQww8oAnoECAMQDA&amp;amp;adurl=" rel="noopener noreferrer"&gt;Multimeter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b5rjrj387818msvv5y3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b5rjrj387818msvv5y3.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;For the basic tooling I bought, I ended up needing the solder wick, multimeter, and tape&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Net you will need the actual parts to build this keyboard. If you have the means and material, you can 3D print the keycaps and case and even order the PCB based on a design. I went with a combination of places to source my parts, but the main electronics are from Keebmaker. My favorites are &lt;a href="https://keebmaker.com/" rel="noopener noreferrer"&gt;Keebmaker&lt;/a&gt;, &lt;a href="https://typeractive.xyz/" rel="noopener noreferrer"&gt;Typeractive&lt;/a&gt;, and &lt;a href="https://splittype.com/" rel="noopener noreferrer"&gt;SplitType&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Corne PCBs x2&lt;/li&gt;
&lt;li&gt;Micro-controllers x2&lt;/li&gt;
&lt;li&gt;Controller Pins x48&lt;/li&gt;
&lt;li&gt;Diodes x36–42&lt;/li&gt;
&lt;li&gt;Kailh Switch Hot Swap Sockets x36–42&lt;/li&gt;
&lt;li&gt;TRRS Jacks x2 (optional if you choose nice!nano controllers for Bluetooth connection)&lt;/li&gt;
&lt;li&gt;Reset Buttons x2&lt;/li&gt;
&lt;li&gt;TRRS Cable x1 (optional if you choose nice!nano controllers for Bluetooth connection)&lt;/li&gt;
&lt;li&gt;Key Switches x36–42&lt;/li&gt;
&lt;li&gt;Keycaps x36–42&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suggested Fun parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard Case x1&lt;/li&gt;
&lt;li&gt;Controller Hot Swap Headers x48&lt;/li&gt;
&lt;li&gt;OLED Screens 2&lt;/li&gt;
&lt;li&gt;OLED Pins x8&lt;/li&gt;
&lt;li&gt;OLED Hot Swap Headers x8&lt;/li&gt;
&lt;li&gt;OLED Covers and Hardware x2&lt;/li&gt;
&lt;li&gt;SK6812 3535 LEDs x12&lt;/li&gt;
&lt;li&gt;SK6812 Mini-E LEDs x36–42&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Firmware:&lt;/p&gt;

&lt;p&gt;The Wired option of a Corne keyboard can use &lt;a href="https://usevia.app/" rel="noopener noreferrer"&gt;VIA&lt;/a&gt; and &lt;a href="https://qmk.fm/" rel="noopener noreferrer"&gt;QMK&lt;/a&gt;, which are a little more user-friendly compared to the wireless option which is&lt;a href="https://zmk.dev/" rel="noopener noreferrer"&gt; ZMK&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Diodes:
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: easy&lt;/p&gt;

&lt;h4&gt;
  
  
  What’s a Diode?
&lt;/h4&gt;

&lt;p&gt;A diode is a semiconductor that allows the current to flow in one single direction and restricts it coming from the opposite. They also change AC (alternating current) to DC (direct current). A diode can fail due to overheating or an overvoltage spike, which causes a short circuit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jfwiklo76pe5lkd2yfi.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jfwiklo76pe5lkd2yfi.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Absolutely make sure the line on the diode is lined up with the line marker on your PCB!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I followed Keebmaker’s Guide to assembling my Corne and set the soldering iron to 400° C. PCB components can burn, so the idea is to be quick. When soldering the diodes, I found it easiest to pre-tin one of the pads in each pair on the PCB. Then take the diode with tweezers and heat the soldered pad, slide and hold the diode and it will harden correctly onto the board. Make sure the 3 lines of the diode are lined up to the line mark on the PCB-designated diode space! Next, go around and solder the other leg to the opposite PCB pad.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch Hot Swap Sockets:
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: medium&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbie6ul04d0kf280m122.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbie6ul04d0kf280m122.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;I found these pieces easier to solder&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I found pre-tinning all the pads for the switches to be the easiest way to tackle mounting the components. After each pad is tinned, all one has to do is set the switch along the pads and press the iron down on the switch until the solder melts under it and the component will be mounted to said board.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reset Switches:
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: easy&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejgfemjft6btvhyn90i2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejgfemjft6btvhyn90i2.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Reset switch in my kit faces up, whereas some may face out&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With the reset switch, you have to flip the board right side up (opposite of the diodes) place the legs down through, and then solder the legs to the board on the underside.&lt;/p&gt;

&lt;h3&gt;
  
  
  TRS Jacks (Optional for Wireless Builds):
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: easy&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After this step, I had forgotten to take a couple of pictures as these steps went much quicker than earlier ones.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The TRS Jack is very similar to the rest switch, although due to it being a larger component, one may have to apply electrical tape to keep it in place. The TRS jack on the keyboard allows communication between the two boards. This is optional when you build a wireless board because the controllers will then connect over Bluetooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller Socket Headers and pins:
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: hard&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpdjhzrvb7phser10d4b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpdjhzrvb7phser10d4b.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This does not have pins installed, but be sure to tuck the battery under to solder the pins on the correct height&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For both headers, it is optional, but highly recommended to use hot swappable socket headers, in case you switch out the microcontroller or OLED screen, and I enjoyed the low profile sockets sold at Keebmaker.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your controller will have labels next to each hole where your pins should connect to the same label on the PCB where the socket is. I found it easiest to click the pins into the header and then the controller can be soldered. Be sure to quickly solder as this is most likely the most expensive piece of the build with the least heat resistance.&lt;/p&gt;

&lt;p&gt;This step is the same for the OLED, just perform the OLED assembly after the battery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flashing the Controllers:
&lt;/h3&gt;

&lt;p&gt;This was my favorite part, most of the nuances are explained and well-documented for the wireless builds at &lt;a href="https://zmk.dev/" rel="noopener noreferrer"&gt;https://zmk.dev/&lt;/a&gt;. This step requires you to clone the zmk repo and push it to your own GitHub, so you do not create a forked version unless you plan to help contribute to the project. The GitHub actions will compile your preferences as a zip with left and right controller files ready to flash. When you connect your board to the laptop, and double-press the reset switch you, you will see your board mounted and you can drag the appropriate folder to the board connected. It will dismount and you have the keyboard ready to type!&lt;/p&gt;

&lt;h3&gt;
  
  
  Micro-Controller Battery:
&lt;/h3&gt;

&lt;p&gt;skill: easy&lt;br&gt;&lt;br&gt;
desolder: easy&lt;/p&gt;

&lt;p&gt;Some PCB boards will have a place for a power switch, mine did not, and I did not mind the risk of soldering the battery directly to the controller. Strip the wire tips so you can make a direct connection to the terminals at the front of your microcontroller. Black is negative and red positive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Underglow LEDs :
&lt;/h3&gt;

&lt;p&gt;skill: medium&lt;br&gt;&lt;br&gt;
desolder: medium&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9abs1auafsnjobfb7k8e.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9abs1auafsnjobfb7k8e.jpeg" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;These were the most difficult pieces, I later went through to fix some joints&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Though I soldered these on, I am not a huge RGB lover. It drains the battery pretty quickly so I leave them disabled. There are two different LEDs on this board, one that points to the desk for an under-glow effect and the other is per key for a backlight under your keycaps. The power for these lights all follow a circuit, so if one is out, all of the following will not work either. Solder the edges of the LED so that the corner of the large LED that has an indent is in the corner of the 90-degree line marking of the PCB. As you solder along, pause and plug your controller in to see if the LED is soldered on and working correctly. Follow the graphic below to know how these LEDs relate. The backlighting LEDs will have a cut corner on one of the connectors which will signal how it should be soldered to the board on the little white arrow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw10ihg1ubameqy3872ix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw10ihg1ubameqy3872ix.png" width="800" height="528"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from the Foostan Guide on GitHub&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cases:
&lt;/h3&gt;

&lt;p&gt;This should be one of the coolest, and most straightforward parts. All you need to do is place your soldered-up PCB into the keyboard casing and tighten the hardware that came with the case down so your PCB stays in place. This is when it all starts to look functional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switches:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A2x3IvKlx_Gix7tAx" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A2x3IvKlx_Gix7tAx" width="1024" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The switches, I ordered Milky Yellow Geatron, have two prongs below that fit into the Switch sockets we soldered onto the PCB on the other side. These should click into place without too much pressure. Depending on your case you may have a piece that will go on top of your PCB to hold it into the casing. Place 4 switches in each corner and then screw the hardware into the case, this makes it easier to have it all lined up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keycaps:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ykbcvhj0oz91yfaaksm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ykbcvhj0oz91yfaaksm.jpeg" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;I chose DSA keycap profiles&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each keycap will fit snuggly onto the switches to complete the look of your assembled keyboard. You can have many different profiles of switches, I ordered DSA, giving the keys a more uniform look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F970%2F0%2Ar57OSwPzrn0HCxL0" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F970%2F0%2Ar57OSwPzrn0HCxL0" width="970" height="550"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from Introduction to Keycap Profiles&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardships I Faced
&lt;/h3&gt;

&lt;p&gt;At points, I did not think before soldering and it got me into some sticky situations. Spare parts can save you! I soldered the diodes in the reverse direction, which makes your board useless unless you fix it. I also soldered a microcontroller in place before tucking the battery under and giving myself no room and extra work to remove the soldered microcontroller and make space for the battery. I ordered hot-swap sockets after this incident so I did not need to go through this pain again.&lt;/p&gt;

&lt;p&gt;The hardest part of this was desoldering the pins. The solution that worked best for me was to add flux to my solder wick and desolder each pin but that will not unsolder the pins completely. To desolder, I had to press the iron on the bottom point of the pin to heat it enough and provide pressure to push it up and out.&lt;/p&gt;

&lt;p&gt;If you run into any LED issues, the previous or faulty LED could be the issue, because each one depends on the previous. I hope if you follow this guide and avoid my mistakes you get a nice experience with a great keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing It Out — Conclusion!
&lt;/h3&gt;

&lt;p&gt;Your final product awaits, the only thing to do left is to get used to it by typing out a comment or the URL to my profile so that you can subscribe for more tutorials and content. I enjoyed the process of this build and cannot wait to explore what else I may be able to build with the leftover resources and tools!&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://keebmaker.com/pages/km-corne-build-guide" rel="noopener noreferrer"&gt;KM Corne Build Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fluke.com/en-us/learn/blog/electrical/what-is-a-diode" rel="noopener noreferrer"&gt;What is a Diode?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://missionengineering.com/what-is-a-trs-cable/" rel="noopener noreferrer"&gt;What is a TRS Cable?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>keyboard</category>
      <category>soldering</category>
      <category>engineering</category>
      <category>howto</category>
    </item>
    <item>
      <title>AWS PrivateLink in a Straightforward Setup</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Mon, 06 May 2024 12:02:19 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/aws-privatelink-in-a-straightforward-setup-48g7</link>
      <guid>https://forem.com/joshtheburgess/aws-privatelink-in-a-straightforward-setup-48g7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Az7eQmgrjtNP8iZTl" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Az7eQmgrjtNP8iZTl" width="1024" height="769"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Mehmet Ali Peker on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Tips and Thought-Tricks from personal experience with PrivateLink and VPC Endpoints/Endpoint Services
&lt;/h4&gt;

&lt;p&gt;One of the most useful services, and yet seemingly complex, is AWS PrivateLink. The service itself is more of a design pattern in which you make a PrivateLink via VPC (Virtual Private Cloud) endpoints and endpoint services. With this combination, you can provide a connection between VPCs without peering or a Transit Gateway over AWS Backbone (their internet, not the public), this includes a connection between two VPCs in separate AWS accounts.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why Would You Need This?
&lt;/h4&gt;

&lt;p&gt;The advantage of using AWS PrivateLink is greater security, between AWS Partners, AWS Marketplace, your on-prem servers, and between VPCs both within the same account or between accounts. This can apply the other way too, where you can provide services from your VPC to customers while staying compliant with HIPAA, PCI, and other regulations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62fy56p8mfs6wwbnsxu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62fy56p8mfs6wwbnsxu7.png" width="800" height="355"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram of a PrivateLink from an EC2 instance to an RDS Proxy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are a couple of ways to go about creating your own SaaS and providing it to customers in AWS, one is with API Gatways, and the other is through PrivateLink. A common scenario faced is with ETL/ELT (Extract Load Transform) tools like Glue or Airbyte that live in an AWS Organization’s dedicated Data Engineering account and need to extract data from an RDS instance in a different account that may be affiliated with an application that the company has. PrivateLink can have multiple consumers while it has a single producer and eliminates the worry of CIDR overlap in your networks.&lt;/p&gt;
&lt;h4&gt;
  
  
  What about VPC Peering and Transit Gateways?
&lt;/h4&gt;

&lt;p&gt;These two other AWS services may seem to provide a similar solution to what PrivateLink does at a glance, however, the biggest difference is the communication direction. PrivateLink is a unidirectional communication between services, a consumer in one VPC is consuming the service in a producer VPC, but the producer cannot consume anything from the consumer’s VPC. Both VPC Peering and Transit Gateway services provide Layer 3 bidirectional communication, meaning, that services in each VPC can talk to one another. VPC Peering and Transit Gateways can talk across regions, unlike PrivateLink.&lt;/p&gt;

&lt;p&gt;VPC Peering is a full-mesh architecture that allows your VPCs to act as if they are inside each other. When scaling these VPCs you may want to peer another, or maybe 3 others. Each VPC would have to peer each other to fully talk with each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg86e3u32gc98fzsykaj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg86e3u32gc98fzsykaj5.png" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;VPC Peering Connection&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Something that solves this scenario and reduces overhead is the Transit Gateway. A Transit Gateway provides a hub-and-spoke model of allowing VPCs to connect. This solution is more scalable, although could introduce a small amount of latency with the network hops. Combining these services with other networking services like Direct Connect for your main office and a VPN for your branches can open up hybrid cloud opportunities and create a global network for your business.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sdxg9h1y24k2pn0ealj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sdxg9h1y24k2pn0ealj.png" width="800" height="688"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Global Network for a corporation can be established with these services&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Let’s Establish some PrivateLinks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuwg32s36yv9ee2119lc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuwg32s36yv9ee2119lc.png" width="800" height="342"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The solution we will implement allowing Glue to connect to the RDS instance&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two AWS Accounts (we will be using free tier sizes)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/docs/install/" rel="noopener noreferrer"&gt;A Pulumi Account&lt;/a&gt; (it's free 😊), install the cli, and login via cli&lt;/li&gt;
&lt;li&gt;git clone &lt;a href="https://github.com/joshbrgs/medium-tutorials.git" rel="noopener noreferrer"&gt;https://github.com/joshbrgs/medium-tutorials.git&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a preface, the RDS and IAM configuration does not follow the best practices guidelines around the least-privileged since this is for demonstration purposes, if you would like to use this for production use, I encourage modifying the IAM policy for the secret and modifying the RDS instance to suit your needs. This includes upgrading the encryption for the proxy and RDS instance.&lt;/p&gt;
&lt;h3&gt;
  
  
  Producer Account
&lt;/h3&gt;

&lt;p&gt;First, we will start with the producer account. Run these commands to get to the example code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd medium-tutorials/privatelink/producer
# The Profile of your AWS cli refer to this for sso or use iam access keys 
# https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
pulumi config set aws:profile account1-profile-producer
pulumi up
# If it fails, rerun, there may have been a race condition 😬
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have the following created in your account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDS with a Postgres Engine and MD5 password encryption with a custom parameter group&lt;/li&gt;
&lt;li&gt;RDS Proxy and Subnet Group&lt;/li&gt;
&lt;li&gt;Secrets Manager Secret for DB password and username&lt;/li&gt;
&lt;li&gt;A VPC&lt;/li&gt;
&lt;li&gt;Two Subnets&lt;/li&gt;
&lt;li&gt;Security Groups for the DB and proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have confirmed this we can start setting up our PrivateLink! We first need to identify the IP addresses assigned to the RDS Proxy endpoint, and for this, you will need a CLI network tool such as dig .&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the Amazon RDS console, choose &lt;strong&gt;Proxies&lt;/strong&gt; in the navigation pane.&lt;/li&gt;
&lt;li&gt;Choose the proxy and locate the &lt;strong&gt;endpoint&lt;/strong&gt; , copy this for later&lt;/li&gt;
&lt;li&gt;Next you will want to &lt;strong&gt;modify the proxy&lt;/strong&gt; to use MD5 as the &lt;strong&gt;authentication&lt;/strong&gt; encryption method, this is a work around for the tutorial&lt;/li&gt;
&lt;li&gt;Use a networking CLI tool, such as dig, to find the proxy IP addresses: dig db-proxy.proxy-cfic8466upy8.us-east-1.rds.amazonaws.com&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note these IPs, you will need them later&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Target Group
&lt;/h4&gt;

&lt;p&gt;These next 3 services combined create the PrivateLink endpoint for a producer account, the one that is providing a service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In EC2, go to the Target Groups section&lt;/li&gt;
&lt;li&gt;Create a Target Group with &lt;strong&gt;IP Addresses&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Change the protocol to &lt;strong&gt;TCP&lt;/strong&gt; and change the port to &lt;strong&gt;5432&lt;/strong&gt; (the default Postgres port)&lt;/li&gt;
&lt;li&gt;Select the producer-vpc, click Next&lt;/li&gt;
&lt;li&gt;Use the IP’s noted from the previous step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b4smrk8qzol2q6s40kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b4smrk8qzol2q6s40kf.png" width="800" height="455"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Health Checks will pass as soon as the NLB is set&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Network Load Balancer
&lt;/h4&gt;

&lt;p&gt;Next, let’s create a Network Load Balancer and its Security Group to route the traffic coming into our VPC to the correct service / Target Group.&lt;/p&gt;

&lt;p&gt;To create the &lt;strong&gt;Security Group&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the EC2 Console, choose Security Groups and click &lt;strong&gt;Create Security Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name and description and choose the producer-vpc&lt;/li&gt;
&lt;li&gt;Add an &lt;strong&gt;inbound rule&lt;/strong&gt; to receive &lt;strong&gt;5432&lt;/strong&gt; traffic from the &lt;strong&gt;VPC CIDR range&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;For the &lt;strong&gt;outbound rule&lt;/strong&gt; , change the port to &lt;strong&gt;5432&lt;/strong&gt; and the &lt;strong&gt;destination to the proxy security group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create descriptions as needed&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;proxy security group&lt;/strong&gt; , and click &lt;strong&gt;Edit inbound rules&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete&lt;/strong&gt; the current Inbound Rule and &lt;strong&gt;Add&lt;/strong&gt; a new one allowing port &lt;strong&gt;5432&lt;/strong&gt;  &lt;strong&gt;from the new Security Group&lt;/strong&gt; we created for the Load Balancer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp1k0gcbbcj6yrvi962k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp1k0gcbbcj6yrvi962k.png" width="800" height="410"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This is the Outbound rule for the Network Load Balancer Security Group&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To create the &lt;strong&gt;Load Balancer&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In EC2 Console, choose &lt;strong&gt;Load Balancers&lt;/strong&gt; and &lt;strong&gt;Create Load Balancer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Network Load Balancer&lt;/strong&gt; , give it a name, and select &lt;strong&gt;Internal&lt;/strong&gt; for the scheme&lt;/li&gt;
&lt;li&gt;Select the producer-vpc and both AZs that show up (make sure the subnets are private)&lt;/li&gt;
&lt;li&gt;For the &lt;strong&gt;Security Group&lt;/strong&gt; , make sure to click the one we just created in the previous step&lt;/li&gt;
&lt;li&gt;In the Listeners section, change the protocol to &lt;strong&gt;TCP&lt;/strong&gt; and port &lt;strong&gt;5432&lt;/strong&gt; and select the &lt;strong&gt;Target Group&lt;/strong&gt; from the previous step for the default action.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Load Balancer&lt;/strong&gt; at the bottom, it may take a minute to provision&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzv2vsz0s27k8nsj40qdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzv2vsz0s27k8nsj40qdi.png" width="800" height="453"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Active NLB, check the target group, which should also be healthy now!&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Endpoint Service
&lt;/h4&gt;

&lt;p&gt;This is the next piece where our previous work all comes together, creating the Endpoint Service&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the Amazon VPC console, choose &lt;strong&gt;Endpoint Services&lt;/strong&gt; and click &lt;strong&gt;Create Endpoint Service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name, select &lt;strong&gt;Network&lt;/strong&gt; for Load Balancer Type, and select the NLB we created in the previous step, all else can stay default, and click  &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Allow Principals&lt;/strong&gt; tab, select Allow Principals, and enter your consumer account arn arn:aws:iam:::root . This can be more restrictive against a given role or user too.&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Service Name&lt;/strong&gt; for the next step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57dnj9corxr5g3njd974.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57dnj9corxr5g3njd974.png" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Consumer Account
&lt;/h4&gt;

&lt;p&gt;Now let’s log into the consumer account as well as run these commands to spin up our network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd medium-tutorials/privatelink/consumer
pulumi config set aws:profile account2-profile-consumer
pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following should have been created in this new consumer account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A VPC&lt;/li&gt;
&lt;li&gt;A Subnet&lt;/li&gt;
&lt;li&gt;A Security Group for Glue&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  VPC Endpoint and Accepting Connection
&lt;/h4&gt;

&lt;p&gt;In your consumer AWS account, we will use Glue as an ETL tool and connect with a jdbc connector string to the Postgres db in our other AWS account! Let’s begin by creating a Security Group for the soon-to-come VPC Endpoint. Note that the Glue Security Group needs all ports inbound and outbound but uses the source for both as the same security groups.&lt;/p&gt;

&lt;p&gt;Go to the Amazon VPC Console, navigate to the &lt;strong&gt;Endpoints&lt;/strong&gt; screen, and click &lt;strong&gt;Create Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provide a name, and select &lt;strong&gt;Other endpoint services&lt;/strong&gt; under the Service category&lt;/li&gt;
&lt;li&gt;For the Service name under &lt;strong&gt;Service settings&lt;/strong&gt; , provide the &lt;strong&gt;Service Endpoint Name&lt;/strong&gt; you copied from the previous section and click &lt;strong&gt;Verify Service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;consumer-vpc&lt;/strong&gt; and one subnet will be grey while the other can be selected, &lt;strong&gt;select the one available&lt;/strong&gt; , the other is to demonstrate how the Endpoint and Service Endpoint need the same AZs for connectivity.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Security Group&lt;/strong&gt; for the endpoint (endpoint is in the name) and click &lt;strong&gt;Create endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cnjba7yb788fsowffjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cnjba7yb788fsowffjf.png" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the other account, we will have to accept/approve the connection of this endpoint and add inbound 5432 from the consumer CIDR to the NLB Security Group.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log back into the producer account&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;VPC Console&lt;/strong&gt; and &lt;strong&gt;Endpoint Services&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Endpoint Connections&lt;/strong&gt; tab, click the &lt;strong&gt;Actions&lt;/strong&gt; tab, and &lt;strong&gt;Accept the endpoint connection request&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;After a few minutes, you should see &lt;strong&gt;Pending&lt;/strong&gt; change to &lt;strong&gt;Available&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhosmtstuwbueev82ab5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhosmtstuwbueev82ab5.png" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Test the connection
&lt;/h4&gt;

&lt;p&gt;Now we can go test out our connection from Glue! You will want to note down the &lt;strong&gt;RDS Database Identifier&lt;/strong&gt; before we log back into the &lt;strong&gt;consumer account.&lt;/strong&gt; The other info we need is the &lt;strong&gt;endpoint DNS name&lt;/strong&gt; , which is the first in the list, and head over to the &lt;strong&gt;AWS Glue Console&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Data Connections&lt;/strong&gt; , and select the &lt;strong&gt;Create Connection&lt;/strong&gt; button in the bottom pane&lt;/li&gt;
&lt;li&gt;In the grid, select &lt;strong&gt;JDBC&lt;/strong&gt; and ** Next**&lt;/li&gt;
&lt;li&gt;Add the jdbc url: jdbc:postgresql://:5432/ and the username and password for the database, which can be found in the producer code in my git repo (since this is a non-secure tutorial) or in the secrets manager the “dbpassword” is created&lt;/li&gt;
&lt;li&gt;You will need to add &lt;strong&gt;Network options&lt;/strong&gt; at the bottom&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;consumer-vpc&lt;/strong&gt; in the drop-down, select the &lt;strong&gt;private subnet&lt;/strong&gt; and &lt;strong&gt;Glue Security Group,&lt;/strong&gt; and click ** Next**&lt;/li&gt;
&lt;li&gt;Name the connector, you cannot change this later, and  &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Last but not least, select the connection at the bottom of the window, click the &lt;strong&gt;Actions&lt;/strong&gt; drop-down, select &lt;strong&gt;Test Connection,&lt;/strong&gt; then select th &lt;strong&gt;e&lt;/strong&gt; Glue Service Role (If this does not exist you can create a new role with the &lt;strong&gt;AWSGlueServiceRole policy&lt;/strong&gt; attached and &lt;strong&gt;AmazonS3FullAccess policy&lt;/strong&gt; ) If you get an error about the NAT Gateway or s3 endpoint, check that the subnet has the route table associated with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9pc1jklnfoy56023uk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9pc1jklnfoy56023uk4.png" width="800" height="288"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Successful connection, go Glue some data!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Success, you can now use a Glue Crawler or Glue ETL Job to extract data from the remote database securely!&lt;/p&gt;

&lt;h4&gt;
  
  
  Clean Up
&lt;/h4&gt;

&lt;p&gt;To clean up this solution, you will just need to delete the resources we created in this example (endpoint, endpoint service, nlb, security groups, and target group, the rds database may need manually destroyed as well). The networking and instances can be deleted via pulumi destroy in the consumer and producer directories.&lt;/p&gt;

&lt;p&gt;I tried this solution out with MySQL, and an issue arose with the “user@host” pattern. The host would need to change to the CIDR of the consumer VPC or a wildcard. To change this, we would need a Bastion Host to connect a MySQL client to the database. Since this is a tutorial, I wanted to keep as minimal resources as possible, but feel free to experiment!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;PrivateLink lets you securely connect to separate VPCs keeping your traffic within AWS Backbone. This can be beneficial for exposing SaaS applications to developers using AWS, or even ensuring security between your own AWS accounts when using ELT/ETL tools. The PrivateLink connection is not limited to AWS Services, for you can set it up so that an EC2 instance running a service you created can be consumed, or you will commonly find PrivateLink as an option to consume your AWS Marketplace offerings. I hope this will help you in your journey to create the next big SaaS product!&lt;/p&gt;

&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/database/use-amazon-rds-proxy-and-aws-privatelink-to-access-amazon-rds-databases-across-aws-organizations-at-american-family-insurance-group/" rel="noopener noreferrer"&gt;Use Amazon RDS Proxy and AWS PrivateLink to access Amazon RDS databases across AWS Organizations at American Family Insurance Group | Amazon Web Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html" rel="noopener noreferrer"&gt;What is AWS PrivateLink?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html" rel="noopener noreferrer"&gt;What is VPC peering?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/tgw/what-is-transit-gateway.html" rel="noopener noreferrer"&gt;What is a transit gateway?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-gateways-intro.html" rel="noopener noreferrer"&gt;Direct Connect gateways&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>endpointsecurity</category>
      <category>aws</category>
      <category>networking</category>
      <category>cloud</category>
    </item>
    <item>
      <title>The Modern Agile Organization</title>
      <dc:creator>Josh Burgess</dc:creator>
      <pubDate>Mon, 12 Feb 2024 14:06:29 +0000</pubDate>
      <link>https://forem.com/joshtheburgess/the-modern-agile-organization-253i</link>
      <guid>https://forem.com/joshtheburgess/the-modern-agile-organization-253i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AcPWwZbQFiESvi_FK" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AcPWwZbQFiESvi_FK" alt="The Scrum Framework is not always Agile unless you make it" width="1024" height="1365"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by İrfan Simsar on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  How to empower self-ownership in teams, excellent software, and quick iterations
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;While this is an opinionated piece, the idea of Agile is to be able to adapt to the project’s needs and organize high-functioning teams.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is Agile?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Agile isn’t defined by a set of ceremonies or specific development techniques. Rather, agile is a group of methodologies that demonstrate a commitment to tight feedback cycles and continuous improvement. — &lt;a href="https://www.atlassian.com/agile" rel="noopener noreferrer"&gt;Atlassian&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Agile methodology tends to apply to projects and creative tasks at hand, whereas other businesses may be more privy to Lean management, which is great for repeatable operations and routine.&lt;/p&gt;

&lt;p&gt;The Agile Manifesto was written in 2001 by seventeen individuals. They found consensus on 4 core values that are further explained with the 12 Agile principles. The whole object of Agile is to put the people first over the process and increase communications to foster a productive environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agile Principles tldr:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continuous Delivery of valuable software to end users&lt;/li&gt;
&lt;li&gt;Welcome changing requirements&lt;/li&gt;
&lt;li&gt;Business and Developers work closely&lt;/li&gt;
&lt;li&gt;Build projects around motivated people and trust they will get the job done&lt;/li&gt;
&lt;li&gt;Convey information within teams face-to-face&lt;/li&gt;
&lt;li&gt;Working software is the measure of progress&lt;/li&gt;
&lt;li&gt;Reduce burnout&lt;/li&gt;
&lt;li&gt;Continuous attention to technical excellence and good design&lt;/li&gt;
&lt;li&gt;Simplicity&lt;/li&gt;
&lt;li&gt;Self-organized teams help emerge the best designs and architecture&lt;/li&gt;
&lt;li&gt;At regular intervals, reflect on how to be more effective and tune behavior accordingly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, this would not be an Agile blog if it did not mention the Scrum Framework. Scrum is a subset of the Agile process that is based around iterative software development cycles, with several key steps in the workflow — a daily standup, sprints (period of work, most seen is 2 weeks), sprint planning, sprint review, and sprint retrospective. A Scrum team consists of 10 or less people. To make an analogy, Six Sigma is to Scrum, as Lean is to Agile.&lt;/p&gt;

&lt;p&gt;Before Agile was common, the Waterfall model was a popular way to manage projects. Waterfall consists of sequential processes, for example, the design and architecture of a feature is first, development work is “done”, it is handed to Ops, then from Ops to QA, and if bugs are found, return to development. Scheduled releases are when the features should be done and ready for live public use.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pitfalls
&lt;/h4&gt;

&lt;p&gt;Sometimes frameworks like Scrum are adopted to help establish processes and speed up software delivery, and sometimes these processes will go overboard and create burnout, slower deliveries, and mistrust.&lt;/p&gt;

&lt;p&gt;During sprint planning meetings, it is a common practice to estimate the complexity of the ticketed work; however, some may conceive the ticket estimate (points) as time-based values. This then is used to “predict” feature readiness or delivery and can cause tension when things do not go as planned. Estimating’s primary purpose is to avoid developer burnout by ensuring work based on complexity is given to the most appropriately experienced individuals (junior engineers may not take an XL ticket).&lt;/p&gt;

&lt;p&gt;Scrum prescribes certain meetings to take place during and before sprints, and Agile does focus a bit on communication; yet, this pattern tends to evolve into more and more meetings, where 80% of developer time is spent in meetings and 20% coding and designing.&lt;/p&gt;

&lt;p&gt;Scrum can often lead to a misperceived balance of communication. The engineers on the development team should be trusted enough to predict how long it may take to build x feature and what it takes to build it. Without this trust or communication from engineers to stakeholders, unreasonable expectations and delivery dates, also known as a Crunch deadline, can happen more often than not, leading to developer burnout and cut corners.&lt;/p&gt;

&lt;p&gt;The easiest way to avoid, or recover from, these pitfalls, is to take a step back from your processes and revisit the principles.&lt;/p&gt;

&lt;h4&gt;
  
  
  Making it Modern: Roles in Agile Teams
&lt;/h4&gt;

&lt;p&gt;Much like Agile, the way teams adopt the philosophy is ever changing and really what matters most is what is productive for that team. The basic framework, adapted to a more modern approach prescribes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business Liaison (Product Owner)&lt;/strong&gt; — As the Liaison, the job entails providing the business/stakeholders with insights on what the team has developed thus far and coordinating with the Agility Lead/Project Manager on what the priorities of the business are. Data should be utilized to help inform the development of new features and presented to the business to aid in decision-making.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers&lt;/strong&gt;  — The Developers on the team get the planned work done. This work may consist of system and application architecture, developing ML models, developing APIs (Application Programming Interfaces), creating UIs and even developing UI wireframes. Developers are responsible for figuring out how to build the application and establishing priority and estimated delivery dates with the help of the Agility Lead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agility Lead (Scrum Master)&lt;/strong&gt; — The Agility Lead is also likely to be your qualified Project Manager. They help assist in running efficient meetings, establishing communication channels, arbitrating for the Liaison and the Developer Lead, and aiding in the visualization of timelines for deliverables.&lt;/p&gt;

&lt;h4&gt;
  
  
  Making it Modern: DevOps and NoOps Culture Adoption is Agile
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffo9gwi8025qso6in84p0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffo9gwi8025qso6in84p0.png" width="640" height="363"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DevOps Toolchain&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Teams&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DevOps is an organization philosophy to help the adoption of Agile. In Waterfall models, Ops and Developers are separate teams and involve separate pieces of work; but with the DevOps philosophy, developers widen the area of responsibility as they not only handle the design and development of projects (left circle) but also own the process into the maintenance and observability phase (right circle). With this workflow, the people who built the project know it best and how it should run as well. This allows developers to catch bugs and performance issues earlier and iterate.&lt;/p&gt;

&lt;p&gt;DevOps is a part of the shift-left culture, where the sooner you find issues the faster they are fixed and deliverables are better quality. In the verification phase, developer teams will write automated tests from the smallest testable code to how the feature or project integrates into the overall application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit Tests (business logic, where a function should always add 2 numbers outcome should be expected)&lt;/li&gt;
&lt;li&gt;Integration Tests (Behavior-driven is most common, if a user clicks — x should happen)&lt;/li&gt;
&lt;li&gt;End to End Tests (E2E how the feature works in the application)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your company can hire QA Testers, they can appreciate the added quality, while they try to find issues in newly released features outside of the sprint cadence your developers are working in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform Teams&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NoOps is a trending pattern that is the idea of fewer infrastructure Subject Matter experts needed on individual teams because the creation of infrastructure is instead approachable and user-friendly in design. The key to this is having Platform Teams!&lt;/p&gt;

&lt;p&gt;Platform Engineering Teams consist of Backend, Frontend, and Infrastructure subject matter experts who create internal tooling that helps application teams create services and projects that adhere to company standards and self-service infrastructure tooling (you want a new Database provisioned, you just need to fill out a small form and click go). Platform Teams help set up UI libraries and Internal Developer Portals that keep a catalog of your projects in your software ecosystem, giving managers a holistic view of what teams are doing, costing, and what makes the applications!&lt;/p&gt;

&lt;h4&gt;
  
  
  Making it Modern: Organization Structure, Trust, and Empowerment Matter
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;[O]rganizations which design systems (in the broad sense used here) are constrained to produce designs which are copies of the communication structures of these organizations. — Melvin E. Conway, How Do Committees Invent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it comes to organizing your engineering teams — let them do the work. Self-organizing teams allow the engineers to acquire the talent they need to build your products. The Engineering Lead of the team can work with their Engineering Manager, or in smaller companies this may be the same role, and either acquire the talent internally or, with the help of HR, externally. The team can then be responsible for a couple of different projects focused on business domains, for example in an e-commerce company, the team could be responsible for the checkout and navigation of the application. This will help build a decoupled system that can evolve as your business does.&lt;/p&gt;

&lt;p&gt;Empower the individuals that are a part of the company and/or team. They may just have the next big idea to create a better product or feature. This entails trusting the engineers hired to actively want to build new features and projects as well as collaborate with them to help establish viable delivery timelines.&lt;/p&gt;

&lt;p&gt;Agile methodology is easier said than done, and when implemented well, teams can deliver high-quality features faster. This is only a guide to help empower the creative process that is engineering applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://agilemanifesto.org/principles.html" rel="noopener noreferrer"&gt;Principles behind the Agile Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/what-is/scrum/" rel="noopener noreferrer"&gt;What is Scrum? - Scrum Methodology Explained - AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techtarget.com/searchitoperations/definition/NoOps" rel="noopener noreferrer"&gt;What is NoOps? - TechTarget Definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/advice/0/how-can-you-avoid-anti-patterns-harm-your-scrum-teams-collaboration" rel="noopener noreferrer"&gt;How can you avoid anti-patterns that harm your Scrum team's collaboration?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law" rel="noopener noreferrer"&gt;Conway's law - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>organization</category>
      <category>software</category>
      <category>agile</category>
    </item>
  </channel>
</rss>
