<?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>DEV Community: las</title>
    <description>The latest articles on DEV Community by las (@pyrolass).</description>
    <link>https://dev.to/pyrolass</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%2F1300588%2F0da91e24-9538-4f25-9a6b-6cfb48e626a7.jpeg</url>
      <title>DEV Community: las</title>
      <link>https://dev.to/pyrolass</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pyrolass"/>
    <language>en</language>
    <item>
      <title>Building a Two-Tower Recommendation System</title>
      <dc:creator>las</dc:creator>
      <pubDate>Mon, 02 Feb 2026 19:05:13 +0000</pubDate>
      <link>https://dev.to/pyrolass/building-a-two-tower-recommendation-system-2i0h</link>
      <guid>https://dev.to/pyrolass/building-a-two-tower-recommendation-system-2i0h</guid>
      <description>&lt;p&gt;I was using Algolia for search and recommendations on POSH, my ecommerce app. It worked great, but the bill kept growing. Every search request, every recommendation call—it all adds up when users are constantly browsing products.&lt;/p&gt;

&lt;p&gt;So I built my own recommendation system using a two-tower model. It's the same approach YouTube and Google use: one tower represents products as vectors, the other represents users based on their behavior. To get recommendations, you just find products closest to the user's vector.&lt;/p&gt;

&lt;p&gt;Here's how I built it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything starts with user behavior. I use Firebase Analytics to track how users interact with products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product viewed — just browsing&lt;/li&gt;
&lt;li&gt;Product clicked — showed interest&lt;/li&gt;
&lt;li&gt;Added to cart — strong intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all interactions are equal. Someone adding a product to cart is way more valuable than a passing view. So I weight them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;View&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Click&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add to cart&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Product Vectorization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All my products live in Elasticsearch. To make recommendations work, I need to represent each product as a vector — a list of 384 numbers that captures what the product is about.&lt;/p&gt;

&lt;p&gt;I use the &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; model from Sentence Transformers. It's fast, lightweight, and works well for semantic similarity.&lt;/p&gt;

&lt;p&gt;For each product, I combine its attributes into a single text string:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Nike Air Max | by Nike | Shoes | Sneakers | Running | Blue color | premium&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product name&lt;/li&gt;
&lt;li&gt;Merchant name&lt;/li&gt;
&lt;li&gt;Category hierarchy (parent → category → subcategory)&lt;/li&gt;
&lt;li&gt;Color&lt;/li&gt;
&lt;li&gt;Price tier (budget / mid-range / premium / luxury)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model turns this text into a 384-dimensional vector. Products with similar attributes end up close together in vector space — a blue Nike sneaker will be near other blue sneakers, other Nike products, and other premium shoes.&lt;/p&gt;

&lt;p&gt;These vectors get stored back in Elasticsearch as a &lt;code&gt;dense_vector&lt;/code&gt; field, ready for similarity search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Tower Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the core of the system. The user tower takes someone's interaction history and outputs a single vector that represents their preferences.&lt;/p&gt;

&lt;p&gt;Input: up to 20 recent interactions, each with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product's vector (384 dims)&lt;/li&gt;
&lt;li&gt;The interaction type (view, click, or add-to-cart)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: one user vector (384 dims) that lives in the same space as product vectors&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model combines each product vector with an embedding for the interaction type. A clicked product gets different treatment than a viewed one.&lt;/p&gt;

&lt;p&gt;Then it runs through a multi-head attention layer — this lets the model figure out which interactions matter most. Maybe that one add-to-cart from yesterday is more important than ten views from last week.&lt;/p&gt;

&lt;p&gt;I also add recency decay. Newer interactions get higher weight. Someone's taste from yesterday matters more than what they looked at two weeks ago.&lt;/p&gt;

&lt;p&gt;Finally, everything gets pooled into a single vector and normalized. This user vector now sits in the same 384-dimensional space as all the products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I trained the model using contrastive learning. For each user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Positive: the next product they actually interacted with&lt;/li&gt;
&lt;li&gt;Negatives: 10 random products they didn't interact with&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model learns to push the user vector closer to products they'll engage with, and away from ones they won't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-Time Updates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Training the model is a one-time thing. But user preferences change constantly — someone might discover a new brand or shift from sneakers to boots. The system needs to keep up.&lt;/p&gt;

&lt;p&gt;I use AWS SQS to handle this. When a user interacts with a product, Firebase sends an event, and a message lands in my queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5678&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product_clicked"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An SQS consumer picks it up and:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the product's vector from Elasticsearch&lt;/li&gt;
&lt;li&gt;Loads the user's recent interaction history&lt;/li&gt;
&lt;li&gt;Runs it through the trained user tower model&lt;/li&gt;
&lt;li&gt;Saves the new user vector back to Elasticsearch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing takes milliseconds. By the time the user scrolls to the next page, their recommendations are already updated.&lt;/p&gt;

&lt;p&gt;I also prune old interactions — anything older than 2 days gets dropped. This keeps the model focused on recent behavior, not what someone browsed months ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendations with Cosine Similarity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now the fun part — actually recommending products.&lt;/p&gt;

&lt;p&gt;Both user vectors and product vectors live in the same 384-dimensional space. To find relevant products, I just look for the ones closest to the user's vector.&lt;/p&gt;

&lt;p&gt;When a user browses products, the API checks if they have a stored vector. If they do, Elasticsearch uses script_score to rank products by cosine similarity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script: {
  source: "cosineSimilarity(params.user_vector, 'product_vector') + 1.0",
  params: { user_vector: userVector }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;+ 1.0&lt;/code&gt; shifts scores to positive range since cosine similarity can be negative.&lt;/p&gt;

&lt;p&gt;If the user has no vector yet (new user, not enough interactions), it falls back to default sorting — popularity score and recency. Same goes if they explicitly sort by price.&lt;/p&gt;

&lt;p&gt;The result: logged-in users with interaction history get a personalized feed. Everyone else still gets a sensible default. No hard-coded limits on recommendations — it works with the existing pagination, just reordered by relevance to that user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results &amp;amp; Learnings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll be honest — I'm not a data scientist. This was my first time building anything like this. I just knew Algolia was too expensive and figured there had to be a way to do it myself.&lt;/p&gt;

&lt;p&gt;Turns out there was.&lt;/p&gt;

&lt;p&gt;I self-hosted everything — Elasticsearch, the PyTorch model, the SQS consumers. No managed ML services, no third-party recommendation APIs. Just my own infrastructure.&lt;/p&gt;

&lt;p&gt;An unexpected bonus: latency dropped. When everything runs on the same private network, there's no round-trip to external APIs. My app server talks to Elasticsearch over the local subnet — way faster than hitting Algolia's servers.&lt;/p&gt;

&lt;p&gt;Since launching the two-tower model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40% increase in app orders&lt;/li&gt;
&lt;li&gt;10% increase in user retention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users are finding products they actually want, and they're coming back more often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model works, but there's room to improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More events&lt;/strong&gt; — &lt;code&gt;adding product_favorited&lt;/code&gt;, &lt;code&gt;product_shared&lt;/code&gt;, and &lt;code&gt;product_purchased&lt;/code&gt; to capture stronger intent signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product labels&lt;/strong&gt; — tagging products with attributes like "vintage", "handmade", "streetwear" and using those labels to fine-tune the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't need a machine learning team to build personalized recommendations. The two-tower architecture is well-documented, PyTorch is approachable, and tools like Elasticsearch and SQS handle the infrastructure. If your recommendation costs are eating into your margins, it might be worth building your own.&lt;/p&gt;

&lt;p&gt;If you've built something similar or have suggestions to improve this approach, I'd love to hear from you.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Solid Principles in GO with examples</title>
      <dc:creator>las</dc:creator>
      <pubDate>Sat, 24 Aug 2024 13:25:06 +0000</pubDate>
      <link>https://dev.to/pyrolass/solid-principles-in-go-with-examples-o55</link>
      <guid>https://dev.to/pyrolass/solid-principles-in-go-with-examples-o55</guid>
      <description>&lt;p&gt;While Go isn't traditionally considered an object-oriented programming (OOP) language due to its lack of classes and objects, we can still apply SOLID principles to write cleaner, more maintainable code. Let's dive into how we can implement these principles in Go, demonstrating that good design transcends language paradigms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are SOLID Principles?
&lt;/h2&gt;

&lt;p&gt;SOLID is an acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. Originally conceived for OOP, we'll see how they can be adapted to Go's unique features.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Single Responsibility Principle (SRP)
&lt;/h2&gt;

&lt;p&gt;SRP states that a module should be responsible for one, and only one, reason to change. In Go, we can achieve this by creating focused structs and interfaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;singleresponsibility&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"time"&lt;/span&gt;

&lt;span class="c"&gt;// SRP states that a module should be responsible for one, and only one, reason to change.&lt;/span&gt;
&lt;span class="c"&gt;// In Go, we can achieve this by creating focused structs and interfaces.&lt;/span&gt;

&lt;span class="c"&gt;// Creating the entity for order&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OrderId&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;OrderTotal&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;
    &lt;span class="n"&gt;CreatedAt&lt;/span&gt;  &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Defining the interface&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IOrderStore&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Defining the store and its dependencies&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;OrderStore&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// define the dependencies&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewOrderStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;IOrderStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;OrderStore&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;OrderStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OrderTotal&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;23.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By separating the &lt;code&gt;Order&lt;/code&gt; entity from the &lt;code&gt;OrderStore&lt;/code&gt;, we've ensured each struct has a single responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Open/Closed Principle (OCP)
&lt;/h2&gt;

&lt;p&gt;OCP suggests that software entities should be open for extension but closed for modification. Go's interfaces make this principle easy to implement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;openclosed&lt;/span&gt;

&lt;span class="c"&gt;// OCP suggests that software entities should be open for extension but closed for modification.&lt;/span&gt;
&lt;span class="c"&gt;// Go's interfaces make this principle easy to implement.&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HorsePowerCalculator&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;CalculateHorsePower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;M3&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewM3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;HorsePowerCalculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;M3&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;M3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CalculateHorsePower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;473&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Porche911&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewPorche911&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;HorsePowerCalculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Porche911&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Porche911&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CalculateHorsePower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;388&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we can add new car types without modifying existing code, simply by implementing the &lt;code&gt;HorsePowerCalculator&lt;/code&gt; interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Liskov Substitution Principle (LSP)
&lt;/h2&gt;

&lt;p&gt;LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In Go, we can demonstrate this with interfaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;liskov&lt;/span&gt;

&lt;span class="c"&gt;// LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.&lt;/span&gt;
&lt;span class="c"&gt;// In Go, we can demonstrate this with interfaces.&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ICar&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Drive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IFastCar&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ICar&lt;/span&gt;
    &lt;span class="n"&gt;Fast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;M3&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Altima&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewM3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;IFastCar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;M3&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;M3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Drive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;M3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Fast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewAltima&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;ICar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Altima&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Altima&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Drive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;M3&lt;/code&gt; and &lt;code&gt;Altima&lt;/code&gt; can be used wherever an &lt;code&gt;ICar&lt;/code&gt; is expected, with &lt;code&gt;M3&lt;/code&gt; providing additional functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Interface Segregation Principle (ISP)
&lt;/h2&gt;

&lt;p&gt;ISP advocates for many client-specific interfaces rather than one general-purpose interface. Go's lightweight interfaces are perfect for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;interfacesegregation&lt;/span&gt;

&lt;span class="c"&gt;// ISP advocates for many client-specific interfaces rather than one general-purpose interface.&lt;/span&gt;
&lt;span class="c"&gt;// Go's lightweight interfaces are perfect for this.&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IReadUser&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IWriteUser&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UserReadStore&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewUserRead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;IReadUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;UserReadStore&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserReadStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pyro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UserWriteStore&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewUserWrite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;IWriteUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;UserWriteStore&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserWriteStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By separating read and write operations, clients can depend only on the methods they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Dependency Inversion Principle (DIP)
&lt;/h2&gt;

&lt;p&gt;DIP states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;dependencyinversion&lt;/span&gt;

&lt;span class="c"&gt;// DIP states that high-level modules should not depend on low-level modules; both should depend on abstractions.&lt;/span&gt;
&lt;span class="c"&gt;// Abstractions should not depend on details; details should depend on abstractions.&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IReadUser&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UserReadStore&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewUserRead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;IReadUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;UserReadStore&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserReadStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pyro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserReadStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pyro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UserHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;userStore&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewUserRead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;userStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// this function doesn't work because there is no abstraction implementation of it&lt;/span&gt;
    &lt;span class="c"&gt;// userStore.CreateUser()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UserHandler&lt;/code&gt; depends on the &lt;code&gt;IReadUser&lt;/code&gt; interface, not on the concrete &lt;code&gt;UserReadStore&lt;/code&gt;, allowing for easy substitution and testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dive Into the Code
&lt;/h2&gt;

&lt;p&gt;The complete code for this project is available on GitHub.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://github.com/pyrolass/grpc-microservice-go" rel="noopener noreferrer"&gt;https://github.com/pyrolass/grpc-microservice-go&lt;/a&gt;)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Microservices Architecture with gRPC and Kafka in Golang</title>
      <dc:creator>las</dc:creator>
      <pubDate>Mon, 01 Apr 2024 17:52:11 +0000</pubDate>
      <link>https://dev.to/pyrolass/building-a-microservices-architecture-with-grpc-and-kafka-in-golang-l7g</link>
      <guid>https://dev.to/pyrolass/building-a-microservices-architecture-with-grpc-and-kafka-in-golang-l7g</guid>
      <description>&lt;p&gt;Today, I want to take you through the journey of building a robust microservices architecture using gRPC for efficient internal communication and Kafka for reliable event streaming. One of my biggest challenges, which often made me feel like an imposter, was understanding microservices and gRPC and how to set them up correctly. This project is my first approach into constructing a microservices architecture, and I’m excited to share the learnings and outcomes with you.&lt;/p&gt;

&lt;p&gt;The concepts and setup explained here were heavily influenced by &lt;a href="https://www.youtube.com/@anthonygg_" rel="noopener noreferrer"&gt;https://www.youtube.com/@anthonygg_&lt;/a&gt;, who provided an excellent foundation and clear guidance that I could build upon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fggzxqjeeshcyji1506eh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fggzxqjeeshcyji1506eh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Under the Hood?
&lt;/h2&gt;

&lt;p&gt;The architecture is composed of several key services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gateway&lt;/code&gt;: The entry point for all client requests, translating JSON HTTP requests to gRPC.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;producer-micro&lt;/code&gt;: Handles incoming driver data and produces messages to Kafka.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;consumer-micro&lt;/code&gt;: Consumes messages from Kafka and persists them into the database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write-db-micro&lt;/code&gt;: Microservice responsible for database write operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read-db-micro&lt;/code&gt;: Microservice responsible for database read operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;distance-micro&lt;/code&gt;: Calculates the distance traveled by drivers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spam&lt;/code&gt;: This is a simple spam services that spams the gateway with locations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build and Run
&lt;/h2&gt;

&lt;p&gt;Getting everything up and running is a breeze with the provided Makefile. Each microservice can be built and started with simple commands.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and run gateway: &lt;code&gt;make gate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run producer-micro: &lt;code&gt;make prod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run consumer-micro: &lt;code&gt;make con&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run write-db-micro: &lt;code&gt;make write&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run read-db-micro: &lt;code&gt;make read&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run distance-micro: &lt;code&gt;make dist&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build and run spam: &lt;code&gt;make spam&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate gRPC code from &lt;code&gt;.proto&lt;/code&gt; files, run: &lt;code&gt;make proto&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Setup
&lt;/h2&gt;

&lt;p&gt;This project includes a &lt;code&gt;docker-compose&lt;/code&gt; file for easy setup of the microservices and MongoDB as containers. The &lt;code&gt;docker-compose&lt;/code&gt; file orchestrates the creation and interconnection of all the necessary containers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the container : &lt;code&gt;docker-compose up -d&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Endpoint
&lt;/h2&gt;

&lt;p&gt;The following endpoint is available on the gateway for drivers to send their data:&lt;/p&gt;

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

- http://localhost:3000/api/v1/drivers

- http://localhost:3000/api/v1/drivers/1


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

&lt;/div&gt;

&lt;p&gt;Feel free to dive into the README on &lt;a href="https://github.com/pyrolass/grpc-microservice-go" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for more detailed instructions on building and running the services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dive Into the Code
&lt;/h2&gt;

&lt;p&gt;The complete code for this project is available on GitHub. Check it out, give it a star if you like it, and contribute if you have ideas on how to improve it!&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://github.com/pyrolass/grpc-microservice-go" rel="noopener noreferrer"&gt;https://github.com/pyrolass/grpc-microservice-go&lt;/a&gt;)&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
