<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Gyri Infotech | Tech Memoir]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.gyri.tech/</link><image><url>https://blog.gyri.tech/favicon.png</url><title>Gyri Infotech | Tech Memoir</title><link>https://blog.gyri.tech/</link></image><generator>Ghost 5.82</generator><lastBuildDate>Sun, 19 Jul 2026 09:54:17 GMT</lastBuildDate><atom:link href="https://blog.gyri.tech/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[APQP in the Automotive Industry: Explained for a 5-Year-Old Engineer]]></title><description><![CDATA[<blockquote>
<p>A simple beginner-friendly guide to Advanced Product Quality Planning (APQP) in the automotive industry with examples, flow diagrams, and easy explanations.</p>
</blockquote>
<hr>
<p>Imagine you want to build the <strong>best toy car ever</strong> for your friends.</p>
<p>Would you start cutting plastic and gluing wheels together immediately?</p>
<p>Probably not.</p>
<p>You would first:</p>
<ul>
<li>Think</li></ul>]]></description><link>https://blog.gyri.tech/what-is-apqp-explained/</link><guid isPermaLink="false">6a4cd249a59c17040f4ea445</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Tue, 07 Jul 2026 10:51:35 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1567789884554-0b844b597180?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNhciUyMGluZHVzdHJ5fGVufDB8fHx8MTc4MzQyMDQ5MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="https://images.unsplash.com/photo-1567789884554-0b844b597180?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNhciUyMGluZHVzdHJ5fGVufDB8fHx8MTc4MzQyMDQ5MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="APQP in the Automotive Industry: Explained for a 5-Year-Old Engineer"><p>A simple beginner-friendly guide to Advanced Product Quality Planning (APQP) in the automotive industry with examples, flow diagrams, and easy explanations.</p>
</blockquote>
<hr>
<p>Imagine you want to build the <strong>best toy car ever</strong> for your friends.</p>
<p>Would you start cutting plastic and gluing wheels together immediately?</p>
<p>Probably not.</p>
<p>You would first:</p>
<ul>
<li>Think about what the toy should look like</li>
<li>Draw a picture</li>
<li>Gather the right materials</li>
<li>Build one toy</li>
<li>Test if it works</li>
<li>Fix any problems</li>
<li>Then make lots of toys</li>
</ul>
<p>That is exactly how car companies build real vehicles and their parts.</p>
<p>This process is called <strong>APQP</strong>.</p>
<hr>
<h1 id="what-does-apqp-stand-for">What Does APQP Stand For?</h1>
<p><strong>APQP = Advanced Product Quality Planning</strong></p>
<p>Don&apos;t let the long name scare you.</p>
<p>It simply means:</p>
<blockquote>
<p><strong>A step-by-step way to make sure a product is good before making thousands or millions of them.</strong></p>
</blockquote>
<p>Instead of fixing mistakes after production, companies try to prevent mistakes before production starts.</p>
<hr>
<h1 id="why-do-we-need-apqp">Why Do We Need APQP?</h1>
<p>Imagine making <strong>100,000 cars</strong>.</p>
<p>If one tiny bolt is installed incorrectly...</p>
<p>You don&apos;t fix one car.</p>
<p>You fix <strong>100,000 cars.</strong></p>
<p>That could cost millions of dollars.</p>
<p>Instead, companies follow this simple idea:</p>
<blockquote>
<p><strong>Think first. Test first. Build later.</strong></p>
</blockquote>
<hr>
<h1 id="a-simple-toy-example">A Simple Toy Example</h1>
<p>Imagine your teacher says:</p>
<blockquote>
<p>&quot;Tomorrow, everyone must bring one paper airplane.&quot;</p>
</blockquote>
<p>There are two ways to do it.</p>
<h2 id="%E2%9D%8C-without-apqp">&#x274C; Without APQP</h2>
<pre><code class="language-text">Make airplane quickly

&#x2193;

It doesn&apos;t fly

&#x2193;

Teacher is unhappy
</code></pre>
<h2 id="%E2%9C%85-with-apqp">&#x2705; With APQP</h2>
<pre><code class="language-text">Think

&#x2193;

Choose the best paper

&#x2193;

Practice folding

&#x2193;

Test flying

&#x2193;

Improve

&#x2193;

Make the final airplane
</code></pre>
<p>The second approach gives everyone a much better airplane.</p>
<p>That&apos;s APQP.</p>
<hr>
<h1 id="real-automotive-example">Real Automotive Example</h1>
<p>Suppose a company wants to manufacture a <strong>car door handle</strong>.</p>
<h3 id="without-apqp">Without APQP</h3>
<pre><code class="language-text">Design

&#x2193;

Manufacture 50,000 handles

&#x2193;

Oops!

The handle breaks easily.

&#x2193;

Replace every handle
</code></pre>
<p>This is expensive and wastes time.</p>
<h3 id="with-apqp">With APQP</h3>
<pre><code class="language-text">Design

&#x2193;

Build prototype

&#x2193;

Test

&#x2193;

Improve

&#x2193;

Test again

&#x2193;

Approve

&#x2193;

Mass production
</code></pre>
<p>Now customers receive reliable products.</p>
<hr>
<h1 id="think-of-building-a-house">Think of Building a House</h1>
<p>You don&apos;t start by building the roof.</p>
<p>You follow a plan.</p>
<pre><code class="language-text">Idea

&#x2193;

Drawing

&#x2193;

Buy materials

&#x2193;

Build

&#x2193;

Inspect

&#x2193;

Move in
</code></pre>
<p>Building cars works the same way.</p>
<hr>
<h1 id="the-5-phases-of-apqp">The 5 Phases of APQP</h1>
<p>Every new automotive product follows five simple phases.</p>
<hr>
<h1 id="phase-1-%E2%80%94-plan">Phase 1 &#x2014; Plan</h1>
<p>First, understand <strong>what the customer wants.</strong></p>
<p>Questions include:</p>
<ul>
<li>What product are we making?</li>
<li>How strong should it be?</li>
<li>How much should it cost?</li>
<li>When should it be ready?</li>
</ul>
<h3 id="example">Example</h3>
<p>A customer says:</p>
<blockquote>
<p>&quot;I need a steering wheel.&quot;</p>
</blockquote>
<p>The team asks:</p>
<ul>
<li>Leather or plastic?</li>
<li>Airbag included?</li>
<li>Buttons required?</li>
<li>What size?</li>
</ul>
<p>This phase is all about planning.</p>
<hr>
<h1 id="phase-2-%E2%80%94-product-design">Phase 2 &#x2014; Product Design</h1>
<p>Now engineers design the product.</p>
<p>Example:</p>
<pre><code class="language-text">Steering Wheel

Diameter: 380 mm

Leather Cover

Airbag

Control Buttons

Weight: 1.8 kg
</code></pre>
<p>Now everyone knows exactly what needs to be built.</p>
<hr>
<h1 id="phase-3-%E2%80%94-process-design">Phase 3 &#x2014; Process Design</h1>
<p>The question changes.</p>
<p>Instead of asking:</p>
<blockquote>
<p>&quot;How should we design it?&quot;</p>
</blockquote>
<p>We ask:</p>
<blockquote>
<p>&quot;How will we manufacture it?&quot;</p>
</blockquote>
<p>Example production flow:</p>
<pre><code class="language-text">Raw Material

&#x2193;

Machine

&#x2193;

Assembly

&#x2193;

Painting

&#x2193;

Inspection

&#x2193;

Packaging
</code></pre>
<p>This phase plans how the factory will produce the product.</p>
<hr>
<h1 id="phase-4-%E2%80%94-product-process-validation">Phase 4 &#x2014; Product &amp; Process Validation</h1>
<p>Now it&apos;s time to test everything.</p>
<p>Questions include:</p>
<ul>
<li>Does the product work?</li>
<li>Is it safe?</li>
<li>Can the factory consistently make good parts?</li>
</ul>
<p>Example:</p>
<p>Manufacture <strong>300 steering wheels</strong>.</p>
<p>Test every one.</p>
<p>If everything passes...</p>
<p>Production can begin.</p>
<p>If not...</p>
<p>Fix the problem and test again.</p>
<hr>
<h1 id="phase-5-%E2%80%94-production">Phase 5 &#x2014; Production</h1>
<p>Everything has been approved.</p>
<p>Now the factory starts producing thousands&#x2014;or even millions&#x2014;of parts.</p>
<p>Even during production, quality is checked continuously.</p>
<hr>
<h1 id="the-complete-apqp-flow">The Complete APQP Flow</h1>
<pre><code class="language-text">Customer Requirement

        &#x2502;

        &#x25BC;

Phase 1
Planning

        &#x2502;

        &#x25BC;

Phase 2
Product Design

        &#x2502;

        &#x25BC;

Phase 3
Process Design

        &#x2502;

        &#x25BC;

Phase 4
Testing &amp; Validation

        &#x2502;

        &#x25BC;

Problems Found?

   Yes &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2510;
        &#x2502;              &#x2502;
        &#x25BC;              &#x2502;
   Improve Design      &#x2502;
        &#x2502;              &#x2502;
        &#x2514;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x25BA; Test Again

No

&#x2193;

Phase 5

Mass Production
</code></pre>
<hr>
<h1 id="another-easy-example-baking-cookies-%F0%9F%8D%AA">Another Easy Example: Baking Cookies &#x1F36A;</h1>
<p>Imagine your mom wants to bake cookies.</p>
<h3 id="phase-1">Phase 1</h3>
<p>Choose a recipe.</p>
<hr>
<h3 id="phase-2">Phase 2</h3>
<p>Select ingredients.</p>
<pre><code class="language-text">Flour

Sugar

Butter

Chocolate Chips
</code></pre>
<hr>
<h3 id="phase-3">Phase 3</h3>
<p>Plan the baking process.</p>
<pre><code class="language-text">Mix

&#x2193;

Shape

&#x2193;

Bake

&#x2193;

Cool

&#x2193;

Pack
</code></pre>
<hr>
<h3 id="phase-4">Phase 4</h3>
<p>Taste one cookie.</p>
<p>Too hard?</p>
<p>Add more butter.</p>
<p>Too soft?</p>
<p>Bake longer.</p>
<hr>
<h3 id="phase-5">Phase 5</h3>
<p>Bake 500 cookies using the improved recipe.</p>
<p>That&apos;s APQP in everyday life.</p>
<hr>
<h1 id="another-automotive-example">Another Automotive Example</h1>
<p>Suppose a company is designing a <strong>brake pedal</strong>.</p>
<h3 id="without-apqp">Without APQP</h3>
<pre><code class="language-text">Design

&#x2193;

Manufacture

&#x2193;

Customer uses pedal

&#x2193;

Pedal breaks
</code></pre>
<p>A dangerous situation.</p>
<hr>
<h3 id="with-apqp">With APQP</h3>
<pre><code class="language-text">Customer Requirement

&#x2193;

Design

&#x2193;

Computer Simulation

&#x2193;

Prototype

&#x2193;

Strength Testing

&#x2193;

Improve

&#x2193;

Mass Production
</code></pre>
<p>Now the brake pedal is much safer.</p>
<hr>
<h1 id="apqp-is-like-preparing-for-an-exam">APQP Is Like Preparing for an Exam</h1>
<p>Imagine you have a big school exam.</p>
<p>What would you do?</p>
<pre><code class="language-text">Study

&#x2193;

Practice

&#x2193;

Find Mistakes

&#x2193;

Practice Again

&#x2193;

Take Exam

&#x2193;

Pass
</code></pre>
<p>You don&apos;t wait until the exam to discover what you don&apos;t know.</p>
<p>Car companies think the same way.</p>
<p>They don&apos;t wait until customers find problems.</p>
<p>They discover and fix problems first.</p>
<hr>
<h1 id="who-works-during-apqp">Who Works During APQP?</h1>
<p>Many different teams work together.</p>
<table>
<thead>
<tr>
<th>Team</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr>
<td>Customer</td>
<td>Defines product requirements</td>
</tr>
<tr>
<td>Design Engineers</td>
<td>Design the product</td>
</tr>
<tr>
<td>Manufacturing Engineers</td>
<td>Design the production process</td>
</tr>
<tr>
<td>Quality Engineers</td>
<td>Plan inspections and testing</td>
</tr>
<tr>
<td>Suppliers</td>
<td>Provide materials and components</td>
</tr>
<tr>
<td>Production Team</td>
<td>Manufacture the product</td>
</tr>
</tbody>
</table>
<p>Everyone collaborates from the beginning.</p>
<hr>
<h1 id="an-easy-way-to-remember-apqp">An Easy Way to Remember APQP</h1>
<p>Think of APQP as this simple journey:</p>
<pre><code class="language-text">Think

&#x2193;

Plan

&#x2193;

Design

&#x2193;

Build

&#x2193;

Test

&#x2193;

Improve

&#x2193;

Produce
</code></pre>
<p>Or even shorter:</p>
<blockquote>
<p><strong>Think &#x2192; Plan &#x2192; Test &#x2192; Improve &#x2192; Produce</strong></p>
</blockquote>
<hr>
<h1 id="key-benefits-of-apqp">Key Benefits of APQP</h1>
<p>Using APQP helps companies:</p>
<ul>
<li>Reduce mistakes</li>
<li>Improve product quality</li>
<li>Lower manufacturing costs</li>
<li>Prevent customer complaints</li>
<li>Improve safety</li>
<li>Deliver products on time</li>
<li>Build consistent products every time</li>
</ul>
<hr>
<h1 id="final-summary">Final Summary</h1>
<p>APQP is simply a <strong>smart planning process</strong> used by automotive companies.</p>
<p>Instead of rushing into production, companies:</p>
<ol>
<li>Understand customer needs</li>
<li>Design the product</li>
<li>Design the manufacturing process</li>
<li>Test everything carefully</li>
<li>Start mass production only after everything works</li>
</ol>
<p>Just like practicing before a school play or testing a recipe before serving guests, APQP helps manufacturers build safer, better, and more reliable products.</p>
<hr>
<h1 id="remember-this-one-sentence">Remember This One Sentence</h1>
<blockquote>
<p><strong>APQP is like practicing before the final performance&#x2014;you plan everything, test everything, fix every mistake, and only then build thousands of products with confidence.</strong></p>
</blockquote>
<pre><code></code></pre>
]]></content:encoded></item><item><title><![CDATA[Business Model Canvas: A Practical Guide with QMS Issue Management Example]]></title><description><![CDATA[<blockquote>
<p>Learn how to design, analyze, and improve a business using the <strong>Business Model Canvas (BMC)</strong> framework. This guide explains each of the nine building blocks with a practical <strong>QMS (Quality Management System) Issue Management Platform</strong> example.</p>
</blockquote>
<hr>
<h1 id="what-is-a-business-model-canvas">What is a Business Model Canvas?</h1>
<p>A <strong>Business Model Canvas (BMC)</strong> is a one-page</p>]]></description><link>https://blog.gyri.tech/business-model-canvas-a-practical-guide-with-qms-issue-management-example/</link><guid isPermaLink="false">6a4b9da1a59c17040f4ea208</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Mon, 06 Jul 2026 14:21:19 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/07/00-bmc-hero.jpeg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="https://blog.gyri.tech/content/images/2026/07/00-bmc-hero.jpeg" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example"><p>Learn how to design, analyze, and improve a business using the <strong>Business Model Canvas (BMC)</strong> framework. This guide explains each of the nine building blocks with a practical <strong>QMS (Quality Management System) Issue Management Platform</strong> example.</p>
</blockquote>
<hr>
<h1 id="what-is-a-business-model-canvas">What is a Business Model Canvas?</h1>
<p>A <strong>Business Model Canvas (BMC)</strong> is a one-page strategic framework that helps organizations visualize how a business creates, delivers, and captures value.</p>
<p>Instead of writing a lengthy business plan, the Business Model Canvas allows founders, product managers, consultants, and business leaders to quickly understand:</p>
<ul>
<li>Who are the customers?</li>
<li>What value is being delivered?</li>
<li>How is value delivered?</li>
<li>How does the business generate revenue?</li>
<li>What resources and partners are required?</li>
</ul>
<p>The framework was introduced by <strong>Alexander Osterwalder</strong> and has become one of the most widely used business planning tools worldwide.</p>
<hr>
<h2 id="why-use-a-business-model-canvas">Why Use a Business Model Canvas?</h2>
<p>Business Model Canvas helps organizations:</p>
<ul>
<li>Validate business ideas quickly</li>
<li>Align teams around a common business vision</li>
<li>Identify business risks</li>
<li>Improve existing business models</li>
<li>Discover new revenue opportunities</li>
<li>Simplify strategic planning</li>
<li>Present ideas to investors and stakeholders</li>
</ul>
<hr>
<h1 id="the-9-building-blocks-of-business-model-canvas">The 9 Building Blocks of Business Model Canvas</h1>
<p>The Business Model Canvas consists of nine interconnected building blocks.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/1280px-Business_Model_Canvas.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"><br>
<em>Reference - <a href="https://en.wikipedia.org/wiki/Business_model_canvas?ref=blog.gyri.tech">https://en.wikipedia.org/wiki/Business_model_canvas</a></em></p>
<hr>
<h1 id="1-customer-segments">1. Customer Segments</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/02-bmc-customer-segments.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Customer Segments define <strong>who your customers are</strong>.</p>
<p>Every business serves one or more customer groups. Understanding them is the first step toward building a successful product.</p>
<p>Ask yourself:</p>
<ul>
<li>Who are our customers?</li>
<li>Who gets the most value?</li>
<li>Who pays?</li>
<li>Who uses the product?</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Primary customers include:</p>
<ul>
<li>OEM Quality Engineers</li>
<li>OEM Supplier Quality Teams</li>
<li>Supplier Engineers</li>
<li>Supplier Managers</li>
<li>Supplier Quality Teams</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Without clearly identifying customer segments, it becomes difficult to create products that solve real problems.</p>
<hr>
<h1 id="2-value-proposition">2. Value Proposition</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/02-bmc-value-proposition.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>The Value Proposition explains <strong>why customers choose your product</strong> instead of alternatives.</p>
<p>It answers:</p>
<blockquote>
<p>What problem are you solving?</p>
</blockquote>
<p>Examples include:</p>
<ul>
<li>Saving time</li>
<li>Reducing cost</li>
<li>Improving quality</li>
<li>Increasing productivity</li>
<li>Better customer experience</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>For OEMs</p>
<ul>
<li>Real-time supplier issue tracking</li>
<li>Faster issue resolution</li>
<li>Centralized quality management</li>
<li>Better supplier performance visibility</li>
</ul>
<p>For Suppliers</p>
<ul>
<li>Receive issues instantly</li>
<li>Track corrective actions</li>
<li>Automated reminders</li>
<li>Better collaboration with OEMs</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Customers don&apos;t buy software.</p>
<p>They buy solutions to their problems.</p>
<hr>
<h1 id="3-key-resources">3. Key Resources</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/03-bmc-key-resources.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Key Resources are the assets required to deliver your value proposition.</p>
<p>They can include:</p>
<ul>
<li>Technology</li>
<li>Software</li>
<li>Infrastructure</li>
<li>Employees</li>
<li>Brand</li>
<li>Data</li>
<li>Intellectual Property</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Key resources include:</p>
<ul>
<li>Cloud Platform</li>
<li>Issue Tracking Software</li>
<li>Notification Engine</li>
<li>Reporting Dashboard</li>
<li>Supplier Network</li>
<li>Customer Database</li>
<li>Security Infrastructure</li>
<li>Development Team</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Without the right resources, the business cannot operate effectively.</p>
<hr>
<h1 id="4-key-partners">4. Key Partners</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/04-bmc-key-partners.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Key Partners are organizations that help your business succeed.</p>
<p>Examples:</p>
<ul>
<li>Suppliers</li>
<li>Technology providers</li>
<li>Cloud vendors</li>
<li>Consultants</li>
<li>Payment providers</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Partners include:</p>
<ul>
<li>OEM Companies</li>
<li>Supplier Organizations</li>
<li>Cloud Infrastructure Providers</li>
<li>Email &amp; Notification Services</li>
<li>Authentication Providers</li>
<li>Technology Vendors</li>
<li>Compliance Consultants</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Strategic partnerships reduce costs and increase business capabilities.</p>
<hr>
<h1 id="5-key-activities">5. Key Activities</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/05-bmc-key-activities.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Key Activities describe the critical work required to operate the business.</p>
<p>Examples include:</p>
<ul>
<li>Software development</li>
<li>Marketing</li>
<li>Customer support</li>
<li>Sales</li>
<li>Operations</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Important activities:</p>
<ul>
<li>Build platform</li>
<li>Maintain software</li>
<li>Deploy new features</li>
<li>Monitor system uptime</li>
<li>Customer onboarding</li>
<li>Technical support</li>
<li>Security updates</li>
<li>Regulatory compliance</li>
<li>Platform monitoring</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Activities transform resources into customer value.</p>
<hr>
<h1 id="6-customer-relationships">6. Customer Relationships</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/07-bmc-customer-relationship.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Customer Relationships describe how you interact with customers throughout their journey.</p>
<p>Examples include:</p>
<ul>
<li>Self-service</li>
<li>Personal support</li>
<li>Dedicated account manager</li>
<li>Community forums</li>
<li>Knowledge base</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Relationship methods:</p>
<ul>
<li>99.9% System Availability</li>
<li>Automated Notifications</li>
<li>Email Alerts</li>
<li>Help Desk</li>
<li>Knowledge Base</li>
<li>Training Sessions</li>
<li>Customer Success Team</li>
<li>SLA-based Support</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Strong customer relationships improve retention and satisfaction.</p>
<hr>
<h1 id="7-revenue-streams">7. Revenue Streams</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/07-bmc-revenue-streams.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Revenue Streams explain <strong>how the business earns money.</strong></p>
<p>Possible models include:</p>
<ul>
<li>Subscription</li>
<li>Licensing</li>
<li>Usage-based pricing</li>
<li>Premium support</li>
<li>Consulting</li>
<li>Advertising</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Revenue sources:</p>
<ul>
<li>Subscription per organization</li>
<li>Number of users</li>
<li>Number of issues managed</li>
<li>Data storage usage</li>
<li>Premium analytics</li>
<li>Enterprise support</li>
<li>API integrations</li>
<li>Training services</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Revenue determines business sustainability.</p>
<hr>
<h1 id="8-cost-structure">8. Cost Structure</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/08-bmc-cost-structure.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Cost Structure identifies where money is spent.</p>
<p>Typical costs include:</p>
<ul>
<li>Salaries</li>
<li>Cloud hosting</li>
<li>Marketing</li>
<li>Licenses</li>
<li>Infrastructure</li>
<li>Research</li>
<li>Customer Support</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Major costs:</p>
<ul>
<li>Employee Salaries</li>
<li>Cloud Infrastructure</li>
<li>Technology Development</li>
<li>Software Licenses</li>
<li>Marketing</li>
<li>Customer Support</li>
<li>Security Compliance</li>
<li>Research &amp; Development</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Understanding costs helps improve profitability.</p>
<hr>
<h1 id="9-channels">9. Channels</h1>
<p><img src="https://blog.gyri.tech/content/images/2026/07/09-bmc-channel.png" alt="Business Model Canvas: A Practical Guide with QMS Issue Management Example" loading="lazy"></p>
<h2 id="what-is-it">What is it?</h2>
<p>Channels define how customers discover, purchase, and use your product.</p>
<p>Examples include:</p>
<ul>
<li>Website</li>
<li>Mobile App</li>
<li>Web Application</li>
<li>Social Media</li>
<li>Sales Team</li>
<li>Partners</li>
</ul>
<h3 id="example-qms-issue-management-platform">Example: QMS Issue Management Platform</h3>
<p>Customer channels:</p>
<ul>
<li>Web Application</li>
<li>Mobile Application</li>
<li>Email</li>
<li>Sales Team</li>
<li>Customer Portal</li>
<li>Social Media</li>
<li>Product Demonstrations</li>
<li>Online Documentation</li>
</ul>
<h3 id="why-it-matters">Why it matters</h3>
<p>Even a great product needs effective channels to reach customers.</p>
<hr>
<h1 id="business-model-canvas-example">Business Model Canvas Example</h1>
<h2 id="qms-issue-management-platform">QMS Issue Management Platform</h2>
<table>
<thead>
<tr>
<th>Building Block</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>Customer Segments</td>
<td>OEM Engineers, Supplier Engineers, Supplier Managers</td>
</tr>
<tr>
<td>Value Proposition</td>
<td>Real-time issue tracking, collaboration, faster issue resolution</td>
</tr>
<tr>
<td>Key Resources</td>
<td>Platform, Software, Cloud Infrastructure, Supplier Network</td>
</tr>
<tr>
<td>Key Partners</td>
<td>OEMs, Suppliers, Cloud Providers, Technology Vendors</td>
</tr>
<tr>
<td>Key Activities</td>
<td>Platform Development, Support, Compliance, Monitoring</td>
</tr>
<tr>
<td>Customer Relationships</td>
<td>Help Desk, Notifications, Knowledge Base, Customer Success</td>
</tr>
<tr>
<td>Revenue Streams</td>
<td>Subscription, User-based Pricing, Data Storage, Premium Support</td>
</tr>
<tr>
<td>Cost Structure</td>
<td>Salaries, Cloud Hosting, Marketing, Licenses, R&amp;D</td>
</tr>
<tr>
<td>Channels</td>
<td>Web App, Mobile App, Sales Team, Email, Social Media</td>
</tr>
</tbody>
</table>
<hr>
<h1 id="tips-for-creating-an-effective-business-model-canvas">Tips for Creating an Effective Business Model Canvas</h1>
<p>&#x2705; Focus on solving customer problems.</p>
<p>&#x2705; Keep customer segments specific.</p>
<p>&#x2705; Clearly define your unique value proposition.</p>
<p>&#x2705; Identify measurable revenue streams.</p>
<p>&#x2705; Build strategic partnerships.</p>
<p>&#x2705; Regularly revisit and update your canvas.</p>
<p>&#x2705; Validate assumptions through customer feedback.</p>
<hr>
<h1 id="common-mistakes">Common Mistakes</h1>
<ul>
<li>Trying to serve everyone</li>
<li>Weak value proposition</li>
<li>Ignoring customer relationships</li>
<li>Missing revenue strategy</li>
<li>Underestimating operating costs</li>
<li>Not identifying key partners</li>
<li>Treating the canvas as a one-time activity</li>
</ul>
<hr>
<h1 id="benefits-of-using-business-model-canvas">Benefits of Using Business Model Canvas</h1>
<ul>
<li>Simple one-page business overview</li>
<li>Faster strategic planning</li>
<li>Easier stakeholder communication</li>
<li>Better product-market fit</li>
<li>Improved innovation</li>
<li>Faster business validation</li>
<li>Supports agile business development</li>
</ul>
<hr>
<h1 id="conclusion">Conclusion</h1>
<p>The Business Model Canvas is one of the most effective frameworks for understanding and designing a business. By breaking a business into nine essential building blocks, teams can quickly identify strengths, weaknesses, opportunities, and risks.</p>
<p>Whether you&apos;re launching a startup, improving an existing product, or building an enterprise platform like a <strong>QMS Issue Management System</strong>, the Business Model Canvas provides a structured way to think about customers, value, operations, and financial sustainability.</p>
<p>A well-maintained Business Model Canvas is not just a planning document&#x2014;it becomes a living blueprint that evolves alongside your business.</p>
]]></content:encoded></item><item><title><![CDATA[Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions]]></title><description><![CDATA[<blockquote>
<p>Before deciding whether to build software internally or purchase an existing solution, organizations should first understand <strong>which part of the business the software supports</strong>. The answer is not purely technical&#x2014;it is a strategic business decision.</p>
</blockquote>
<hr>
<h2 id="introduction-why-build-vs-buy-matters">Introduction: Why Build vs Buy Matters</h2>
<p>Every organization eventually faces the question:</p>
<blockquote>
<p><strong>Should</strong></p></blockquote>]]></description><link>https://blog.gyri.tech/making-build-vs-buy-decisions-for-technology-solutions/</link><guid isPermaLink="false">6a4a3b3fa59c17040f4ea1b6</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Sun, 05 Jul 2026 13:02:42 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/07/01-build-vs-buy-hero.jpeg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="https://blog.gyri.tech/content/images/2026/07/01-build-vs-buy-hero.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions"><p>Before deciding whether to build software internally or purchase an existing solution, organizations should first understand <strong>which part of the business the software supports</strong>. The answer is not purely technical&#x2014;it is a strategic business decision.</p>
</blockquote>
<hr>
<h2 id="introduction-why-build-vs-buy-matters">Introduction: Why Build vs Buy Matters</h2>
<p>Every organization eventually faces the question:</p>
<blockquote>
<p><strong>Should we build this solution ourselves or buy an existing product?</strong></p>
</blockquote>
<p>Building software requires significant investment in engineering talent, maintenance, and long-term ownership. Buying software accelerates implementation but may limit flexibility and differentiation.</p>
<p>The right decision depends on <strong>how strategically important the capability is to the business</strong>.</p>
<hr>
<h1 id="understanding-technology-solutions">Understanding Technology Solutions</h1>
<p>Not every software system contributes equally to business success.</p>
<p>Some systems create competitive advantage.</p>
<p>Others simply help run the business efficiently.</p>
<p>Understanding this difference is the foundation of Build vs Buy decisions.</p>
<hr>
<h1 id="business-domains-and-subdomains">Business Domains and Subdomains</h1>
<p>A business can be divided into multiple subdomains.</p>
<p>These subdomains generally fall into three categories:</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Purpose</th>
<th>Competitive Advantage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Core</td>
<td>Defines the business</td>
<td>High</td>
</tr>
<tr>
<td>Supporting</td>
<td>Enables business operations</td>
<td>Medium</td>
</tr>
<tr>
<td>Generic</td>
<td>Common across industries</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p><img src="https://blog.gyri.tech/content/images/2026/07/02-build-vs-buy-core-supporting-generic-subdomain.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="generic-subdomain">Generic Subdomain</h1>
<p>Generic subdomains solve problems that almost every organization has.</p>
<p>Examples include:</p>
<ul>
<li>Email</li>
<li>Payroll</li>
<li>HR Management</li>
<li>Accounting</li>
<li>Calendar</li>
<li>Authentication</li>
</ul>
<p>These functions rarely differentiate one company from another.</p>
<h3 id="recommended-strategy">Recommended Strategy</h3>
<p>&#x2705; Buy established products</p>
<p>or</p>
<p>&#x2705; Use SaaS platforms</p>
<p>Examples:</p>
<ul>
<li>Microsoft 365</li>
<li>Google Workspace</li>
<li>SAP SuccessFactors</li>
<li>Workday</li>
</ul>
<p>Building these systems usually offers little business value.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/03-build-vs-buy-generic-subdomain-strategy.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="supporting-subdomain">Supporting Subdomain</h1>
<p>Supporting subdomains are important but are not the primary reason customers choose your business.</p>
<p>Examples:</p>
<ul>
<li>CRM customization</li>
<li>Vendor Management</li>
<li>Procurement</li>
<li>Reporting</li>
<li>Customer Support</li>
</ul>
<p>These systems often require organization-specific workflows.</p>
<h3 id="recommended-strategy">Recommended Strategy</h3>
<ul>
<li>Buy a mature product</li>
<li>Customize where necessary</li>
<li>Outsource implementation if appropriate</li>
</ul>
<p>This provides flexibility without reinventing existing capabilities.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/04-build-vs-buy-supporting-subdomain-strategy.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="core-subdomain">Core Subdomain</h1>
<p>Core subdomains are where the organization creates unique value.</p>
<p>They represent the company&apos;s competitive advantage.</p>
<p>Examples:</p>
<ul>
<li>Recommendation engine (Netflix)</li>
<li>Search algorithm (Google)</li>
<li>Route optimization (Uber)</li>
<li>Pricing engine</li>
<li>Risk model</li>
<li>Trading platform</li>
</ul>
<p>These capabilities differentiate the business from competitors.</p>
<h3 id="recommended-strategy">Recommended Strategy</h3>
<p>&#x2705; Build internally</p>
<p>Maintain ownership of:</p>
<ul>
<li>source code</li>
<li>business rules</li>
<li>intellectual property</li>
<li>innovation</li>
</ul>
<p>This is where the organization&apos;s best engineering team should focus.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/05-build-vs-buy-core-subdomain-strategy.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="why-categorization-comes-before-build-vs-buy">Why Categorization Comes Before Build vs Buy</h1>
<p>Many organizations make Build vs Buy decisions too early.</p>
<p>Instead, they should first ask:</p>
<blockquote>
<p>Which business subdomain does this solution belong to?</p>
</blockquote>
<p>Once categorized, the strategy becomes much clearer.</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Strategy</th>
</tr>
</thead>
<tbody>
<tr>
<td>Generic</td>
<td>Buy</td>
</tr>
<tr>
<td>Supporting</td>
<td>Buy + Customize / Outsource</td>
</tr>
<tr>
<td>Core</td>
<td>Build In-house vs Build with an Outsourceing Partner</td>
</tr>
</tbody>
</table>
<p>Categorization removes ambiguity and aligns technology investment with business goals.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/07-build-vs-buy-decision-matrix.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="core-subdomain-build-or-outsource">Core Subdomain: Build or Outsource?</h1>
<p>Core subdomains represent the organization&apos;s competitive advantage. They contain the business logic, proprietary algorithms, and unique processes that differentiate the company from its competitors.</p>
<p>Unlike generic or supporting subdomains, <strong>core capabilities should rarely be purchased as off-the-shelf products</strong> because doing so may limit differentiation.</p>
<p>However, organizations don&apos;t always need to build everything with a fully in-house engineering team.</p>
<p>The real decision is often:</p>
<blockquote>
<p><strong>Build with your own team</strong> or <strong>Build with a trusted outsourcing partner.</strong></p>
</blockquote>
<hr>
<h2 id="option-1-build-in-house">Option 1: Build In-house</h2>
<p>Building internally is ideal when:</p>
<ul>
<li>The capability is strategically critical.</li>
<li>Business requirements evolve rapidly.</li>
<li>Close collaboration with domain experts is required.</li>
<li>The organization wants complete ownership of the product and intellectual property.</li>
<li>Long-term innovation is a priority.</li>
</ul>
<p><strong>Advantages</strong></p>
<ul>
<li>Complete control over architecture and roadmap</li>
<li>Faster business feedback loops</li>
<li>Strong knowledge retention</li>
<li>Better protection of intellectual property</li>
<li>Continuous innovation</li>
</ul>
<hr>
<h2 id="option-2-build-with-an-outsourcing-partner">Option 2: Build with an Outsourcing Partner</h2>
<p>Outsourcing does <strong>not</strong> mean giving away your competitive advantage.</p>
<p>Instead, it means extending your engineering capacity while <strong>retaining ownership of the solution</strong>.</p>
<p>The business continues to own:</p>
<ul>
<li>Product vision</li>
<li>Business rules</li>
<li>Architecture decisions</li>
<li>Intellectual property</li>
<li>Source code</li>
<li>Product roadmap</li>
</ul>
<p>The outsourcing partner contributes engineering expertise and delivery capacity.</p>
<h3 id="when-outsourcing-makes-sense">When Outsourcing Makes Sense</h3>
<ul>
<li>Need to scale engineering teams quickly</li>
<li>Limited availability of specialized skills</li>
<li>Faster time-to-market is important</li>
<li>Temporary increase in development workload</li>
<li>Cost optimization without compromising ownership</li>
</ul>
<h3 id="benefits-of-outsourcing-core-development">Benefits of Outsourcing Core Development</h3>
<ul>
<li>Access to experienced engineers</li>
<li>Faster product development</li>
<li>Reduced hiring time</li>
<li>Flexible team scaling</li>
<li>Lower operational overhead</li>
<li>Ability for internal teams to focus on product strategy and innovation</li>
</ul>
<p>The key is that the outsourced team works <strong>as an extension of the internal engineering organization</strong>, not as an independent owner of the product.</p>
<hr>
<h2 id="build-vs-outsource-decision">Build vs Outsource Decision</h2>
<table>
<thead>
<tr>
<th>Factor</th>
<th>Build In-house</th>
<th>Build with Outsourcing Partner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Product Ownership</td>
<td>&#x2705; Internal</td>
<td>&#x2705; Internal</td>
</tr>
<tr>
<td>Intellectual Property</td>
<td>&#x2705; Internal</td>
<td>&#x2705; Internal</td>
</tr>
<tr>
<td>Engineering Team</td>
<td>Internal employees</td>
<td>External delivery team</td>
</tr>
<tr>
<td>Speed to Scale</td>
<td>Medium</td>
<td>High</td>
</tr>
<tr>
<td>Hiring Effort</td>
<td>High</td>
<td>Low</td>
</tr>
<tr>
<td>Long-term Knowledge</td>
<td>Highest</td>
<td>Shared through documentation and collaboration</td>
</tr>
<tr>
<td>Best For</td>
<td>Long-term strategic capability</td>
<td>Rapid execution with retained ownership</td>
</tr>
</tbody>
</table>
<p><img src="https://blog.gyri.tech/content/images/2026/07/08-build-vs-buy-build-vs-outsource.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h2 id="important-principle">Important Principle</h2>
<p>Whether development is performed by internal employees or an outsourcing partner, <strong>the organization should retain ownership of the product, architecture, intellectual property, and business knowledge</strong>.</p>
<p>The strategic mistake is not outsourcing development&#x2014;it is outsourcing <strong>ownership and decision-making</strong>.</p>
<p>Think of outsourcing as adding more skilled engineers to your team, while keeping product strategy, business expertise, and innovation firmly under your control.</p>
<pre><code>             Core Business Capability
                      &#x2502;
      &#x250C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2510;
      &#x2502;                               &#x2502;
 Build In-house                 Build via Outsourcing
      &#x2502;                               &#x2502;
      &#x2514;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2518;
                      &#x2502;
          Business Owns Everything
    &#x2022; Product Vision
    &#x2022; Architecture
    &#x2022; Source Code
    &#x2022; Intellectual Property
    &#x2022; Product Roadmap
</code></pre>
<hr>
<h1 id="build-vs-buy-decision-matrix">Build vs Buy Decision Matrix</h1>
<table>
<thead>
<tr>
<th>Business Category</th>
<th>Strategic Importance</th>
<th>Recommended Approach</th>
</tr>
</thead>
<tbody>
<tr>
<td>Generic</td>
<td>Low</td>
<td>Buy</td>
</tr>
<tr>
<td>Supporting</td>
<td>Medium</td>
<td>Buy + Customize</td>
</tr>
<tr>
<td>Core</td>
<td>High</td>
<td>Build</td>
</tr>
</tbody>
</table>
<p>Decision flow:</p>
<pre><code>New Requirement
        &#x2502;
        &#x25BC;
Identify Business Subdomain
        &#x2502;
        &#x251C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500; Generic &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x25BA; Buy
        &#x2502;
        &#x251C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500; Supporting &#x2500;&#x2500;&#x2500;&#x2500;&#x25BA; Buy + Customize
        &#x2502;
        &#x2514;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500; Core &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x25BA; Build
</code></pre>
<p><img src="https://blog.gyri.tech/content/images/2026/07/06-build-vs-buy-decision-tree.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="maximizing-roi-through-core-investment">Maximizing ROI Through Core Investment</h1>
<p>Technology budgets are always limited.</p>
<p>Organizations achieve the highest return on investment by allocating their best resources toward <strong>core business capabilities</strong>.</p>
<p>Instead of spending months building payroll or email systems, engineering teams should focus on solving problems that directly improve:</p>
<ul>
<li>customer experience</li>
<li>revenue generation</li>
<li>operational excellence</li>
<li>competitive differentiation</li>
</ul>
<p>This creates long-term strategic value.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/08-build-vs-buy-roi-investment-funnel.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
<hr>
<h1 id="common-mistakes">Common Mistakes</h1>
<h3 id="building-generic-solutions">Building Generic Solutions</h3>
<ul>
<li>Reinvents existing software</li>
<li>High maintenance cost</li>
<li>Low business value</li>
</ul>
<hr>
<h3 id="buying-core-capabilities">Buying Core Capabilities</h3>
<ul>
<li>Loss of differentiation</li>
<li>Vendor dependency</li>
<li>Limited innovation</li>
</ul>
<hr>
<h3 id="treating-every-requirement-as-special">Treating Every Requirement as &quot;Special&quot;</h3>
<p>Not every business process is unique.</p>
<p>Many organizations overestimate the uniqueness of their operations, resulting in unnecessary custom development.</p>
<hr>
<h1 id="key-takeaways">Key Takeaways</h1>
<ul>
<li>Build vs Buy is a business strategy, not just a technical decision.</li>
<li>Categorize business domains before selecting a technology approach.</li>
<li>Buy solutions for generic capabilities.</li>
<li>Buy and customize supporting capabilities.</li>
<li>Build core capabilities using the organization&apos;s strongest engineering team.</li>
<li>Invest engineering effort where it creates competitive advantage.</li>
<li>Businesses maximize ROI by focusing technology investment on their core subdomains.</li>
</ul>
<hr>
<h1 id="final-thought">Final Thought</h1>
<p>A successful technology strategy is not about building everything.</p>
<p>It is about building <strong>only what makes your business unique</strong> and leveraging proven solutions for everything else.</p>
<p>Organizations that consistently apply this principle deliver software faster, reduce costs, and invest where innovation creates the greatest business value.</p>
<p><img src="https://blog.gyri.tech/content/images/2026/07/10-build-vs-buy-final-summary.jpeg" alt="Build, Buy, or Outsource? A Domain-Driven Design (DDD) Approach to Strategic Technology Decisions" loading="lazy"></p>
]]></content:encoded></item><item><title><![CDATA[Best Practices for Sending Marketing (Bulk) Emails]]></title><description><![CDATA[<p>Sending marketing emails at scale can be one of the most effective ways to reach users, drive engagement, and grow revenue. However, poor practices can quickly lead to spam complaints, high bounce rates, or even domain blacklisting.</p>
<p>Below are essential best practices to ensure your email campaigns are effective, compliant,</p>]]></description><link>https://blog.gyri.tech/sending-marketing-emails/</link><guid isPermaLink="false">6a4a04d7a59c17040f4ea1a3</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Sun, 05 Jul 2026 07:33:23 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/07/marketing-email.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/07/marketing-email.png" alt="Best Practices for Sending Marketing (Bulk) Emails"><p>Sending marketing emails at scale can be one of the most effective ways to reach users, drive engagement, and grow revenue. However, poor practices can quickly lead to spam complaints, high bounce rates, or even domain blacklisting.</p>
<p>Below are essential best practices to ensure your email campaigns are effective, compliant, and well-received.</p>
<hr>
<h2 id="1-always-include-an-unsubscribe-link">1. Always Include an Unsubscribe Link</h2>
<p>Every marketing email must include a clear and easy way for users to opt out.</p>
<h3 id="why-it-matters">Why it matters:</h3>
<ul>
<li>Required by regulations like GDPR, CAN-SPAM, and similar laws</li>
<li>Builds trust with recipients</li>
<li>Reduces spam complaints</li>
</ul>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Place the unsubscribe link in the footer</li>
<li>Make the process one-click or simple (no login required)</li>
<li>Process unsubscribe requests immediately</li>
</ul>
<hr>
<h2 id="2-avoid-sending-too-many-emails-in-a-short-time">2. Avoid Sending Too Many Emails in a Short Time</h2>
<p>Email fatigue is real. Overloading users leads to higher unsubscribe rates and lower engagement.</p>
<h3 id="why-it-matters">Why it matters:</h3>
<ul>
<li>Protects sender reputation</li>
<li>Improves open and click-through rates</li>
<li>Prevents users from marking emails as spam</li>
</ul>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Set a consistent sending schedule (e.g., weekly or bi-weekly)</li>
<li>Use frequency caps per user</li>
<li>Segment audiences to control email volume per group</li>
</ul>
<hr>
<h2 id="3-validate-email-addresses-before-sending">3. Validate Email Addresses Before Sending</h2>
<p>Invalid or fake email addresses can damage deliverability and sender reputation.</p>
<h3 id="why-it-matters">Why it matters:</h3>
<ul>
<li>Reduces bounce rates</li>
<li>Improves inbox placement</li>
<li>Saves cost on email service providers</li>
</ul>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Use real-time email validation during sign-up</li>
<li>Periodically clean your email list</li>
<li>Remove hard bounces automatically</li>
</ul>
<hr>
<h2 id="4-segment-your-audience">4. Segment Your Audience</h2>
<p>Not all users should receive the same message.</p>
<h3 id="why-it-matters">Why it matters:</h3>
<ul>
<li>Increases relevance and engagement</li>
<li>Reduces unsubscribe rates</li>
<li>Improves conversion rates</li>
</ul>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Segment by behavior (clicks, purchases, activity)</li>
<li>Segment by demographics (location, language, age)</li>
<li>Segment by lifecycle stage (new users, active users, churned users)</li>
</ul>
<hr>
<h2 id="5-use-double-opt-in-for-subscriptions">5. Use Double Opt-In for Subscriptions</h2>
<p>Double opt-in requires users to confirm their email address before being added to your list.</p>
<h3 id="why-it-matters">Why it matters:</h3>
<ul>
<li>Ensures valid email addresses</li>
<li>Confirms user intent</li>
<li>Improves list quality</li>
</ul>
<hr>
<h2 id="6-monitor-deliverability-metrics">6. Monitor Deliverability Metrics</h2>
<p>Tracking performance helps you identify issues early.</p>
<h3 id="key-metrics-to-watch">Key metrics to watch:</h3>
<ul>
<li>Bounce rate</li>
<li>Open rate</li>
<li>Click-through rate (CTR)</li>
<li>Spam complaint rate</li>
<li>Unsubscribe rate</li>
</ul>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Set up domain authentication (SPF, DKIM, DMARC)</li>
<li>Use a reputable email service provider</li>
<li>Warm up new domains gradually</li>
</ul>
<hr>
<h2 id="7-write-clear-and-honest-subject-lines">7. Write Clear and Honest Subject Lines</h2>
<p>Misleading subject lines may increase opens short-term but harm trust long-term.</p>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Keep subject lines concise</li>
<li>Avoid spam trigger words (e.g., &quot;FREE!!!&quot;, &quot;URGENT!!!&quot;)</li>
<li>Ensure content matches the subject line</li>
</ul>
<hr>
<h2 id="8-optimize-email-content-and-design">8. Optimize Email Content and Design</h2>
<p>A well-structured email improves engagement.</p>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Use responsive design for mobile users</li>
<li>Keep paragraphs short and scannable</li>
<li>Include a single primary call-to-action (CTA)</li>
<li>Avoid overly image-heavy emails (can trigger spam filters)</li>
</ul>
<hr>
<h2 id="9-respect-time-zones-and-sending-time">9. Respect Time Zones and Sending Time</h2>
<p>Sending emails at the right time improves engagement rates.</p>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Analyze user behavior by region</li>
<li>Schedule emails during active hours</li>
<li>Test different send times (A/B testing)</li>
</ul>
<hr>
<h2 id="10-maintain-a-clean-sender-reputation">10. Maintain a Clean Sender Reputation</h2>
<p>Your domain and IP reputation determine whether emails reach inboxes.</p>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Avoid sudden spikes in email volume</li>
<li>Regularly remove inactive users</li>
<li>Monitor blacklists</li>
<li>Use dedicated IPs if sending at high volume</li>
</ul>
<hr>
<h2 id="11-provide-value-in-every-email">11. Provide Value in Every Email</h2>
<p>Users should feel that opening your email is worth their time.</p>
<h3 id="best-practices">Best practices:</h3>
<ul>
<li>Share useful content, not just promotions</li>
<li>Personalize messages where possible</li>
<li>Avoid repetitive or irrelevant messaging</li>
</ul>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Effective bulk email marketing is a balance between compliance, technical setup, and user experience. By following these best practices, you can improve deliverability, maintain trust, and maximize engagement over time.</p>
<hr>
<h3 id="useful-links-and-references">Useful links and references</h3>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>IP and Domain reputation checker</td>
<td><a href="https://check.spamhaus.org/?ref=blog.gyri.tech">https://check.spamhaus.org/</a></td>
</tr>
<tr>
<td>Test MX records</td>
<td><a href="https://mxtoolbox.com/?ref=blog.gyri.tech">https://mxtoolbox.com/</a></td>
</tr>
<tr>
<td>Postmaster Tools</td>
<td><a href="https://postmaster.google.com/?ref=blog.gyri.tech">https://postmaster.google.com/</a></td>
</tr>
</tbody>
</table>
<h4 id="remember-a-high-quality-email-list-is-more-valuable-than-a-large-but-disengaged-one"><em>Remember: a high-quality email list is more valuable than a large but disengaged one.</em></h4>
]]></content:encoded></item><item><title><![CDATA[Complete Guide: Configure an Email Server on Ubuntu 24.04 with Postfix & Dovecot]]></title><description><![CDATA[<hr>
<h1 id="1-domain-registration">1. Domain Registration</h1>
<p>Before setting up your mail server, you need a domain.</p>
<h2 id="steps">Steps</h2>
<ol>
<li>
<p>Register a domain from a registrar:</p>
<ul>
<li>Namecheap</li>
<li>GoDaddy</li>
<li>Cloudflare Registrar</li>
<li>Google Domains (if available in your region)</li>
</ul>
</li>
<li>
<p>Choose a domain like: <code>example.com</code></p>
</li>
<li>
<p>Ensure you have access to DNS management.</p>
</li>
</ol>
<hr>
<h1 id="2-server-requirements">2. Server Requirements</h1>
<p>You need an</p>]]></description><link>https://blog.gyri.tech/complete-guide-configure-an-email-server-on-ubuntu-24-04-with-postfix-dovecot/</link><guid isPermaLink="false">6a4a0062a59c17040f4ea17d</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Sun, 05 Jul 2026 07:15:45 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1683117927786-f146451082fb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGVtYWlsfGVufDB8fHx8MTc4MzIzNTY5NXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<hr>
<h1 id="1-domain-registration">1. Domain Registration</h1>
<img src="https://images.unsplash.com/photo-1683117927786-f146451082fb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGVtYWlsfGVufDB8fHx8MTc4MzIzNTY5NXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Complete Guide: Configure an Email Server on Ubuntu 24.04 with Postfix &amp; Dovecot"><p>Before setting up your mail server, you need a domain.</p>
<h2 id="steps">Steps</h2>
<ol>
<li>
<p>Register a domain from a registrar:</p>
<ul>
<li>Namecheap</li>
<li>GoDaddy</li>
<li>Cloudflare Registrar</li>
<li>Google Domains (if available in your region)</li>
</ul>
</li>
<li>
<p>Choose a domain like: <code>example.com</code></p>
</li>
<li>
<p>Ensure you have access to DNS management.</p>
</li>
</ol>
<hr>
<h1 id="2-server-requirements">2. Server Requirements</h1>
<p>You need an Ubuntu 24.04 server with:</p>
<ul>
<li>Static public IP (important)</li>
<li>Open ports:
<ul>
<li>25 (SMTP)</li>
<li>587 (Submission)</li>
<li>465 (SMTPS optional)</li>
<li>143 (IMAP)</li>
<li>993 (IMAPS)</li>
<li>110 (POP3 optional)</li>
<li>995 (POP3S optional)</li>
</ul>
</li>
</ul>
<hr>
<h1 id="3-update-system">3. Update System</h1>
<pre><code class="language-bash">sudo apt update &amp;&amp; sudo apt upgrade -y
</code></pre>
<hr>
<h1 id="4-install-postfix-smtp-server">4. Install Postfix (SMTP Server)</h1>
<h2 id="installation">Installation</h2>
<pre><code class="language-bash">sudo apt install postfix -y
</code></pre>
<p>During installation:</p>
<ul>
<li>Select: <strong>Internet Site</strong></li>
<li>Set system mail name: <code>example.com</code></li>
</ul>
<hr>
<h2 id="configure-postfix">Configure Postfix</h2>
<p>Edit:</p>
<pre><code class="language-bash">sudo nano /etc/postfix/main.cf
</code></pre>
<h3 id="basic-configuration">Basic configuration:</h3>
<pre><code class="language-ini">myhostname = mail.example.com
mydomain = example.com
myorigin = /etc/mailname
inet_interfaces = all
inet_protocols = all

mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
home_mailbox = Maildir/

smtpd_banner = $myhostname ESMTP
</code></pre>
<hr>
<h2 id="enable-smtp-authentication">Enable SMTP authentication</h2>
<p>Later we integrate with Dovecot:</p>
<pre><code class="language-ini">smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_recipient_restrictions =
    permit_sasl_authenticated,
    permit_mynetworks,
    reject_unauth_destination
</code></pre>
<hr>
<h1 id="5-install-dovecot-imappop3-server">5. Install Dovecot (IMAP/POP3 Server)</h1>
<pre><code class="language-bash">sudo apt install dovecot-core dovecot-imapd dovecot-pop3d -y
</code></pre>
<hr>
<h2 id="configure-mailbox-format">Configure Mailbox Format</h2>
<p>Edit:</p>
<pre><code class="language-bash">sudo nano /etc/dovecot/conf.d/10-mail.conf
</code></pre>
<p>Set:</p>
<pre><code class="language-ini">mail_location = maildir:~/Maildir
</code></pre>
<hr>
<h2 id="enable-imap-pop3">Enable IMAP &amp; POP3</h2>
<p>Edit:</p>
<pre><code class="language-bash">sudo nano /etc/dovecot/dovecot.conf
</code></pre>
<p>Ensure:</p>
<pre><code class="language-ini">protocols = imap pop3
</code></pre>
<hr>
<h2 id="configure-authentication-socket-for-postfix">Configure Authentication Socket for Postfix</h2>
<p>Edit:</p>
<pre><code class="language-bash">sudo nano /etc/dovecot/conf.d/10-master.conf
</code></pre>
<p>Add:</p>
<pre><code class="language-ini">service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}
</code></pre>
<hr>
<h1 id="6-install-opendkim-email-signing">6. Install OpenDKIM (Email Signing)</h1>
<pre><code class="language-bash">sudo apt install opendkim opendkim-tools -y
</code></pre>
<hr>
<h2 id="generate-dkim-key">Generate DKIM key</h2>
<pre><code class="language-bash">sudo opendkim-genkey -s mail -d example.com
</code></pre>
<p>Move keys:</p>
<pre><code class="language-bash">sudo mv mail.private /etc/opendkim/keys/example.com/
</code></pre>
<hr>
<h2 id="configure-opendkim">Configure OpenDKIM</h2>
<p>Edit:</p>
<pre><code class="language-bash">sudo nano /etc/opendkim.conf
</code></pre>
<p>Add:</p>
<pre><code class="language-ini">Domain                  example.com
KeyFile                 /etc/opendkim/keys/example.com/mail.private
Selector                mail
Socket                  local:/var/spool/postfix/run/opendkim/opendkim.sock
</code></pre>
<hr>
<h2 id="connect-to-postfix">Connect to Postfix</h2>
<pre><code class="language-ini">smtpd_milters = local:/var/spool/postfix/run/opendkim/opendkim.sock
non_smtpd_milters = local:/var/spool/postfix/run/opendkim/opendkim.sock
</code></pre>
<hr>
<h1 id="7-dns-configuration-very-important">7. DNS Configuration (VERY IMPORTANT)</h1>
<p>All DNS records must be configured correctly for deliverability.</p>
<hr>
<h2 id="71-a-record">7.1 A Record</h2>
<pre><code>mail.example.com &#x2192; x.x.x.x
</code></pre>
<hr>
<h2 id="72-mx-record">7.2 MX Record</h2>
<pre><code>example.com &#x2192; mail.example.com (priority 10)
</code></pre>
<hr>
<h2 id="73-spf-txt-record">7.3 SPF (TXT record)</h2>
<pre><code>example.com TXT

v=spf1 mx ip4:x.x.x.x -all
</code></pre>
<hr>
<h2 id="74-dkim-record">7.4 DKIM Record</h2>
<pre><code>mail._domainkey.example.com TXT

v=DKIM1; k=rsa; p=PUBLIC_KEY_HERE
</code></pre>
<hr>
<h2 id="75-dmarc-record">7.5 DMARC Record</h2>
<pre><code>_dmarc.example.com TXT

v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s; pct=100
</code></pre>
<p>Recommended later:</p>
<pre><code>p=reject
</code></pre>
<hr>
<h2 id="76-mta-sts-security">7.6 MTA-STS (Security)</h2>
<h3 id="dns-record">DNS record:</h3>
<pre><code>_mta-sts.example.com TXT

v=STSv1; id=2026070501
</code></pre>
<hr>
<h3 id="policy-file">Policy file:</h3>
<p>Host:</p>
<pre><code>https://mta-sts.example.com/.well-known/mta-sts.txt
</code></pre>
<p>Content:</p>
<pre><code>version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800
</code></pre>
<hr>
<h2 id="77-tls-reporting-tls-rpt">7.7 TLS Reporting (TLS-RPT)</h2>
<pre><code>_smtp._tls.example.com TXT

v=TLSRPTv1; rua=mailto:tlsrpt@example.com
</code></pre>
<hr>
<h1 id="8-ssltls-setup-let%E2%80%99s-encrypt">8. SSL/TLS Setup (Let&#x2019;s Encrypt)</h1>
<p>Install Certbot:</p>
<pre><code class="language-bash">sudo apt install certbot -y
</code></pre>
<p>Generate certificate:</p>
<pre><code class="language-bash">sudo certbot certonly --standalone -d mail.example.com
</code></pre>
<hr>
<p>Configure Postfix TLS:</p>
<pre><code class="language-ini">smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_security_level=may
</code></pre>
<hr>
<p>Configure Dovecot TLS:</p>
<pre><code class="language-ini">ssl = required
ssl_cert = &lt;/etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = &lt;/etc/letsencrypt/live/mail.example.com/privkey.pem
</code></pre>
<hr>
<h1 id="9-open-required-ports">9. Open Required Ports</h1>
<pre><code class="language-bash">sudo ufw allow 25
sudo ufw allow 587
sudo ufw allow 465
sudo ufw allow 143
sudo ufw allow 993
sudo ufw enable
</code></pre>
<hr>
<h1 id="10-restart-services">10. Restart Services</h1>
<pre><code class="language-bash">sudo systemctl restart postfix
sudo systemctl restart dovecot
sudo systemctl restart opendkim
</code></pre>
<hr>
<h1 id="11-testing-email-setup">11. Testing Email Setup</h1>
<h2 id="check-dns">Check DNS</h2>
<pre><code class="language-bash">dig MX example.com
dig TXT example.com
</code></pre>
<h2 id="check-dkim">Check DKIM</h2>
<pre><code class="language-bash">opendkim-testkey -d example.com -s mail -vvv
</code></pre>
<h2 id="send-test-email">Send test email</h2>
<p>Use:</p>
<ul>
<li>Gmail</li>
<li>Mail-Tester.com</li>
</ul>
<p>Check:</p>
<ul>
<li>SPF PASS</li>
<li>DKIM PASS</li>
<li>DMARC PASS</li>
</ul>
<hr>
<h1 id="12-deliverability-best-practices">12. Deliverability Best Practices</h1>
<p>To avoid spam filtering:</p>
<h3 id="always">Always:</h3>
<ul>
<li>Use verified email lists</li>
<li>Enable unsubscribe links</li>
<li>Warm up IP gradually</li>
<li>Monitor bounce rates</li>
</ul>
<h3 id="avoid">Avoid:</h3>
<ul>
<li>Bulk sending immediately</li>
<li>Purchased email lists</li>
<li>High bounce rates</li>
</ul>
<hr>
<h1 id="13-production-architecture-recommendation">13. Production Architecture Recommendation</h1>
<p>For better scalability:</p>
<pre><code>Apps &#x2192; Postfix (queue) &#x2192; DKIM &#x2192; SMTP &#x2192; Internet
        &#x2193;
   Dovecot (mailboxes)
</code></pre>
<p>For marketing:</p>
<ul>
<li>Use separate domain or IP</li>
<li>Use rate limiting</li>
<li>Track engagement</li>
</ul>
<hr>
<h1 id="14-summary">14. Summary</h1>
<p>A production email server requires:</p>
<ul>
<li>Proper DNS (SPF, DKIM, DMARC)</li>
<li>Correct Postfix + Dovecot configuration</li>
<li>TLS encryption</li>
<li>IP reputation management</li>
<li>Controlled sending behavior</li>
</ul>
<hr>
<h1 id="final-note">Final Note</h1>
<p>Self-hosted email servers give full control but require careful management of:</p>
<ul>
<li>reputation</li>
<li>deliverability</li>
<li>rate limits</li>
</ul>
<p>For bulk marketing, consider separating transactional mail and marketing infrastructure to maintain inbox placement quality.</p>
]]></content:encoded></item><item><title><![CDATA[GitLab CI/CD Pipeline Documentation]]></title><description><![CDATA[<h2 id="overview">Overview</h2>
<p>This repository uses a multi-environment GitLab CI/CD pipeline for a <strong>Spring Boot Reactive (WebFlux)</strong> application.</p>
<p>The pipeline provides:</p>
<ul>
<li>Automated build and deployment</li>
<li>Parallel deployment to multiple test environments</li>
<li>Mattermost notifications with changelog support</li>
<li>Automatic handling of unavailable GitLab runners</li>
<li>Deployment status tracking</li>
<li>Production deployment authorization controls</li>
<li>Deployment failure</li></ul>]]></description><link>https://blog.gyri.tech/gitlab-ci-cd-pipeline-documentation/</link><guid isPermaLink="false">6a33bad5a59c17040f4ea172</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Thu, 18 Jun 2026 10:06:31 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/06/gitlab-architecture.png" medium="image"/><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<img src="https://blog.gyri.tech/content/images/2026/06/gitlab-architecture.png" alt="GitLab CI/CD Pipeline Documentation"><p>This repository uses a multi-environment GitLab CI/CD pipeline for a <strong>Spring Boot Reactive (WebFlux)</strong> application.</p>
<p>The pipeline provides:</p>
<ul>
<li>Automated build and deployment</li>
<li>Parallel deployment to multiple test environments</li>
<li>Mattermost notifications with changelog support</li>
<li>Automatic handling of unavailable GitLab runners</li>
<li>Deployment status tracking</li>
<li>Production deployment authorization controls</li>
<li>Deployment failure notifications with job logs</li>
</ul>
<hr>
<h1 id="architecture">Architecture</h1>
<h2 id="test-environments">Test Environments</h2>
<table>
<thead>
<tr>
<th>Environment</th>
<th>Location</th>
<th>Runner Tag</th>
</tr>
</thead>
<tbody>
<tr>
<td>TH</td>
<td>Test Server Location 1</td>
<td><code>th-runner</code></td>
</tr>
<tr>
<td>KP</td>
<td>Test Server Location 2</td>
<td><code>kp-runner</code></td>
</tr>
</tbody>
</table>
<p>Both environments build and deploy independently.</p>
<p>Pipeline success requires <strong>at least one test deployment</strong> to complete successfully.</p>
<hr>
<h1 id="pipeline-stages">Pipeline Stages</h1>
<pre><code class="language-text">notify_start
    &#x2502;
    &#x25BC;
build_test
    &#x251C;&#x2500;&#x2500; build_test_th
    &#x251C;&#x2500;&#x2500; build_test_kp
    &#x2514;&#x2500;&#x2500; pending_job_monitor_build_test
    &#x2502;
    &#x25BC;
deploy_test
    &#x251C;&#x2500;&#x2500; deploy_test_th
    &#x251C;&#x2500;&#x2500; deploy_test_kp
    &#x2514;&#x2500;&#x2500; pending_job_monitor_deploy_test
    &#x2502;
    &#x25BC;
pipeline_complete
    &#x2502;
    &#x25BC;
deploy_prod (manual)
</code></pre>
<hr>
<h1 id="stage-details">Stage Details</h1>
<hr>
<h2 id="1-notifystart">1. notify_start</h2>
<h3 id="purpose">Purpose</h3>
<p>Sends a pipeline start notification to Mattermost.</p>
<h3 id="features">Features</h3>
<ul>
<li>Includes pipeline link</li>
<li>Includes changelog</li>
<li>Displays recent commits</li>
</ul>
<p>Example:</p>
<pre><code class="language-text">&#x1F680; Build STARTED

Changes in this build:

- abc123 Fixed login issue
- def456 Added Kafka consumer
- ghi789 Updated API validation
</code></pre>
<hr>
<h2 id="2-buildtest">2. build_test</h2>
<h3 id="jobs">Jobs</h3>
<h4 id="buildtestth">build_test_th</h4>
<p>Builds application for TH environment.</p>
<h4 id="buildtestkp">build_test_kp</h4>
<p>Builds application for KP environment.</p>
<h3 id="build-process">Build Process</h3>
<ol>
<li>Copy example property files</li>
<li>Generate environment-specific property file</li>
<li>Run Gradle build</li>
</ol>
<pre><code class="language-bash">./gradlew clean build \
-Pspring.config.additional-location=file:./application-test.properties
</code></pre>
<h3 id="generated-artifacts">Generated Artifacts</h3>
<pre><code class="language-text">build/libs/spring-boot-template-cqrs-1.0.0.jar
application-test.properties
</code></pre>
<p>Artifacts expire after:</p>
<pre><code class="language-text">1 day
</code></pre>
<hr>
<h2 id="3-runner-availability-monitoring">3. Runner Availability Monitoring</h2>
<h3 id="pendingjobmonitorbuildtest">pending_job_monitor_build_test</h3>
<p>Problem:</p>
<p>If a dedicated runner is unavailable, jobs can remain stuck in <code>Pending</code>.</p>
<p>Solution:</p>
<p>After 30 seconds:</p>
<ol>
<li>Wait configured timeout</li>
<li>Query GitLab API</li>
<li>Detect pending jobs</li>
<li>Cancel stuck jobs automatically</li>
</ol>
<p>Current timeout:</p>
<pre><code class="language-yaml">PENDING_JOB_TIMEOUT=15
</code></pre>
<hr>
<h2 id="4-deploytest">4. deploy_test</h2>
<h3 id="jobs">Jobs</h3>
<h4 id="deploytestth">deploy_test_th</h4>
<p>Deploys build artifact to TH environment.</p>
<h4 id="deploytestkp">deploy_test_kp</h4>
<p>Deploys build artifact to KP environment.</p>
<h3 id="deployment-flow">Deployment Flow</h3>
<h4 id="step-1">Step 1</h4>
<p>Backup existing JAR</p>
<pre><code class="language-bash">old.jar -&gt; old.jar_bkp
</code></pre>
<h4 id="step-2">Step 2</h4>
<p>Upload new JAR</p>
<pre><code class="language-bash">scp build/libs/app.jar
</code></pre>
<h4 id="step-3">Step 3</h4>
<p>Upload generated property file</p>
<pre><code class="language-bash">scp application.properties
</code></pre>
<h4 id="step-4">Step 4</h4>
<p>Execute remote deployment command</p>
<p>Example:</p>
<pre><code class="language-bash">docker compose restart
</code></pre>
<p>or</p>
<pre><code class="language-bash">systemctl restart my-service
</code></pre>
<hr>
<h2 id="deployment-validation">Deployment Validation</h2>
<p>Deployment only proceeds if:</p>
<pre><code class="language-text">Build succeeded
AND
Artifact exists
</code></pre>
<p>Otherwise deployment is skipped.</p>
<hr>
<h2 id="5-runner-availability-monitoring-deploy">5. Runner Availability Monitoring (Deploy)</h2>
<h3 id="pendingjobmonitordeploytest">pending_job_monitor_deploy_test</h3>
<p>Automatically cancels deploy jobs that remain pending due to unavailable runners.</p>
<hr>
<h2 id="6-pipelinecomplete">6. pipeline_complete</h2>
<h3 id="purpose">Purpose</h3>
<p>Determines final pipeline status.</p>
<h3 id="success-criteria">Success Criteria</h3>
<p>Pipeline succeeds if:</p>
<pre><code class="language-text">TH deployment succeeded
OR
KP deployment succeeded
</code></pre>
<h3 id="failure-criteria">Failure Criteria</h3>
<p>Pipeline fails if:</p>
<pre><code class="language-text">TH deployment failed
AND
KP deployment failed
</code></pre>
<p>Examples:</p>
<table>
<thead>
<tr>
<th>TH</th>
<th>KP</th>
<th>Pipeline</th>
</tr>
</thead>
<tbody>
<tr>
<td>Success</td>
<td>Success</td>
<td>Success</td>
</tr>
<tr>
<td>Success</td>
<td>Failed</td>
<td>Success</td>
</tr>
<tr>
<td>Failed</td>
<td>Success</td>
<td>Success</td>
</tr>
<tr>
<td>Failed</td>
<td>Failed</td>
<td>Failed</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="7-deployprod">7. deploy_prod</h2>
<h3 id="type">Type</h3>
<p>Manual deployment.</p>
<h3 id="authorization">Authorization</h3>
<p>Only approved GitLab users can deploy to production.</p>
<p>Validation:</p>
<pre><code class="language-bash">GITLAB_USERS_IDS_FOR_PROD_DEPLOY
</code></pre>
<p>contains:</p>
<pre><code class="language-bash">GITLAB_USER_ID
</code></pre>
<p>Unauthorized users receive:</p>
<pre><code class="language-text">&#x274C; User is not authorized for production deployment
</code></pre>
<h3 id="production-deployment-steps">Production Deployment Steps</h3>
<ol>
<li>Backup current JAR</li>
<li>Upload new JAR</li>
<li>Upload property file</li>
<li>Execute production deployment command</li>
<li>Send Mattermost notification</li>
</ol>
<hr>
<h1 id="mattermost-notifications">Mattermost Notifications</h1>
<hr>
<h2 id="start-notification">Start Notification</h2>
<p>Color:</p>
<pre><code class="language-text">Blue
</code></pre>
<p>Example:</p>
<pre><code class="language-text">&#x1F680; Build STARTED
</code></pre>
<hr>
<h2 id="deployment-success">Deployment Success</h2>
<p>Color:</p>
<pre><code class="language-text">Purple
</code></pre>
<p>Example:</p>
<pre><code class="language-text">&#x2705; TH Deploy SUCCESS
</code></pre>
<hr>
<h2 id="pipeline-success">Pipeline Success</h2>
<p>Color:</p>
<pre><code class="language-text">Green
</code></pre>
<p>Example:</p>
<pre><code class="language-text">&#x1F389; Pipeline SUCCESS
</code></pre>
<hr>
<h2 id="failure-notification">Failure Notification</h2>
<p>Color:</p>
<pre><code class="language-text">Red
</code></pre>
<p>Includes:</p>
<ul>
<li>Error details</li>
<li>Last 50 log lines</li>
<li>Pipeline link</li>
<li>Job URL</li>
</ul>
<p>Example:</p>
<pre><code class="language-text">&#x274C; Deploy FAILED

Job Log:
...
</code></pre>
<hr>
<h1 id="changelog-generation">Changelog Generation</h1>
<p>Each pipeline automatically generates a changelog.</p>
<p>Information included:</p>
<ul>
<li>Commit hash</li>
<li>Author</li>
<li>Commit message</li>
</ul>
<p>Example:</p>
<pre><code class="language-text">- abc123 Added Kafka consumer [John]
- def456 Fixed Redis configuration [Mike]
</code></pre>
<p>Limits:</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>MAX_COMMIT_MSG_LENGTH</td>
<td>100</td>
</tr>
<tr>
<td>MAX_COMMITS_TO_SHOW</td>
<td>10</td>
</tr>
</tbody>
</table>
<hr>
<h1 id="required-gitlab-variables">Required GitLab Variables</h1>
<h2 id="general">General</h2>
<pre><code class="language-text">APP_NAME

GYRI_TEST_PROPERTY_FILE

GYRI_TEST_MATTERMOST_WEBHOOK
ALL_FAIL_MATTERMOST_WEBHOOK

GITLAB_API_TOKEN
</code></pre>
<hr>
<h2 id="th-environment">TH Environment</h2>
<pre><code class="language-text">GYRI_TEST_TH_SERVER_USER
GYRI_TEST_TH_SERVER_01
GYRI_TEST_TH_SERVER_01_DEPLOYMENT_PATH

TEST_TH_DEPLOY_COMMAND

TEST_TH_APP_LOG_FILE_PROPERTY
TEST_TH_APP_BASE_SERVER_URL
TEST_TH_SWAGGER_SERVER_URL

TEST_TH_REDIS_HOST
TEST_TH_REDIS_PORT
TEST_TH_REDIS_TOPIC_PREFIX

TEST_TH_MONGO_HOST
TEST_TH_MONGO_PORT
TEST_TH_MONGO_DATABASE
TEST_TH_MONGO_USERNAME
TEST_TH_MONGO_PASSWORD
TEST_TH_MONGO_AUTH_DATABASE

TEST_TH_KAFKA_SECURITY_PROTOCOL
TEST_TH_KAFKA_SASL_MECHANISM
TEST_TH_KAFKA_SASL_JAAS_CONFIG
TEST_TH_KAFKA_SERVERS
TEST_TH_KAFKA_CONSUMER_GROUP_ID
TEST_TH_KAFKA_TOPIC_PREFIX
</code></pre>
<hr>
<h2 id="kp-environment">KP Environment</h2>
<pre><code class="language-text">GYRI_TEST_KP_SERVER_USER
GYRI_TEST_KP_SERVER_01
GYRI_TEST_KP_SERVER_01_DEPLOYMENT_PATH

TEST_KP_DEPLOY_COMMAND

TEST_KP_APP_LOG_FILE_PROPERTY
TEST_KP_APP_BASE_SERVER_URL
TEST_KP_SWAGGER_SERVER_URL

TEST_KP_REDIS_HOST
TEST_KP_REDIS_PORT
TEST_KP_REDIS_TOPIC_PREFIX

TEST_KP_MONGO_HOST
TEST_KP_MONGO_PORT
TEST_KP_MONGO_DATABASE
TEST_KP_MONGO_USERNAME
TEST_KP_MONGO_PASSWORD
TEST_KP_MONGO_AUTH_DATABASE

TEST_KP_KAFKA_SECURITY_PROTOCOL
TEST_KP_KAFKA_SASL_MECHANISM
TEST_KP_KAFKA_SASL_JAAS_CONFIG
TEST_KP_KAFKA_SERVERS
TEST_KP_KAFKA_CONSUMER_GROUP_ID
TEST_KP_KAFKA_TOPIC_PREFIX
</code></pre>
<hr>
<h2 id="production">Production</h2>
<pre><code class="language-text">GYRI_PROD_SERVER_USER
GYRI_PROD_SERVER_HOST
GYRI_PROD_SERVER_SSH_PORT
GYRI_PROD_SERVER_DEPLOYMENT_PATH

PROD_DEPLOY_COMMAND

GYRI_PROD_MATTERMOST_WEBHOOK

GITLAB_USERS_IDS_FOR_PROD_DEPLOY
</code></pre>
<hr>
<h1 id="gitlab-runner-registration">GitLab Runner Registration</h1>
<p>The pipeline requires three runners:</p>
<table>
<thead>
<tr>
<th>Runner</th>
<th>Tag</th>
</tr>
</thead>
<tbody>
<tr>
<td>TH Runner</td>
<td><code>th-runner</code></td>
</tr>
<tr>
<td>KP Runner</td>
<td><code>kp-runner</code></td>
</tr>
<tr>
<td>Production Runner</td>
<td><code>prod-runner</code></td>
</tr>
</tbody>
</table>
<hr>
<h2 id="install-gitlab-runner">Install GitLab Runner</h2>
<h3 id="ubuntudebian">Ubuntu/Debian</h3>
<pre><code class="language-bash">curl -L \
https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh \
| sudo bash

sudo apt install gitlab-runner
</code></pre>
<p>Verify:</p>
<pre><code class="language-bash">gitlab-runner --version
</code></pre>
<hr>
<h2 id="register-th-runner">Register TH Runner</h2>
<pre><code class="language-bash">sudo gitlab-runner register
</code></pre>
<p>Prompts:</p>
<pre><code class="language-text">GitLab URL:
https://gitlab.example.com

Registration Token:
&lt;project-runner-token&gt;

Description:
th-runner

Tags:
th-runner

Executor:
shell
</code></pre>
<hr>
<h2 id="register-kp-runner">Register KP Runner</h2>
<pre><code class="language-bash">sudo gitlab-runner register
</code></pre>
<p>Prompts:</p>
<pre><code class="language-text">GitLab URL:
https://gitlab.example.com

Registration Token:
&lt;project-runner-token&gt;

Description:
kp-runner

Tags:
kp-runner

Executor:
shell
</code></pre>
<hr>
<h2 id="register-production-runner">Register Production Runner</h2>
<pre><code class="language-bash">sudo gitlab-runner register
</code></pre>
<p>Prompts:</p>
<pre><code class="language-text">GitLab URL:
https://gitlab.example.com

Registration Token:
&lt;project-runner-token&gt;

Description:
prod-runner

Tags:
prod-runner

Executor:
shell
</code></pre>
<hr>
<h2 id="verify-registered-runners">Verify Registered Runners</h2>
<pre><code class="language-bash">sudo gitlab-runner list
</code></pre>
<p>Example:</p>
<pre><code class="language-text">th-runner
kp-runner
prod-runner
</code></pre>
<hr>
<h2 id="start-runner-service">Start Runner Service</h2>
<pre><code class="language-bash">sudo systemctl enable gitlab-runner
sudo systemctl start gitlab-runner
</code></pre>
<p>Check status:</p>
<pre><code class="language-bash">sudo systemctl status gitlab-runner
</code></pre>
<hr>
<h1 id="deployment-directory-structure">Deployment Directory Structure</h1>
<p>Expected structure on target servers:</p>
<pre><code class="language-text">template-spring-cqrs-reactjs/

&#x251C;&#x2500;&#x2500; server/
&#x2502;
&#x251C;&#x2500;&#x2500; artifacts/
&#x2502;   &#x251C;&#x2500;&#x2500; application.jar
&#x2502;   &#x2514;&#x2500;&#x2500; application.jar_bkp
&#x2502;
&#x2514;&#x2500;&#x2500; server-config/
    &#x2514;&#x2500;&#x2500; application.properties
</code></pre>
<hr>
<h1 id="failure-handling">Failure Handling</h1>
<p>The pipeline is designed to tolerate infrastructure failures.</p>
<p>Examples:</p>
<h3 id="th-runner-offline">TH Runner Offline</h3>
<pre><code class="language-text">TH Build &#x2192; Failed

KP Build &#x2192; Success

KP Deploy &#x2192; Success

Pipeline &#x2192; SUCCESS
</code></pre>
<h3 id="kp-runner-offline">KP Runner Offline</h3>
<pre><code class="language-text">TH Build &#x2192; Success

TH Deploy &#x2192; Success

Pipeline &#x2192; SUCCESS
</code></pre>
<h3 id="both-runners-offline">Both Runners Offline</h3>
<pre><code class="language-text">TH Build &#x2192; Failed

KP Build &#x2192; Failed

Pipeline &#x2192; FAILED
</code></pre>
<hr>
<h1 id="summary">Summary</h1>
<p>This pipeline provides:</p>
<ul>
<li>Multi-location deployment (TH + KP)</li>
<li>Spring Boot Reactive build automation</li>
<li>Automatic changelog generation</li>
<li>Mattermost notifications</li>
<li>Job log reporting on failures</li>
<li>Runner health monitoring</li>
<li>Automatic cancellation of stuck jobs</li>
<li>Production deployment authorization</li>
<li>Artifact backup and rollback preparation</li>
<li>High availability deployment strategy</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[React Redux v9 - `connect`]]></title><description><![CDATA[<h1 id="react-redux-v9-why-connect-shows-a-deprecated-warning-and-how-to-fix-it">React Redux v9: Why <code>connect()</code> Shows a Deprecated Warning and How to Fix It</h1>
<p>If you&apos;ve recently upgraded to React Redux v9 and are using IntelliJ, VS Code, or another TypeScript-aware IDE, you may have noticed a warning when hovering over <code>connect()</code>:</p>
<pre><code class="language-ts">@deprecated
We recommend using the useSelector</code></pre>]]></description><link>https://blog.gyri.tech/react-redux-v9-connect/</link><guid isPermaLink="false">6a30edc45d149203fdd917c1</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Tue, 16 Jun 2026 06:36:58 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1573496773905-f5b17e717f05?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDg0fHx0ZWNoJTIwbWlncmF0aW9ufGVufDB8fHx8MTc4MTU5MTY1Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h1 id="react-redux-v9-why-connect-shows-a-deprecated-warning-and-how-to-fix-it">React Redux v9: Why <code>connect()</code> Shows a Deprecated Warning and How to Fix It</h1>
<img src="https://images.unsplash.com/photo-1573496773905-f5b17e717f05?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDg0fHx0ZWNoJTIwbWlncmF0aW9ufGVufDB8fHx8MTc4MTU5MTY1Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="React Redux v9 - `connect`"><p>If you&apos;ve recently upgraded to React Redux v9 and are using IntelliJ, VS Code, or another TypeScript-aware IDE, you may have noticed a warning when hovering over <code>connect()</code>:</p>
<pre><code class="language-ts">@deprecated
We recommend using the useSelector and useDispatch hooks instead.
See https://react-redux.js.org/api/hooks

If you need to use connect without this visual deprecation warning,
import legacy_connect instead:

import { legacy_connect as connect } from &apos;react-redux&apos;
</code></pre>
<p>This can be surprising, especially because many existing React applications still use <code>connect()</code> extensively.</p>
<p>Let&apos;s understand what&apos;s happening and explore the available options.</p>
<h2 id="is-connect-actually-deprecated">Is <code>connect()</code> Actually Deprecated?</h2>
<p>Not exactly.</p>
<p>React Redux v9 adds a TypeScript/JSDoc deprecation annotation to encourage developers to adopt the modern Hooks API:</p>
<ul>
<li><code>useSelector()</code></li>
<li><code>useDispatch()</code></li>
</ul>
<p>However, <code>connect()</code> still works and remains supported.</p>
<p>The warning is primarily intended to guide developers toward the recommended pattern for function components.</p>
<h2 id="example-existing-connect-implementation">Example: Existing <code>connect()</code> Implementation</h2>
<p>Many applications use a pattern similar to this:</p>
<pre><code class="language-tsx">import { useNavigate } from &apos;react-router-dom&apos;;
import { useEffect } from &apos;react&apos;;
import { connect } from &apos;react-redux&apos;;

const ForgotPassword = ({ auth }) =&gt; {
  const navigate = useNavigate();

  useEffect(() =&gt; {
    if (auth.loggedIn) {
      navigate(&apos;/dashboard&apos;);
    }
  }, [auth.loggedIn, navigate]);

  return &lt;div&gt;Forgot Password&lt;/div&gt;;
};

const mapState = (state) =&gt; ({
  auth: state.auth,
});

export default connect(mapState)(ForgotPassword);
</code></pre>
<p>This code is completely valid and will continue to work.</p>
<p>The only issue is the IDE warning.</p>
<hr>
<h2 id="option-1-migrate-to-hooks-recommended">Option 1: Migrate to Hooks (Recommended)</h2>
<p>For function components, React Redux recommends using hooks instead of <code>connect()</code>.</p>
<h3 id="before">Before</h3>
<pre><code class="language-tsx">export default connect(mapState)(ForgotPassword);
</code></pre>
<h3 id="after">After</h3>
<pre><code class="language-tsx">import { useSelector } from &apos;react-redux&apos;;

const ForgotPassword = () =&gt; {
  const auth = useSelector((state) =&gt; state.auth);

  return &lt;div&gt;Forgot Password&lt;/div&gt;;
};

export default ForgotPassword;
</code></pre>
<p>A more complete example:</p>
<pre><code class="language-tsx">import { useNavigate } from &apos;react-router-dom&apos;;
import { useEffect } from &apos;react&apos;;
import { useSelector } from &apos;react-redux&apos;;

const ForgotPassword = () =&gt; {
  const navigate = useNavigate();

  const auth = useSelector((state) =&gt; state.auth);

  useEffect(() =&gt; {
    if (auth.loggedIn) {
      navigate(&apos;/dashboard&apos;);
    }
  }, [auth.loggedIn, navigate]);

  return &lt;div&gt;Forgot Password&lt;/div&gt;;
};

export default ForgotPassword;
</code></pre>
<h3 id="benefits">Benefits</h3>
<ul>
<li>Less boilerplate</li>
<li>No <code>mapStateToProps</code></li>
<li>Better TypeScript support</li>
<li>Officially recommended by React Redux</li>
</ul>
<hr>
<h2 id="option-2-continue-using-connect">Option 2: Continue Using <code>connect()</code></h2>
<p>If your application already contains many connected components, there is no immediate need to refactor everything.</p>
<p>You can simply keep using:</p>
<pre><code class="language-tsx">import { connect } from &apos;react-redux&apos;;
</code></pre>
<p>The warning is informational and does not affect runtime behavior.</p>
<p>This approach is often preferred in mature enterprise applications where consistency is more important than adopting the latest syntax.</p>
<hr>
<h2 id="option-3-use-legacyconnect">Option 3: Use <code>legacy_connect</code></h2>
<p>React Redux v9 introduces a special alias called <code>legacy_connect</code>.</p>
<p>Replace:</p>
<pre><code class="language-tsx">import { connect } from &apos;react-redux&apos;;
</code></pre>
<p>with:</p>
<pre><code class="language-tsx">import { legacy_connect as connect } from &apos;react-redux&apos;;
</code></pre>
<p>The rest of your code remains unchanged:</p>
<pre><code class="language-tsx">const mapState = (state) =&gt; ({
  auth: state.auth,
});

export default connect(mapState)(ForgotPassword);
</code></pre>
<h3 id="why-use-legacyconnect">Why Use <code>legacy_connect</code>?</h3>
<ul>
<li>Removes IDE deprecation warnings</li>
<li>No behavior changes</li>
<li>No refactoring required</li>
<li>Useful for large existing codebases</li>
</ul>
<hr>
<h2 id="recommended-approach-for-different-scenarios">Recommended Approach for Different Scenarios</h2>
<h3 id="new-projects">New Projects</h3>
<p>Use hooks:</p>
<pre><code class="language-tsx">useSelector()
useDispatch()
</code></pre>
<p>This is the modern React Redux pattern.</p>
<h3 id="existing-applications">Existing Applications</h3>
<p>If your project already contains dozens or hundreds of connected components:</p>
<pre><code class="language-tsx">import { legacy_connect as connect } from &apos;react-redux&apos;;
</code></pre>
<p>can be a practical intermediate solution.</p>
<h3 id="gradual-migration-strategy">Gradual Migration Strategy</h3>
<p>Many teams adopt the following approach:</p>
<ol>
<li>Keep existing <code>connect()</code> components.</li>
<li>Use hooks for all new components.</li>
<li>Gradually migrate old components when they are modified.</li>
<li>Use <code>legacy_connect</code> to suppress warnings during the transition.</li>
</ol>
<p>This avoids large-scale refactoring while moving toward modern React Redux patterns.</p>
<hr>
<h2 id="typescript-bonus-typed-hooks">TypeScript Bonus: Typed Hooks</h2>
<p>For Redux Toolkit applications, consider creating typed hooks.</p>
<pre><code class="language-ts">// store.ts
export type RootState = ReturnType&lt;typeof store.getState&gt;;
export type AppDispatch = typeof store.dispatch;
</code></pre>
<pre><code class="language-ts">// hooks.ts
import { useDispatch, useSelector } from &apos;react-redux&apos;;

export const useAppDispatch =
  useDispatch.withTypes&lt;AppDispatch&gt;();

export const useAppSelector =
  useSelector.withTypes&lt;RootState&gt;();
</code></pre>
<p>Usage:</p>
<pre><code class="language-tsx">const auth = useAppSelector((state) =&gt; state.auth);
</code></pre>
<p>This provides stronger type safety and a better developer experience.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>The <code>connect()</code> warning in React Redux v9 is not an indication that the API has been removed. It is a recommendation from the React Redux team to use the Hooks API in modern React applications.</p>
<p>You can choose one of three paths:</p>
<ul>
<li><strong>Best for new code:</strong> <code>useSelector()</code> and <code>useDispatch()</code></li>
<li><strong>Best for existing code:</strong> continue using <code>connect()</code></li>
<li><strong>Best for removing warnings without refactoring:</strong> <code>legacy_connect</code></li>
</ul>
<p>For most teams, a gradual migration strategy offers the best balance between modernizing the codebase and minimizing risk.</p>
]]></content:encoded></item><item><title><![CDATA[Installing Zsh on Ubuntu: A Beginner-Friendly Guide]]></title><description><![CDATA[<p></p><p><strong>Introduction</strong></p><p>After installing Ubuntu, one of the first things many users encounter is the Linux terminal. By default,Ubuntu uses <strong>Bash (Bourne Again Shell)</strong> as its command-line shell. While Bash is powerful and widely used,many developers and Linux enthusiasts prefer an alternative shell called <strong>Zsh (Z Shell)</strong>.<br>This guide</p>]]></description><link>https://blog.gyri.tech/installing-zsh-on-ubuntu-a-beginner-friendly-guide/</link><guid isPermaLink="false">6a20116b5600f10407326b07</guid><dc:creator><![CDATA[Niranjan Kolwankar]]></dc:creator><pubDate>Thu, 11 Jun 2026 10:55:52 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/06/zsh-7172334_640-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/06/zsh-7172334_640-1.png" alt="Installing Zsh on Ubuntu: A Beginner-Friendly Guide"><p></p><p><strong>Introduction</strong></p><p>After installing Ubuntu, one of the first things many users encounter is the Linux terminal. By default,Ubuntu uses <strong>Bash (Bourne Again Shell)</strong> as its command-line shell. While Bash is powerful and widely used,many developers and Linux enthusiasts prefer an alternative shell called <strong>Zsh (Z Shell)</strong>.<br>This guide explains what Zsh is, why it is popular, how to install it on Ubuntu, and what happens during its<br>first-time setup. The explanations are written with beginners in mind, breaking down every command used<br>during the installation process.</p><hr><p><strong>What is a Shell?</strong></p><p>Before understanding Zsh, it is important to understand what a shell is.</p><p>A shell is a program that allows users to interact with the operating system through commands. Whenever commands such as the following are executed:</p><p><strong><em>ls<br>cd<br>mkdir</em></strong></p><p>the shell interprets those commands and communicates with the operating system to perform the requested actions.</p><p>Ubuntu uses <strong>Bash (Bourne Again Shell)</strong> as its default shell.</p><hr><p><strong>What is Zsh?</strong></p><p>Zsh stands for Z Shell. It is an advanced shell that provides all the features of Bash while adding many enhancements that improve the command-line experience.</p><p>Some of the most popular features of Zsh include:<br>        &#x2022; Better command auto-completion<br>        &#x2022; Easier navigation through directories<br>        &#x2022; Command suggestions and corrections<br>        &#x2022; Customizable themes<br>        &#x2022; Plugin support<br>        &#x2022; Improved productivity for developers<br></p><p>Because of these features, Zsh has become one of the most widely used shells in the Linux community.</p><hr><p><strong>Why Use Zsh?</strong></p><p>Many users choose Zsh for the following reasons:<br><strong><em>Better Auto-Completion</em></strong><br>Zsh provides intelligent suggestions when typing commands, file names, and directories.<br><strong><em>Improved Productivity</em></strong><br>Frequently used commands can be executed more quickly with the help of plugins and shortcuts.<br><strong><em>Customization</em></strong><br>The terminal appearance can be customized using themes, making it more informative and visually appealing.<br><strong><em>Learning Modern Linux Tools</em></strong><br>Many developers use Zsh along with frameworks such as Oh My Zsh, making it a valuable tool for anyone learning Linux development environments.</p><hr><p><strong>Installing Zsh</strong></p><p><em><strong>Step 1: Updating Package Information</strong></em><br>Before installing any software, Ubuntu&apos;s package information should be updated.<br><em>sudo apt update<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>sudo</em> = Super User DO. Temporarily grants administrator privileges.<br>   &#x2022; <em>apt</em> = Advanced Package Tool used to manage software packages.<br>   &#x2022; <em>update</em> = Downloads the latest package information from Ubuntu repositories.<br>This command does <strong>not upgrade software</strong>. It only refreshes the package database so Ubuntu knows which software versions are available.<br><em><strong>Step 2: Installing Zsh</strong></em><br>Install Zsh using the following command:<br><em>sudo apt install zsh -y<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>sudo</em> = Runs the command with administrator privileges.<br>   &#x2022; <em>apt</em> = Ubuntu package manager.<br>   &#x2022; <em>install</em> = Installs a software package.<br>   &#x2022; <em>zsh</em> = The package to be installed.<br>   &#x2022; -<em>y</em> = Automatically answers &quot;Yes&quot; to confirmation prompts.<br>This command downloads and installs Zsh along with any required dependencies.<br><em><strong>Step 3: Verifying Installation</strong></em><br>After installation, verify that Zsh has been installed correctly.<br><em>zsh --version<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>zsh</em> = Runs the Zsh executable.<br>   &#x2022; --<em>version</em> = Displays version information.<br>Example output:<br><em>zsh 5.9</em><br>Displaying a version number confirms that the installation was successful.<br><em><strong>Step 4: Making Zsh the Default Shell</strong></em><br>Installing Zsh does not automatically replace Bash. To make Zsh the default shell, execute:<br><em>chsh -s $(which zsh)<br><strong>Understanding the Command</strong></em><br>This command consists of multiple parts.<br><em>which zsh</em><br>   &#x2022; <em>which</em> = Finds the location of an executable program.<br>   &#x2022; <em>zsh</em> = The program being searched for.<br><em><strong>Example output:</strong><br>/usr/bin/zsh</em><br>This shows the location of the Zsh executable.<br><em>$(which zsh)</em><br>The $() syntax is known as <strong>command substitution</strong>.<br>It executes the command inside the brackets and uses its output.<br>Therefore:<br><em>$(which zsh)</em><br>becomes:<br><em>/usr/bin/zsh<br>chsh -s</em><br>   &#x2022; <em>chsh</em> = Change Shell.<br>   &#x2022; -<em>s</em> = Specifies which shell should be used.<br>Therefore:<br><em>chsh -s $(which zsh)</em><br>effectively becomes:<br><em>chsh -s /usr/bin/zsh</em><br>This tells Linux to use Zsh as the default login shell for the current user.<br>After running the command, log out and log back in.</p><hr><p><strong>First Launch of Zsh</strong></p><p>After logging back in and opening the terminal, Zsh may display a configuration screen called <strong>zsh-newuser-install.</strong><br>This happens because no Zsh configuration files currently exist in the user&apos;s home directory. Zsh therefore launches a setup wizard to create an initial configuration.<br>The setup wizard presents four options.<br><strong><em>Option (q)</em></strong><br>  (q) Quit and do nothing.<br>        &#x2022; Exits the setup wizard.<br>        &#x2022; The wizard will appear again the next time Zsh starts.<br><strong><em>Option (0)</em></strong><br>  (0) Exit, creating the file ~/.zshrc containing just a comment.<br>        &#x2022; Creates a minimal .zshrc file.<br>        &#x2022; Prevents the setup wizard from appearing again.<br>        &#x2022; Useful for users who want to configure everything manually.<br><strong><em>Option (1)</em></strong><br>  (1) Continue to the main menu.<br>        &#x2022; Opens the advanced configuration menu.<br>        &#x2022; Allows customization of history settings, key bindings, completion settings, and                                 other shell features. <br><strong><em>Option (2)</em></strong><br>  (2) Populate your ~/.zshrc with the configuration recommended by the system<br>  administrator.<br>        &#x2022; Automatically creates a recommended .zshrc file.<br>        &#x2022; Applies sensible default settings.<br>        &#x2022; Recommended for beginners.</p><hr><p><strong>Recommended Choice for Beginners</strong><br>For users who are new to Zsh, selecting:<br><strong>2</strong><br>is generally the best option.<br>This option:<br>        &#x2022; Creates the configuration file automatically.<br>        &#x2022; Applies useful default settings.<br>        &#x2022; Allows immediate use of Zsh without manual configuration.</p><hr><p><strong>Understanding the .zshrc File</strong><br>During the setup process, a file named:<br><em>~/.zshrc</em><br>is created.<br><strong><em>Breaking It Down</em></strong><br>   &#x2022; ~ = Home directory of the current user.<br>   &#x2022; .zshrc = Zsh Run Commands file.<br>   &#x2022; The dot ( . ) indicates that it is a hidden file.<br>The .zshrc file stores:<br>        &#x2022; Shell settings<br>        &#x2022; Aliases<br>        &#x2022; Plugins<br>        &#x2022; Themes<br>        &#x2022; Environment variables<br>        &#x2022; Custom terminal configurations<br>Every time Zsh starts, it reads this file and applies the stored settings.</p><hr><p><strong>Verifying the Configuration File</strong><br>To verify that the configuration file was created successfully:<br><em>ls -la ~/.zshrc<br><strong>Understanding the Command</strong></em><br>   &#x2022; ls = Lists files and directories.<br>   &#x2022; -l = Displays detailed information.<br>   &#x2022; -a = Shows hidden files.<br>   &#x2022; ~/.zshrc = Path of the configuration file.<br>If the file exists, Linux displays its details.</p><hr><p><strong>Key Takeaways</strong><br>Installing Zsh introduces several important Linux concepts:<br>        &#x2022; Linux supports multiple shells.<br>        &#x2022; Users can choose their preferred shell.<br>        &#x2022; Shell behavior is controlled through configuration files.<br>        &#x2022; Commands can be combined using command substitution.<br>        &#x2022; Terminal environments can be customized extensively.<br>Understanding these concepts provides a strong foundation for further Linux learning.</p><hr><p><strong>Conclusion</strong><br>Zsh is a modern and feature-rich shell that enhances the Linux terminal experience through improved auto-completion, customization, and productivity features. Installing Zsh is a simple process, but it also serves as an excellent opportunity to learn about package management, shell configuration, and Linux command-line fundamentals.<br>For beginners exploring Ubuntu and Linux, Zsh provides a practical introduction to terminal customization while maintaining compatibility with familiar Bash workflows.</p>]]></content:encoded></item><item><title><![CDATA[What is Kafka? (Part 1)]]></title><description><![CDATA[<p><u><strong>Definition:</strong></u> <br>Kafka, in simple terms, is a distributed-infrastructure used to store, manage and process <em>events</em> in real-time.<br><br>In traditional software world, developers visualized data as tables which represents things in real world like for example, items in an inventory, signed-up users, cars in a showroom, etc.<br>But what about things</p>]]></description><link>https://blog.gyri.tech/kafka-architecture-part-1/</link><guid isPermaLink="false">6a1d47925600f104073269c7</guid><dc:creator><![CDATA[Abhishek Bhingarde]]></dc:creator><pubDate>Mon, 01 Jun 2026 11:31:26 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/06/kafka.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/06/kafka.jpg" alt="What is Kafka? (Part 1)"><p><u><strong>Definition:</strong></u> <br>Kafka, in simple terms, is a distributed-infrastructure used to store, manage and process <em>events</em> in real-time.<br><br>In traditional software world, developers visualized data as tables which represents things in real world like for example, items in an inventory, signed-up users, cars in a showroom, etc.<br>But what about things like item being dispatched from storeroom, user clicking somewhere on your website, driver picking up the client from a particular location?<br>You guessed it right! These are all <em>events</em>.<br>So, Kafka helps us process these events in real time, meaning the <u>moment that particular event took place.</u><br>Hence, there is no such concept of storing these events in a file or a table for processing them in a batch in future.<br>However, this does not goes on to conclude that Kafka cannot remember events that already took place. Kafka is built to remember these events. </p><p>Lets understand the need for Kafka with the help of this simple example.</p><p>Consider a thermostat_readings table.</p><table>
<thead>
<tr>
<th>sensor_id</th>
<th>location</th>
<th>temperature</th>
<th>read_at</th>
</tr>
</thead>
<tbody>
<tr>
<td>42</td>
<td>Mumbai</td>
<td>24</td>
<td>1700</td>
</tr>
<tr>
<td>51</td>
<td>Delhi</td>
<td>22</td>
<td>1715</td>
</tr>
<tr>
<td>76</td>
<td>Calicut</td>
<td>25</td>
<td>1600</td>
</tr>
</tbody>
</table>
<p>Now, suppose we need to update the thermostat reading for sensor_id 42 from 24 to 20, we will destructively change the value in temperature column for sensor id 42 from 24 to 20.<br>
So our new table, looks like:</p>
<table>
<thead>
<tr>
<th>sensor_id</th>
<th>location</th>
<th>temperature</th>
<th>read_at</th>
</tr>
</thead>
<tbody>
<tr>
<td>42</td>
<td>Mumbai</td>
<td>20</td>
<td>1700</td>
</tr>
<tr>
<td>51</td>
<td>Delhi</td>
<td>22</td>
<td>1715</td>
</tr>
<tr>
<td>76</td>
<td>Calicut</td>
<td>25</td>
<td>1600</td>
</tr>
</tbody>
</table>
<p>The critical issue here is that, we lost our previous info (context) of the sensor_id in Mumbai, like how quickly the temperature shoots up here or at what time of day it heats up. <br><br>To overcome this crucial challenge, Kafka implements <strong><em>logs.</em></strong></p><p>Now you might be wondering what are logs?<br>Logs can be interpreted as an abstraction used by Kafka to store events as a sequence of data items. Mostly these data items are referred as events and quiet rarely called as messages.<br>The data items are appended at the very end, and items which are already present are never changed! You never modify these logs.<br>In Kafka, logs are known as <strong><em>Topics. </em></strong>So topics are where our messages get accumulated. And ALWAYS remember, messages in Kafka are <strong><em>IMMUTABLE! </em></strong>Once you write them into a topic, you cannot modify them. Basically, its an event which has happened and you cannot rewrite history; as same as you cannot change your past, simple as that!</p><p>So, in a gist, a topic is analogous to a table in a database, and we have numerous tables in a database; similarly we can have numerous topics in a single Kafka cluster.<br><br><strong>NOTE: </strong>The messages in a topic can be of different format unlike a schema which needs to be followed in a database table.<br>The reason behind this is, that Kafka stores these messages as plain bytes. So format or schema doesn&apos;t matter.</p><p>How do you filter out messages out of a topic then?<br>Just like we deal with immutable data structures in various programming languages. Create a copy of that topic and filter out the unnecessary messages.<br><br><strong>MISCONCEPTION: </strong>Topics are <em>Logs. </em>Not <em>queues.</em><br>When a queue is read, the item is taken out of the queue and read. Now, nobody else can read that value. In a Kafka topic, when a message is read, it still stays intact. I can comeback after a few years and read that same message again!</p><p><strong>More on Kafka message:</strong><br>Its a <u>value</u>, associated with a <u>key</u>. <br>For example, consider this JSON:<br>{<br>    &quot;sensor_id&quot;: 42,<br>    &quot;location&quot;: &quot;Mumbai&quot;, <br>    &quot;temperature&quot;: 22, <br>    &quot;read_at&quot;: 1700<br>}<br>Here, the &apos;sensor_id&apos; becomes the key. This key is basically an identifier the payload relates to. Generally any unique id is taken as the key. Its not mandatory, but highly recommended to have a key! <br>We also got the <u>timestamp</u>, which tells us the moment the producer created that message.<br>The message also has some light-weight <u>headers</u>, and the most important parts of a Kafka message are <em>topic</em>, <em>partition</em> and <em>offset</em>!<br><br>Policies available in Kafka to prevent your disk from running out of space:<br>1. Log <strong>retention</strong>: delete old data or trim logs by size<br>2. Log <strong>compaction</strong>: keep only the latest value per key as sometimes context or history is irrelevant to maintain.<br><br><strong>How Kafka scales?</strong><br>If we limit our Kafka topic to a single node, then we will be restricting the topic&apos;s ability to scale to the node&apos;s disk space.<br>Since, Kafka is a distributed system, we partition the topic into several partitions.<br>Once partitions are created we need to decide to which partition the message goes to. <br>The message key helps us to make this decision. If the key is NULL, the messages are distributed in a round robin fashion. If not NULL and to ensure that a message with a certain key ALWAYS goes into a particular partition, hash the message key against a hash function mod the number of partitions and the output of this hash function is the partition number where the message goes to. This also helps us order our messages.<br><br><strong>A few terminologies:</strong></p><ol>
<li><strong>Cluster</strong>: A collection of one or more Kafka brokers working together to share the workload and provide redundancy (simply, a network of nodes).</li>
<li><strong>Node</strong>: The underlying physical or virtual infrastructure that hosts a broker (simply, machine with some disk space for storage purposes).</li>
<li><strong>Broker</strong>: A single Kafka server process that receives, stores, and fetches data (simply, a Kafka software process running on a node).</li>
</ol>
<p>Point to note here is that, a broker has access to disk space on that node on which it&apos;s running; this disk space is basically SSD which is tightly coupled next to the processor.</p>
<p>Brokers are responsible for handling incoming requests to write new messages to partitions as well as read messages out of them.<br><br><strong>Replication (for fault tolerance):</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/06/Screenshot-from-2026-06-01-16-35-07.png" class="kg-image" alt="What is Kafka? (Part 1)" loading="lazy" width="1777" height="703" srcset="https://blog.gyri.tech/content/images/size/w600/2026/06/Screenshot-from-2026-06-01-16-35-07.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/06/Screenshot-from-2026-06-01-16-35-07.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/06/Screenshot-from-2026-06-01-16-35-07.png 1600w, https://blog.gyri.tech/content/images/2026/06/Screenshot-from-2026-06-01-16-35-07.png 1777w" sizes="(min-width: 720px) 720px"></figure><p>What is Replication factor? <br>Number of copies created for each partition.<br><br>We set the replication factor (n), which in above case is 3. The darker one is termed as Leader (lead replica) whereas the remaining (n-1) copies are called as followers.<br>Messages are preferred to be written into and read from the leader but in rare scenarios we might write and read from a replica which is nearest to us in the network. <br>Meanwhile, as messages are written into the lead replica, the followers keep scrapping out the newly written messages from the leader to keep themselves updated and have everything replicated.<br><br>Now in case the Broker 1 fails, we still have copies of Partitions 0 (with brokers 2, 3) and 2 (with brokers 3, 4). <br>Here, Broker 1 had the leader of partition 0. So new leader will be elected for partition 0.<br><br><strong>PRODUCER:</strong><br>A producer is nothing but a Kafka client which writes data into the Kafka partition.<br><strong>CONSUMER:</strong><br>A consumer is a Kafka client that reads from the Kafka topic.<br><br>In short, anything which is not a broker, is, at the end of the day a producer or a consumer.<br><br>REFERENCE: <br>Apache Kafka 101 - <a href="https://developer.confluent.io/courses/apache-kafka/events/?ref=blog.gyri.tech">https://developer.confluent.io/courses/apache-kafka/events/</a></p>]]></content:encoded></item><item><title><![CDATA[The Complete Guide to Setting Up SSH Keys on Ubuntu, Windows, and macOS (With GitLab Integration)]]></title><description><![CDATA[<h1 id="step-1-understand-what-ssh-is">Step 1: Understand What SSH Is</h1><p>SSH stands for <strong>Secure Shell</strong>.<br>It is a secure protocol used to:</p><ul><li>Connect to remote servers</li><li>Clone Git repositories</li><li>Push and pull code securely</li><li>Deploy applications</li></ul><p>Instead of using passwords, SSH uses <strong>cryptographic key pairs</strong>.</p><hr><h1 id="step-2-understand-ssh-key-pair">Step 2: Understand SSH Key Pair</h1><p>When you create</p>]]></description><link>https://blog.gyri.tech/the-complete-guide-to-setting-up-ssh-keys-on-ubuntu-windows-and-macos-with-git-lab-integration/</link><guid isPermaLink="false">6996ab4de63ce6040c22526c</guid><dc:creator><![CDATA[AsifDesai]]></dc:creator><pubDate>Thu, 19 Feb 2026 06:51:15 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/02/git-lab-ssh-key.webp" medium="image"/><content:encoded><![CDATA[<h1 id="step-1-understand-what-ssh-is">Step 1: Understand What SSH Is</h1><img src="https://blog.gyri.tech/content/images/2026/02/git-lab-ssh-key.webp" alt="The Complete Guide to Setting Up SSH Keys on Ubuntu, Windows, and macOS (With GitLab Integration)"><p>SSH stands for <strong>Secure Shell</strong>.<br>It is a secure protocol used to:</p><ul><li>Connect to remote servers</li><li>Clone Git repositories</li><li>Push and pull code securely</li><li>Deploy applications</li></ul><p>Instead of using passwords, SSH uses <strong>cryptographic key pairs</strong>.</p><hr><h1 id="step-2-understand-ssh-key-pair">Step 2: Understand SSH Key Pair</h1><p>When you create an SSH key, two files are generated:</p>
<!--kg-card-begin: html-->
<table data-start="876" data-end="1025" class="w-fit min-w-(--thread-content-width)"><thead data-start="876" data-end="894"><tr data-start="876" data-end="894"><th data-start="876" data-end="883" data-col-size="sm" class>File</th><th data-start="883" data-end="894" data-col-size="sm" class>Purpose</th></tr></thead><tbody data-start="915" data-end="1025"><tr data-start="915" data-end="973"><td data-start="915" data-end="930" data-col-size="sm"><code data-start="917" data-end="929">id_ed25519</code></td><td data-col-size="sm" data-start="930" data-end="973">Private key (Keep secret &#x274C; Never share)</td></tr><tr data-start="974" data-end="1025"><td data-start="974" data-end="993" data-col-size="sm"><code data-start="976" data-end="992">id_ed25519.pub</code></td><td data-col-size="sm" data-start="993" data-end="1025">Public key (Safe to share &#x2705;)</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>How it works:</p><ol><li>You give your <strong>public key</strong> to GitLab.</li><li>GitLab stores it.</li><li>When you connect, GitLab verifies you using your <strong>private key</strong>.</li><li>If matched &#x2192; Access granted.<br><br>No password needed.</li></ol><hr><h1 id="step-3-why-we-use-ssh-in-gitlab">Step 3: Why We Use SSH in GitLab</h1><p>GitLab is used for:</p><ul><li>Hosting repositories</li><li>CI/CD</li><li>Version control</li><li>Collaboration</li></ul><p>Without SSH (using HTTPS):</p><p><code>git clone</code> https://gitlab.com/username/project.git<br>Every push asks for username &amp; password.</p><p>With SSH:<code>git clone</code> git@gitlab.com:username/project.git<br>Push &#x2192; Done instantly &#x1F680;<br>That&#x2019;s why developers prefer SSH.<br></p><hr><h1 id="step-4-generate-ssh-key-on-ubuntu">Step 4: Generate SSH Key on Ubuntu</h1><h3 id="1%EF%B8%8F%E2%83%A3-open-terminal">1&#xFE0F;&#x20E3; Open Terminal</h3><p>Press:<code>Ctrl + Alt + T</code><br><br>2&#xFE0F;&#x20E3; Check If SSH Already Exists<br><code>ls</code> -al ~/.ssh<br>If folder doesn&#x2019;t exist &#x2192; no problem.</p><h3 id="3%EF%B8%8F%E2%83%A3-generate-new-ssh-key">3&#xFE0F;&#x20E3; Generate New SSH Key</h3><p>Recommended modern algorithm:<br><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>If not supported:<br><code>ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;</code><br>Press Enter to accept default location:<code>/home/username/.ssh/</code>id_ed25519<br>Set passphrase (optional but recommended).<br><br>4&#xFE0F;&#x20E3; Start SSH Agent<br><code>eval &quot;$(ssh-agent -s)</code>&quot;<br><br>5&#xFE0F;&#x20E3; Add Key to SSH Agent<br>ssh-add ~/.ssh/id_ed25519</p><p>6&#xFE0F;&#x20E3; Copy Public Key<br><code>cat</code> ~/.ssh/id_ed25519.pub<br>Copy the entire output.</p><hr><h1 id="step-5-generate-ssh-key-on-windows">Step 5: Generate SSH Key on Windows</h1><h2 id="option-1-using-git-bash-recommended">Option 1: Using Git Bash (Recommended)</h2><ol><li>Install Git for Windows</li><li>Open Git Bash</li></ol><p>Run:<code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>Keys are stored in:<code>C:\Users\YourUsername\.ssh\</code><br><br>View public key:<code>cat</code> ~/.ssh/id_ed25519.pub<br></p><h2 id="option-2-using-powershell">Option 2: Using PowerShell</h2><p>Open PowerShell and run:</p><p><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>Same process..</p><hr><h1 id="step-6-generate-ssh-key-on-macos">Step 6: Generate SSH Key on macOS</h1><h3 id="1%EF%B8%8F%E2%83%A3-open-terminal-1">1&#xFE0F;&#x20E3; Open Terminal</h3><p><code>Cmd + Space &#x2192; Terminal</code></p><h3 id="2%EF%B8%8F%E2%83%A3-generate-key">2&#xFE0F;&#x20E3; Generate Key</h3><p><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code></p><h3 id="3%EF%B8%8F%E2%83%A3-start-ssh-agent">3&#xFE0F;&#x20E3; Start SSH Agent</h3><p><code>eval &quot;$(ssh-agent -s)</code>&quot;</p><h3 id="4%EF%B8%8F%E2%83%A3-add-key">4&#xFE0F;&#x20E3; Add Key</h3><p>ssh-add ~/.ssh/id_ed25519</p><h3 id="5%EF%B8%8F%E2%83%A3-copy-public-key">5&#xFE0F;&#x20E3; Copy Public Key</h3><p><code>pbcopy &lt; ~/.ssh/id_ed25519.pub</code></p><hr><h1 id="step-7-add-ssh-key-to-gitlab">Step 7: Add SSH Key to GitLab</h1><ol><li>Login to GitLab</li><li>Click Profile &#x2192; Preferences</li><li>Click <strong>SSH Keys</strong></li><li>Paste copied public key</li><li>Click <strong>Add Key</strong></li></ol><p>Now GitLab trusts your machine.</p><hr><h1 id="step-8-test-ssh-connection">Step 8: Test SSH Connection</h1><p>Run: ssh -T git@gitlab.com<br>If successful, you&#x2019;ll see:<code>Welcome to GitLab!</code></p><hr><h1 id="step-9-clone-using-ssh">Step 9: Clone Using SSH</h1><p>Instead of HTTPS:<br><code>git clone</code> git@gitlab.com:username/project.git<br>Now: git push<br>No password required.</p><hr><h1 id="step-10-security-best-practices"><strong>Ste</strong>p 10: Security Best Practices</h1><p>&#x2714; Use <code>ed25519</code><br>&#x2714; Protect private key<br>&#x2714; Add passphrase<br>&#x2714; Never share private key<br>&#x2714; Set correct permissions (Linux/macOS):</p><p><code>chmod</code> 700 ~/.ssh<br><code>chmod 600 ~/.ssh/id_ed25519</code></p><hr><h1 id="bonus-how-ssh-works-internally">Bonus: How SSH Works Internally</h1><ol><li>Client connects to GitLab</li><li>GitLab checks public key</li><li>GitLab sends encrypted challenge</li><li>Your private key decrypts it</li><li>Verified &#x2192; Access granted</li></ol><p>This uses asymmetric cryptography &#x2014; extremely secure.</p><hr><h1 id="final-conclusion">Final Conclusion</h1><p>Setting up SSH keys:</p><ul><li>Improves security</li><li>Removes password dependency</li><li>Enables automation</li><li>Essential for DevOps</li><li>Industry best practice</li></ul><p>If you are serious about professional development, SSH is not optional &#x2014; it&#x2019;s foundational</p>]]></content:encoded></item><item><title><![CDATA[A Complete Guide to Migrating to Spring Boot 4.0.1]]></title><description><![CDATA[<h2 id="technical-guide-upgrading-to-spring-boot-401">Technical Guide: Upgrading to Spring Boot 4.0.1</h2>
<p>This guide outlines the technical requirements, dependency changes, and configuration updates necessary to migrate a Spring Boot application to version 4.0.1.</p>
<p><strong>1. Build Environment Update</strong><br>
Before updating application dependencies, ensure your build tool is compatible with Spring Boot 4.</p>]]></description><link>https://blog.gyri.tech/spring-boot/</link><guid isPermaLink="false">697c526cec9cb1040bfad28c</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Fri, 30 Jan 2026 07:27:01 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/01/spring_boot_4.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="technical-guide-upgrading-to-spring-boot-401">Technical Guide: Upgrading to Spring Boot 4.0.1</h2>
<img src="https://blog.gyri.tech/content/images/2026/01/spring_boot_4.jpg" alt="A Complete Guide to Migrating to Spring Boot 4.0.1"><p>This guide outlines the technical requirements, dependency changes, and configuration updates necessary to migrate a Spring Boot application to version 4.0.1.</p>
<p><strong>1. Build Environment Update</strong><br>
Before updating application dependencies, ensure your build tool is compatible with Spring Boot 4.0.1.<br>
<strong>Gradle Version</strong>: Update to Gradle 9.2.1. This version is required to support the updated Spring Boot plugins and dependencies.</p>
<p><strong>2. Dependency Management</strong><br>
Update your build.gradle.kts dependencies to their compatible versions.<br>
<strong>Spring Boot Starters</strong>: Ensure all standard starters are updated to the managed versions provided by the Spring Boot 4.0.1 BOM:</p>
<ul>
<li>spring-boot-starter-actuator</li>
<li>spring-boot-starter-data-mongodb-reactive</li>
<li>spring-boot-starter-security</li>
<li>spring-boot-starter-webflux</li>
<li>spring-boot-starter-oauth2-client</li>
</ul>
<p><strong>Third-Party Library Updates</strong>: You must manually update several key libraries to versions compatible with Spring Boot 4:</p>
<ul>
<li>Redisson: Update redisson-spring-boot-starter to 4.1.0.</li>
<li>SpringDoc OpenAPI (Swagger): Update to springdoc-openapi-starter-webflux-ui:3.0.1 to maintain API documentation support.</li>
</ul>
<p><strong>3. Breaking Change: Jackson 3 Migration</strong><br>
Spring Boot 4.0.1 moves from Jackson 2 to Jackson 3, which introduces significant namespace changes. This will cause compilation errors until references are updated.</p>
<p><strong>Dependency Changes</strong>: Replace the standard Kotlin module dependency:</p>
<p>// Remove or Update<br>
<code>implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin&quot;)</code><br>
// Add New Tools Module<br>
<code>implementation(&quot;tools.jackson.module:jackson-module-kotlin&quot;)</code><br>
// Maintain backward compatibility if needed for specific libs<br>
<code>implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin:2.20.1&quot;)</code></p>
<p><strong>Code Refactoring Required</strong>:</p>
<ul>
<li>
<p>Imports: Update your import statements for the object mapper.<br>
From: <code>com.fasterxml.jackson.module.kotlin.jacksonObjectMapper</code><br>
To: <code>tools.jackson.module.kotlin.jacksonObjectMapper</code></p>
</li>
<li>
<p>Codecs: If you are using Redisson or custom codecs, update <code>TypedJsonJacksonCodec</code> to <code>TypedJsonJackson3Codec</code>.</p>
</li>
</ul>
<p><strong>4. Configuration Properties Updates</strong><br>
Several properties in application.properties have been renamed or require explicit enablement.<br>
<strong>MongoDB Configuration:</strong> The <strong>spring.data.mongodb</strong> prefix has been simplified. You must remove the <strong>.data</strong> segment from your keys.</p>
<p>Full Mapping:</p>
<pre><code>spring.mongodb.host=192.168.1.122
spring.mongodb.port=27017
spring.mongodb.database=AppDBTest
spring.mongodb.username=admin
spring.mongodb.password=secret
spring.mongodb.authentication-database=admin
</code></pre>
<p><strong>Swagger UI</strong>: SpringDoc now requires explicit properties to enable the UI and API docs endpoints:</p>
<pre><code>springdoc.api-docs.enabled=true
springdoc.swagger-ui.enabled=true
</code></pre>
<p><strong>5. Tooling: Replacing Ktlint with Spotless</strong><br>
Older versions of ktlint may not be supported in this environment. It is recommended to switch to the Spotless Gradle plugin for code formatting.</p>
<p><strong>Implementation</strong>: Add the plugin to your build.gradle.kts:</p>
<pre><code>plugins {
    id(&quot;com.diffplug.spotless&quot;) version &quot;8.1.0&quot;
}
</code></pre>
<p><strong>Configuration</strong>: Configure Spotless to handle both Kotlin and Java formatting:</p>
<pre><code>spotless {
  kotlin {
    target(&quot;**/*.kt&quot;)
    ktfmt(&quot;0.53&quot;)
    trimTrailingWhitespace()
    endWithNewline()
  }

  java {
    target(&quot;**/*.java&quot;)
    googleJavaFormat(&quot;1.19.2&quot;)
    removeUnusedImports()
    trimTrailingWhitespace()
    endWithNewline()
  }

  // Bind to compilation tasks
  tasks.named(&quot;spotlessCheck&quot;) { dependsOn(&quot;compileKotlin&quot;, &quot;compileJava&quot;) }
  tasks.named(&quot;spotlessApply&quot;) { dependsOn(&quot;compileKotlin&quot;, &quot;compileJava&quot;) }
}
</code></pre>
<hr>]]></content:encoded></item><item><title><![CDATA[Nextcloud: The Master-Slave Guide]]></title><description><![CDATA[<h2 id="nextcloud-manual-setup-steps"><strong>Nextcloud manual setup steps</strong></h2><ul>
<li><strong>Update and Install Dependencies</strong></li>
</ul>
<p>We need the web server and all PHP modules required by Nextcloud.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo apt update

sudo apt install apache2 php libapache2-mod-php php-mysql php-common php-gd php-curl php-imagick php-mbstring php-xml php-zip php-intl php-bcmath php-gmp unzip -y</code></pre>]]></description><link>https://blog.gyri.tech/nextcloud/</link><guid isPermaLink="false">69520346ec9cb1040bfad198</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Mon, 29 Dec 2025 06:05:43 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/12/nextcloud-scaled.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="nextcloud-manual-setup-steps"><strong>Nextcloud manual setup steps</strong></h2><ul>
<li><strong>Update and Install Dependencies</strong></li>
</ul>
<img src="https://blog.gyri.tech/content/images/2025/12/nextcloud-scaled.jpg" alt="Nextcloud: The Master-Slave Guide"><p>We need the web server and all PHP modules required by Nextcloud.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo apt update

sudo apt install apache2 php libapache2-mod-php php-mysql php-common php-gd php-curl php-imagick php-mbstring php-xml php-zip php-intl php-bcmath php-gmp unzip -y
</code></pre>
<ul>
<li><strong>Enable Required Apache Modules</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>sudo a2enmod rewrite headers env dir mime
sudo systemctl restart apache2
</code></pre>
<ul>
<li><strong>Create the Directory Structure</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>sudo mkdir -p /var/www/html/nextcloud
sudo mkdir -p /var/www/html/nextcloud/data
</code></pre>
<ul>
<li><strong>Set Permissions</strong></li>
</ul>
<p>Apache (www-data) must own these folders to write files.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud
sudo chmod -R 755 /var/www/html/nextcloud
</code></pre>
<ul>
<li><strong>Download and Unzip</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>cd /var/www/html
sudo wget https://download.nextcloud.com/server/releases/latest.zip
sudo unzip latest.zip
</code></pre>
<p>unzip will extract everything into the nextcloud folder we created</p>
<ul>
<li><strong>Final Permission Fix</strong></li>
</ul>
<p>Ensure the new files are owned by the web server.</p>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud
</code></pre>
<ul>
<li><strong>Configure Apache</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2 to create the web config:</p>
<pre><code>sudo nano /etc/apache2/sites-available/nextcloud.conf
</code></pre>
<p>Paste this content:</p>
<pre><code>&lt;VirtualHost *:80&gt;
    DocumentRoot /var/www/html/nextcloud
    
    &lt;Directory /var/www/html/nextcloud&gt;
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>Enable the site:</p>
<pre><code>sudo a2ensite nextcloud.conf
sudo a2enmod rewrite
sudo systemctl restart apache2
</code></pre>
<hr><h2 id="database-setup-master-slave-replication"><strong>Database setup (Master slave replication)</strong></h2><ul>
<li><strong>Set up the Master (Server 1 Only)</strong></li>
</ul>
<ol>
<li>Secure the installation:</li>
</ol>
<pre><code>sudo mysql\_secure\_installation
</code></pre>
<ol start="2">
<li>Create the Database:</li>
</ol>
<pre><code>sudo mysql -u root -p
</code></pre>
<pre><code>CREATE DATABASE nextcloud;
CREATE USER &apos;nc\_admin&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;12345&apos;;
GRANT ALL PRIVILEGES ON nextcloud.\* TO &apos;nc\_admin&apos;@&apos;localhost&apos;;

-- Create the User for Server 2 to copy data

CREATE USER &apos;replicator&apos;@&apos;%&apos; IDENTIFIED BY &apos;password&apos;;
GRANT REPLICATION SLAVE ON \*.\* TO &apos;replicator&apos;@&apos;%&apos;;
FLUSH PRIVILEGES;
</code></pre>
<ol start="3">
<li>Enable Binary Logging: Edit /etc/mysql/mariadb.conf.d/50-server.cnf:</li>
</ol>
<pre><code>bind-address = 0.0.0.0
server-id    = 1
log\_bin      = /var/log/mysql/mysql-bin.log
binlog\_do\_db = nextcloud
</code></pre>
<p>Restart &amp; Get Coordinates:</p>
<pre><code>sudo systemctl restart mysql
sudo mysql -u root -p -e &quot;SHOW MASTER STATUS;&quot;
</code></pre>
<ul>
<li><strong>Set up the Slave (Server 2 Only)</strong></li>
</ul>
<ol>
<li>
<p>Do NOT create a database. (It will copy Server 1&apos;s DB).</p>
</li>
<li>
<p>Configure Config File: Edit /etc/mysql/mariadb.conf.d/50-server.cnf:</p>
</li>
</ol>
<pre><code>bind-address = 0.0.0.0
server-id    = 2
log\_bin      = /var/log/mysql/mysql-bin.log
binlog\_do\_db = nextcloud
</code></pre>
<ol start="3">
<li>Restart:</li>
</ol>
<pre><code>sudo systemctl restart mysql
</code></pre>
<ul>
<li><strong>Connect Slave to Master</strong></li>
</ul>
<ol>
<li>Run on Server 2: Note: If servers are on different networks (192.168 vs 10.10), open the SSH tunnel first.</li>
</ol>
<pre><code>STOP SLAVE;
CHANGE MASTER TO
MASTER\_HOST=&apos;192.168.0.135&apos;, -- Or &apos;127.0.0.1&apos; if using Tunnel
MASTER\_USER=&apos;replicator&apos;,
MASTER\_PASSWORD=&apos;password&apos;,
MASTER\_LOG\_FILE=&apos;mysql-bin.000001&apos;, -- From Phase 2, Step 1
MASTER\_LOG\_POS=123;                 -- From Phase 2, Step 1
START SLAVE;
</code></pre>
<ol start="2">
<li>Copy Config to Server 2:</li>
</ol>
<p>On Server 1: Open /var/www/html/nextcloud/config/config.php and copy the text.</p>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>On Server 2: Create the file:<br>
Paste the text.</p>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>Important Change: Find the line &apos;dbhost&apos; =&gt; &apos;localhost&apos;, and ensure it stays localhost (so Server 2 reads its own replica DB).</p>
<hr><h2 id="file-synchronization-unison"><strong>File Synchronization (Unison)</strong></h2><ul>
<li><strong>Install Unison (Both Servers)</strong></li>
</ul>
<pre><code>sudo apt install unison -y
</code></pre>
<ul>
<li><strong>Configure SSH Key (Server 2 -&gt; Server 1)</strong></li>
</ul>
<pre><code>ssh-keygen -t ed25519
ssh-copy-id gyriexp@192.168.0.135
</code></pre>
<ul>
<li><strong>Create Sync Profile (Server 2)</strong><br>
Create ~/.unison/default.prf:</li>
</ul>
<pre><code>root = /var/www/html/nextcloud/data
root = ssh://gyriexp@192.168.0.135//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0     # Ignore permissions
</code></pre>
<ul>
<li><strong>Running the Sync</strong></li>
</ul>
<p>Run this sequence whenever you want to backup files:</p>
<ol>
<li>On Server 1 (Unlock):</li>
</ol>
<pre><code>sudo chown -R gyriexp:gyriexp /var/www/html/nextcloud/data
</code></pre>
<ol start="2">
<li>On Server 2 (Sync):</li>
</ol>
<pre><code>unison default
</code></pre>
<ol start="3">
<li>On Server 1 (Lock):</li>
</ol>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud/data
</code></pre>
<h2 id="special-conditions"><strong>Special conditions</strong></h2><h3 id="server-1-fails-%E2%86%92-make-server-2-master-%E2%86%92-restore-server-1-as-slave">Server 1 Fails &#x2192; Make Server 2 Master &#x2192; Restore Server 1 as Slave</h3>
<ul>
<li><strong>Promote Server 2</strong></li>
</ul>
<p>Run these on Server 2 to make it the independent Master.</p>
<p>1.Stop it from looking for the dead master:</p>
<pre><code>sudo mysql -u root -p -e &quot;STOP SLAVE; RESET SLAVE ALL;&quot;
</code></pre>
<p>2.Enable Writes (if Read-Only was on)</p>
<pre><code>sudo mysql -u root -p -e &quot;SET GLOBAL read\_only = OFF;&quot;
</code></pre>
<ol start="3">
<li>Update Nextcloud Config: Ensure config.php points to localhost.</li>
</ol>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>Ensure: &apos;dbhost&apos; =&gt; &apos;localhost&apos;,</p>
<ul>
<li><strong>Server 1 is Back (Make it the Slave)</strong></li>
</ul>
<ol>
<li>Create Backup on Server 2:</li>
</ol>
<p>Run on Server 2</p>
<pre><code>sudo mysqldump -u root -p --all-databases --master-data &gt; production\_backup.sql
</code></pre>
<ol start="2">
<li>Send Backup to Server 1:</li>
</ol>
<p>Run on Server 2</p>
<pre><code>scp production\_backup.sql user@server1\_ip:/home/user/
</code></pre>
<ol start="3">
<li>Import &amp; Configure Slave on Server 1:</li>
</ol>
<p>Run on Server 1</p>
<pre><code>sudo mysql -u root -p &lt; /home/user/production\_backup.sql
</code></pre>
<ol start="4">
<li>Start Replication on Server 1:</li>
</ol>
<p>Run on server 2</p>
<pre><code>sudo mysql -u root -p -e &quot;SHOW MASTER STATUS;&quot;
</code></pre>
<p>Run on server 1</p>
<pre><code>sudo mysql -u root -p
STOP SLAVE;
CHANGE MASTER TO
MASTER\_HOST=&apos;Server2\_IP&apos;,
MASTER\_USER=&apos;replicator&apos;,
MASTER\_PASSWORD=&apos;password&apos;,
MASTER\_LOG\_FILE=&apos;mysql-bin.00000X&apos;,  -- Value from Server 2
MASTER\_LOG\_POS=1234;                 -- Value from Server 2
START SLAVE;
</code></pre>
<ul>
<li><strong>Correct Unison File Sync</strong></li>
</ul>
<p>Since Unison is bidirectional, you usually don&apos;t need to change the command if the config is on Server 2. It will detect Server 2 has &quot;New files&quot; and Server 1 is empty/old, and it will push them automatically.</p>
<pre><code>\# Unlock Server 1
ssh user@server1\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# Run Sync (Pushes S2 data to S1)
unison default

\# Lock Server 1
ssh user@server1\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<p></p><h3 id="normal-chain-is-s1-%E2%86%92-s2-%E2%86%92-s3-server-2-dies-you-need-s1-to-sync-directly-to-s3-when-s2-returns-s3-updates-s2">Normal chain is S1 &#x2192; S2 &#x2192; S3. Server 2 dies. You need S1 to sync directly to S3. When S2 returns, S3 updates S2.</h3>
<ul>
<li><strong>Bypass Server 2 (S2 is Down)</strong></li>
</ul>
<ol>
<li>Create a &quot;Bypass Profile&quot; on Server 3:</li>
</ol>
<pre><code>nano ~/.unison/bypass.prf
</code></pre>
<p>Paste this:</p>
<pre><code>\# Sync Local (S3) with Server 1 (Skipping S2)
root = /var/www/html/nextcloud/data
root = ssh://user@server1\_ip//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0
</code></pre>
<ol start="2">
<li>Run the Bypass Sync: Run this on Server 3 whenever S2 is down.</li>
</ol>
<pre><code>\# 1. Unlock Server 1
ssh user@server1\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# 2. Sync S3 &lt;-&gt; S1
unison bypass

\# 3. Lock Server 1
ssh user@server1\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ul>
<li><strong>Server 2 is Back (Restore &amp; Fill Gaps)</strong></li>
</ul>
<ol>
<li>Sync S3 &#x2192; S2 (Run on Server 3): We create a temporary profile to push data back to the recovered Server 2.</li>
</ol>
<pre><code>nano ~/.unison/restore\_s2.prf
</code></pre>
<pre><code>\# Sync Local (S3) to Server 2
root = /var/www/html/nextcloud/data
root = ssh://user@server2\_ip//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0
</code></pre>
<ol start="2">
<li>Run the Restore Sync:</li>
</ol>
<pre><code>\# 1. Unlock Server 2 (The recovered server)
ssh user@server2\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# 2. Sync S3 -&gt; S2
unison restore\_s2

\# 3. Lock Server 2
ssh user@server2\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ol start="3">
<li>Resume Normal Operations: Now that S2 is up-to-date, you can go back to your original setup (S1 syncing to S2, and S2 syncing to S3).</li>
</ol>
<ul>
<li><strong>step 1: Verify Server 2 is &quot;Clean&quot;</strong></li>
</ul>
<p>Run on Server 2:</p>
<pre><code>ls -l /var/www/html/nextcloud/data/
</code></pre>
<ul>
<li><strong>Step 2: Resume Server 1 &#x2192; Server 2 Sync</strong></li>
</ul>
<ol>
<li>Unlock Server 2<br>
Run on Server 1</li>
</ol>
<pre><code>ssh user@server2_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;
</code></pre>
<ol start="2">
<li>ssh user@server2_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;</li>
</ol>
<pre><code>unison default
</code></pre>
<ol start="3">
<li>Lock Server 2:</li>
</ol>
<pre><code>ssh user@server2_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ul>
<li><strong>Step 3: Resume Server 2 &#x2192; Server 3 Sync</strong></li>
</ul>
<p>Now that S2 is updated by S1, it passes those updates down to S3.<br>
Run on Server 2: (Assuming you have a profile named sync_to_s3 that points to S3).</p>
<p>Unlock Server 3:</p>
<pre><code>ssh user@server3_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;
</code></pre>
<p>Run the Standard Sync:</p>
<pre><code>unison sync_to_s3
</code></pre>
<p>Lock Server 3:</p>
<pre><code>ssh user@server3_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Designing a Reactive, Event-Driven Email OTP Login System with CQRS and Event Sourcing]]></title><description><![CDATA[<p>Modern authentication systems are moving beyond passwords. With the rise of <strong>passwordless authentication</strong>, <strong>One-Time Passwords (OTPs)</strong> delivered over email or SMS are becoming a trusted standard for secure and seamless user logins.</p><p>However, implementing an OTP-based login mechanism that is <strong>scalable, reactive, and audit-compliant</strong> requires more than a simple verification</p>]]></description><link>https://blog.gyri.tech/building-a-reactive-event-driven-otp-login-system-with-spring-boot-cqrs-event-sourcing/</link><guid isPermaLink="false">691303ee7c25ab040d0c2db6</guid><dc:creator><![CDATA[Sangram Ekshinge]]></dc:creator><pubDate>Tue, 11 Nov 2025 11:06:10 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/11/Screenshot-2025-11-11-163110-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2025/11/Screenshot-2025-11-11-163110-1.png" alt="Designing a Reactive, Event-Driven Email OTP Login System with CQRS and Event Sourcing"><p>Modern authentication systems are moving beyond passwords. With the rise of <strong>passwordless authentication</strong>, <strong>One-Time Passwords (OTPs)</strong> delivered over email or SMS are becoming a trusted standard for secure and seamless user logins.</p><p>However, implementing an OTP-based login mechanism that is <strong>scalable, reactive, and audit-compliant</strong> requires more than a simple verification service. It demands an architecture that supports <strong>asynchronous workflows</strong>, <strong>event traceability</strong>, and <strong>reactive scalability</strong> &#x2014; all while keeping the system maintainable and extendable.</p><p>In this post, we&#x2019;ll walk through the <strong>conceptual design</strong> of a <strong>Reactive OTP Login System</strong> built using <strong>Spring Boot</strong>, <strong>CQRS (Command Query Responsibility Segregation)</strong>, and <strong>Event Sourcing</strong>.<br>We&#x2019;ll focus purely on the architecture and system flow &#x2014; without diving into code &#x2014; to illustrate how such a system achieves <strong>high throughput</strong>, <strong>clean separation of concerns</strong>, and <strong>full auditability</strong>.</p><h2 id="architectural-overview">Architectural Overview</h2><p>At its core, this system adopts a <strong>Reactive CQRS + Event Sourcing</strong> design:</p><ul><li><strong>Commands</strong> modify the system state (e.g., logging in a user).</li><li><strong>Queries</strong> fetch or compute data without altering state (e.g., generating OTP for a user).</li><li><strong>Events</strong> record every state transition as an immutable fact.</li><li><strong>Reactive Streams (Mono/Flux)</strong> ensure non-blocking, backpressure-aware execution.</li></ul><h2 id="step-1-requesting-an-email-otp">Step 1: Requesting an Email OTP</h2><p>The OTP generation process begins when a user initiates a login using their email.</p><h3 id="conceptual-flow">Conceptual Flow</h3><ol><li>The <strong>client</strong> sends a request to <code>/api/v1/user/request/emailloginotp/{email}</code>.</li><li>The <strong>Query Controller</strong> constructs a <code>RequestEmailLoginOTPQuery</code>, encapsulating the user&#x2019;s email and tenant metadata.</li><li>The <strong>Query Dispatcher</strong> routes the query to the appropriate <strong>Query Handler</strong>.</li><li>The <strong>Query Handler</strong> verifies whether the user exists and is active using a <strong>reactive repository</strong>.</li><li>If valid, it invokes <strong>EmailLoginOTPService</strong> to generate a unique OTP.</li><li>It then emits an <strong>EmailOTPSentEvent</strong>, which is published to <strong>Kafka</strong> for asynchronous notification delivery and audit tracking.</li><li>The API responds with a confirmation that the OTP request was processed successfully.</li></ol><h2 id="step-2-validating-the-email-otp-login-process">Step 2: Validating the Email OTP (Login Process)</h2><p>Once the user receives the OTP, they can authenticate using it.</p><h3 id="conceptual-flow-1">Conceptual Flow</h3><ol><li>The <strong>client</strong> submits a POST request to <code>/api/v1/user/validate/emailloginotp</code> with <code>email</code> and <code>otp</code>.</li><li>The <strong>Command Controller</strong> validates the OTP through the <strong>EmailLoginOTPService</strong>.</li><li>On success, it dispatches a <strong>LogInWithEmailOTPCommand</strong> to the <strong>Command Handler</strong>.</li><li>The <strong>Command Handler</strong> performs several key actions:<ul><li>Rehydrates the user&#x2019;s aggregate state from the <strong>Event Store</strong>.</li><li>Raises a <strong>LoggedInWithEmailOTPEvent</strong> to represent successful login.</li><li>Persists this event back to the <strong>Event Store</strong>.</li></ul></li><li>The <strong>Event Handler</strong> listens for the event, updating the <strong>read projection</strong> (user table) to reflect login status.</li><li>The <strong>JWT Token Provider</strong> issues a new <strong>access token</strong> for the authenticated session.</li><li>The client receives a success response along with the token.</li></ol><h2 id="event-sourcing-lifecycle">Event Sourcing Lifecycle</h2><p>Event Sourcing lies at the heart of this system.<br>Every change in user state &#x2014; from OTP sent to login success &#x2014; is captured as a <strong>domain event</strong> and stored immutably in the <strong>Event Store</strong>.</p><h3 id="conceptual-event-chain">Conceptual Event Chain</h3>
<!--kg-card-begin: html-->
<table data-start="6159" data-end="6635" class="w-fit min-w-(--thread-content-width)"><thead data-start="6159" data-end="6253"><tr data-start="6159" data-end="6253"><th data-start="6159" data-end="6170" data-col-size="sm">Sequence</th><th data-start="6170" data-end="6198" data-col-size="sm">Event Name</th><th data-start="6198" data-end="6253" data-col-size="md">Description</th></tr></thead><tbody data-start="6349" data-end="6635"><tr data-start="6349" data-end="6443"><td data-start="6349" data-end="6360" data-col-size="sm">1</td><td data-start="6360" data-end="6388" data-col-size="sm"><code data-start="6362" data-end="6381">EmailOTPSentEvent</code></td><td data-start="6388" data-end="6443" data-col-size="md">OTP generated and dispatched via Kafka.</td></tr><tr data-start="6444" data-end="6540"><td data-start="6444" data-end="6455" data-col-size="sm">2</td><td data-start="6455" data-end="6485" data-col-size="sm"><code data-start="6457" data-end="6484">LoggedInWithEmailOTPEvent</code></td><td data-start="6485" data-end="6540" data-col-size="md">User successfully logged in with the OTP.</td></tr><tr data-start="6541" data-end="6635"><td data-start="6541" data-end="6552" data-col-size="sm">3</td><td data-start="6552" data-end="6580" data-col-size="sm"><code data-start="6554" data-end="6574">AuditRecordedEvent</code></td><td data-start="6580" data-end="6635" data-col-size="md">Login and OTP actions recorded for compliance.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Replaying these events allows the system to <strong>rebuild state from history</strong>, ensuring <strong>full traceability</strong> and <strong>fault recovery</strong> without traditional database state dependencies.</p><h2 id="security-and-compliance-considerations">Security and Compliance Considerations</h2><p>Security is baked into every stage of the pipeline:</p><ul><li><strong>OTP expiration, uniqueness, and rate limiting</strong> are handled by the <code>EmailLoginOTPService</code>.</li><li><strong>JWT tokens</strong> provide stateless, secure session management.</li><li><strong>Audit events</strong> ensure all critical actions are logged for compliance.</li><li><strong>Tenant-aware scoping</strong> enforces strict data isolation in multi-tenant setups.</li></ul><p>By combining event logs with JWT-based access, the system achieves both <strong>security</strong> and <strong>observability</strong>.</p><h2 id="conclusion">Conclusion</h2><p>Building an OTP-based authentication system using <strong>Reactive</strong>, <strong>CQRS</strong>, and <strong>Event Sourcing</strong> principles enables:</p><p><strong>Scalable, non-blocking performance</strong> under heavy traffic<br><strong>Strict auditability</strong> with event logs<br><strong>Fault tolerance</strong> through event replay<br><strong>Extensibility</strong> for future login channels or audit pipelines<br><strong>Seamless user experience</strong> with real-time responsiveness</p><p>In essence, this architecture transforms a simple OTP mechanism into a <strong>production-grade authentication framework</strong> &#x2014; one that is reactive by nature, event-driven by design, and compliant by default.</p>]]></content:encoded></item><item><title><![CDATA[Securely Embedding Documents in Excel: The Short URL Pattern]]></title><description><![CDATA[<h2 id="the-problem">The Problem</h2>
<p>Enterprise applications that export Excel reports with embedded documents face a critical security challenge. The straightforward implementation exposes internal system identifiers:</p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p>This reveals:</p>
<ul>
<li>Internal document IDs (12345)</li>
<li>Tenant identifiers (67890)</li>
<li>API structure and endpoints</li>
</ul>
<p>Once Excel files are</p>]]></description><link>https://blog.gyri.tech/the-secure-redirect-pattern-the-real-way-to-link-to-secure-documents-in-excel/</link><guid isPermaLink="false">69034b7a35fde4040e7a7ae4</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Thu, 30 Oct 2025 13:46:27 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1590859808308-3d2d9c515b1a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYiUyMGxpbmt8ZW58MHx8fHwxNzYxODMxOTMwfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="the-problem">The Problem</h2>
<img src="https://images.unsplash.com/photo-1590859808308-3d2d9c515b1a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYiUyMGxpbmt8ZW58MHx8fHwxNzYxODMxOTMwfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Securely Embedding Documents in Excel: The Short URL Pattern"><p>Enterprise applications that export Excel reports with embedded documents face a critical security challenge. The straightforward implementation exposes internal system identifiers:</p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p>This reveals:</p>
<ul>
<li>Internal document IDs (12345)</li>
<li>Tenant identifiers (67890)</li>
<li>API structure and endpoints</li>
</ul>
<p>Once Excel files are distributed, these identifiers persist indefinitely, creating ongoing security exposure even if the URLs require authentication.</p>
<p>The challenge intensifies when requirements include:</p>
<ul>
<li>Embedding images directly in Excel cells</li>
<li>Providing download links for full-resolution access</li>
<li>Supporting non-image documents (PDFs, DOCX) as clickable links</li>
</ul>
<h2 id="solution-short-url-redirection">Solution: Short URL Redirection</h2>
<p>Replace direct URLs with temporary, opaque redirect tokens:</p>
<p><strong>Before:</strong></p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p><strong>After:</strong></p>
<pre><code>https://app.example.com/api/shorturl/7k9x-m2p4
</code></pre>
<p>The short URL provides:</p>
<ul>
<li>Complete opacity (no exposed identifiers)</li>
<li>Automatic expiration (configurable timeframe)</li>
<li>Full auditability (access tracking)</li>
<li>Universal compatibility (images and documents)</li>
</ul>
<hr>
<h2 id="architecture">Architecture</h2>
<h3 id="phase-1-report-generation">Phase 1: Report Generation</h3>
<p><strong>Token Generation:</strong></p>
<p>For each document in the export:</p>
<ol>
<li>Generate cryptographically secure random token (e.g., <code>7k9x-m2p4</code>)</li>
<li>Store mapping in database:</li>
</ol>
<pre><code>short_code:    7k9x-m2p4
original_url:  /api/document/12345/view?tenantId=67890
expires_at:    2025-11-06 14:30:00 UTC
created_by:    user@example.com
</code></pre>
<p><strong>Content Handling:</strong></p>
<p><strong>Images:</strong></p>
<ul>
<li>Fetch image via secure internal URL (server-side)</li>
<li>Embed binary data directly into Excel cell</li>
<li>Attach short URL as clickable hyperlink</li>
<li>Result: Thumbnail preview with download link</li>
</ul>
<p><strong>Non-Images:</strong></p>
<ul>
<li>Insert short URL as clickable hyperlink only</li>
<li>Result: Text link for download/viewing</li>
</ul>
<p><strong>Excel Generation:</strong></p>
<p>Use server-side libraries (Apache POI, OpenPyXL, EPPlus) to:</p>
<ul>
<li>Embed image binary data in cells</li>
<li>Configure hyperlinks to short URLs</li>
<li>Ensure zero internal identifiers in output</li>
</ul>
<h3 id="phase-2-document-access">Phase 2: Document Access</h3>
<p><strong>User clicks link in Excel:</strong></p>
<ol>
<li>
<p><strong>Request</strong>: Browser opens <code>GET /api/shorturl/7k9x-m2p4</code></p>
</li>
<li>
<p><strong>Validation</strong>: Public endpoint checks:</p>
<ul>
<li>Token exists in database</li>
<li>Token not expired</li>
<li>Log access attempt</li>
</ul>
</li>
<li>
<p><strong>Redirect</strong>: Return HTTP 302:</p>
<pre><code>Location: https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
</li>
<li>
<p><strong>Authentication</strong>: Browser follows redirect with existing session, normal authentication applies</p>
</li>
</ol>
<p><strong>User experience:</strong> Click &#x2192; Document opens</p>
<p><strong>System process:</strong> Validate &#x2192; Redirect &#x2192; Authenticate &#x2192; Deliver</p>
<hr>
<h2 id="technical-implementation">Technical Implementation</h2>
<h3 id="database-schema">Database Schema</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>short_code</code></td>
<td>VARCHAR(32), PRIMARY KEY</td>
<td>Unique token identifier</td>
</tr>
<tr>
<td><code>original_url</code></td>
<td>TEXT</td>
<td>Target document URL</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>Expiration timestamp</td>
</tr>
<tr>
<td><code>created_by</code></td>
<td>VARCHAR(255)</td>
<td>Originating user</td>
</tr>
<tr>
<td><code>click_count</code></td>
<td>INTEGER</td>
<td>Access counter</td>
</tr>
</tbody>
</table>
<h3 id="security-configuration">Security Configuration</h3>
<p><strong>Redirect Endpoint:</strong></p>
<ul>
<li>Path: <code>/api/shorturl/**</code></li>
<li>Authentication: None (public access)</li>
<li>Rate limiting: Required (prevents enumeration)</li>
<li>Validation: Token existence and expiration only</li>
</ul>
<p><strong>Token Specification:</strong></p>
<ul>
<li>Length: Minimum 12 alphanumeric characters</li>
<li>Entropy: Cryptographically secure random generation</li>
<li>Character set: URL-safe (a-z, A-Z, 0-9)</li>
<li>Collision handling: Database unique constraint with retry</li>
</ul>
<h3 id="expiration-policy">Expiration Policy</h3>
<table>
<thead>
<tr>
<th>Use Case</th>
<th>Recommended Period</th>
</tr>
</thead>
<tbody>
<tr>
<td>Internal reports</td>
<td>7 days</td>
</tr>
<tr>
<td>External sharing</td>
<td>24-48 hours</td>
</tr>
<tr>
<td>Archive/compliance</td>
<td>30 days</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="security-benefits">Security Benefits</h2>
<h3 id="1-information-protection">1. Information Protection</h3>
<ul>
<li>Internal IDs completely hidden</li>
<li>API structure remains confidential</li>
<li>Zero architectural details exposed</li>
</ul>
<h3 id="2-temporal-controls">2. Temporal Controls</h3>
<ul>
<li>Automatic expiration reduces long-term risk</li>
<li>Distributed files lose access over time</li>
<li>No indefinite credential exposure</li>
</ul>
<h3 id="3-audit-capability">3. Audit Capability</h3>
<ul>
<li>Track every access attempt</li>
<li>Record timestamp, IP, user identity</li>
<li>Enable anomaly detection and compliance reporting</li>
</ul>
<h3 id="4-access-management">4. Access Management</h3>
<ul>
<li>Revoke tokens immediately if needed</li>
<li>Extend expiration programmatically</li>
<li>Tenant-level and user-level controls</li>
</ul>
<hr>
<h2 id="performance-considerations">Performance Considerations</h2>
<p><strong>Token Generation:</strong></p>
<ul>
<li>Batch create during report generation</li>
<li>~1ms per token generation</li>
<li>Minimal database overhead</li>
</ul>
<p><strong>Lookup Performance:</strong></p>
<ul>
<li>O(1) with proper indexing</li>
<li>Cache active tokens (optional)</li>
<li>Typical response time: &lt;10ms</li>
</ul>
<p><strong>Cleanup:</strong></p>
<ul>
<li>Schedule periodic deletion of expired tokens</li>
<li>Run during off-peak hours</li>
<li>Archive audit data before deletion</li>
</ul>
<p><strong>Image Processing:</strong></p>
<ul>
<li>Server-side fetch and embed: ~50-100ms per image</li>
<li>Memory efficient with streaming</li>
<li>No browser limitations</li>
</ul>
<hr>
<h2 id="example-workflow">Example Workflow</h2>
<p><strong>Scenario:</strong> Report with 30 images, 20 PDFs</p>
<p><strong>Generation:</strong></p>
<ol>
<li>Generate 50 short URLs: ~50ms</li>
<li>Fetch and embed 30 images: ~2-3 seconds</li>
<li>Build Excel file: ~500ms</li>
<li>Total: ~3-4 seconds</li>
</ol>
<p><strong>Usage:</strong></p>
<ul>
<li>5 users, 7-day period</li>
<li>150 total accesses across 50 documents</li>
<li>Complete audit trail maintained</li>
<li>Zero ID exposure</li>
<li>Automatic expiration after policy period</li>
</ul>
<hr>
<h2 id="implementation-checklist">Implementation Checklist</h2>
<p><strong>Required Components:</strong></p>
<ul>
<li>Short URL mapping database table with proper indexes</li>
<li>Token generation service (cryptographically secure)</li>
<li>Public redirect endpoint (unauthenticated)</li>
<li>Token validation logic (existence + expiration)</li>
<li>Audit logging mechanism</li>
<li>Scheduled cleanup job for expired tokens</li>
</ul>
<p><strong>Security Requirements:</strong></p>
<ul>
<li>Rate limiting on redirect endpoint</li>
<li>Cryptographic random token generation</li>
<li>Proper expiration policy configuration</li>
<li>HTTP 302 redirect (not 301 permanent)</li>
<li>Error handling (404 for invalid/expired tokens)</li>
</ul>
<p><strong>Server-Side Excel Library:</strong></p>
<ul>
<li>Java: Apache POI</li>
<li>Python: OpenPyXL</li>
<li>.NET: EPPlus</li>
</ul>
<p><strong>Why Server-Side:</strong><br>
Browser-based libraries (xlsx-js-style, etc.) cannot embed images in Excel cells. Server-side libraries provide full binary embedding capability.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>The short URL redirection pattern solves document embedding security through architectural indirection. By replacing direct URLs with temporary tokens, systems achieve:</p>
<ul>
<li><strong>Complete identifier masking</strong> (no IDs exposed)</li>
<li><strong>Automatic expiration</strong> (temporal security)</li>
<li><strong>Full auditability</strong> (compliance ready)</li>
<li><strong>Seamless UX</strong> (transparent to users)</li>
</ul>
<p>This approach is applicable beyond Excel exports&#x2014;anywhere temporary, auditable access to secure resources is required without exposing internal architecture.</p>
<p><strong>Key Takeaway:</strong> Never expose internal identifiers in exported files. Use opaque, expiring tokens as an indirection layer between distributed content and secure resources.</p>
<hr>
<p><strong>Technical Stack Considerations:</strong></p>
<p>This pattern integrates with existing authentication infrastructure. The redirect endpoint bypasses authentication (public), while the target URL enforces normal security. Token validation provides the security boundary, relying on cryptographic randomness and expiration rather than session state.</p>
<p><strong>Broader Applications:</strong> PDF reports, email notifications, third-party integrations, mobile apps, automated distribution systems&#x2014;any context requiring temporary resource access without persistent authentication.</p>
]]></content:encoded></item><item><title><![CDATA[MongoDB Commands]]></title><description><![CDATA[<h2 id="commands">Commands</h2><ul><li><code>mongod</code> - start mongo db server instance</li><li><code>mongo</code> - start mongo db command prompt to execute commands</li><li><code>show dbs</code> - show databases</li><li><code>mongoimport -d [DATABASE_NAME] -c [COLLECTIONS] --file [FILE_NAME]</code> - import into database and collection from file</li><li><code>mongoexport -d [DATABASE_NEM] -c [COLLECTIONS] --out [FILE_NAME]</code> -</li></ul>]]></description><link>https://blog.gyri.tech/mongodb-commands/</link><guid isPermaLink="false">68ec6c3bd1e62b04108b4ceb</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Mon, 13 Oct 2025 03:06:19 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1658204238967-3a81a063d162?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fG1vbmdvZGJ8ZW58MHx8fHwxNzYwMzI0NzE2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="commands">Commands</h2><ul><li><code>mongod</code> - start mongo db server instance</li><li><code>mongo</code> - start mongo db command prompt to execute commands</li><li><code>show dbs</code> - show databases</li><li><code>mongoimport -d [DATABASE_NAME] -c [COLLECTIONS] --file [FILE_NAME]</code> - import into database and collection from file</li><li><code>mongoexport -d [DATABASE_NEM] -c [COLLECTIONS] --out [FILE_NAME]</code> - export db in json</li><li><code>mongodump</code> - binary export</li><li><code>mongorestore</code> - restore binary export</li><li><code>bsondump</code></li><li><code>mongostat</code></li><li><code>use [DB_NAME]</code>- change/switch DB. There is NO command to create DB</li><li><code>db</code> - set variable as DB (after <em>user [DB_NAME]</em>)</li><li><code>db.[COLLECTIONS]</code> - e.g. db.links. Don&apos;t have to create collections (similar to DB)</li><li><code>db.[COLLECTIONS].count()</code> - to check number of documents. e.g. db.links.count()</li><li><code>db.[COLLECTIONS].insert({[JSON_DATA]})</code> - e.g. db.links.insert({title: &quot;google search&quot;, url:&quot;www.google.com&quot;, comment:&quot;top search engine&quot;, tags:[&quot;default&quot;,&quot;main page&quot;], saved_on: new Date()})</li><li><code>db.[COLLECTIONS].save({[JSON_DATA]})</code> - e.g. db.links.save({title: &quot;yahoo search&quot;, url: &quot;www.yahoo.com&quot;, saved_on: new Date()})</li><li><code>db.[COLLECTIONS].find()</code> - e.g. db.links.find(). Query the database to find the documents</li></ul><h3 id="examples">Examples</h3><ul><li><code>show collections</code> - show collections for db</li><li><code>db.users.insert({&quot;name&quot;:&quot;username&quot;})</code> - insert collection user with data</li><li><code>db.users.count()</code> - show count of collections</li><li><code>db.users.find()</code> - find all data from collection</li><li><code>db.users.find().explain()</code> - explain execution planner for query to find data</li><li><code>db.users.find().explain(&quot;executionStats&quot;)</code> - more information on explain</li><li><code>db.users.createIndex({number:1})</code> - to create an index on collection</li><li><code>db.users.update({&quot;name&quot;:&quot;username&quot;}, $set: {&quot;first_name&quot;:&quot;Name&quot;})&quot;</code> - <em>$set</em> update record</li><li><code>db.users.update({&quot;name&quot;:&quot;username&quot;}, {$addToSet: {&quot;hobbies&quot;:&quot;running&quot;}})&quot;</code> - <em>$addToSet</em> update array with new value</li><li><code>db.users.remove({&quot;name&quot;:&quot;username&quot;})</code> - remove record</li><li><code>db.users.drop()</code> - drop entire <em>users</em> collection</li></ul><h3 id="notes">Notes</h3><ul><li><strong>bson</strong>: binary JSON</li><li><strong>Collection</strong>: Like an RDBMS table; but collection has no schemas.</li><li><strong>Document</strong>: Like an RDBMS record or row. There are bson documents.</li><li><strong>Field</strong>: Like an RDBMS column; {key: value}</li></ul><hr>]]></content:encoded></item></channel></rss>