<?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>Sat, 29 Aug 2026 10:55:30 GMT</lastBuildDate><atom:link href="https://blog.gyri.tech/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Automating a Scalable Command, Query and Event Package Structure with a Batch Script]]></title><description><![CDATA[<p></p><h2 id="introduction">Introduction</h2><p>As backend applications grow, maintaining a consistent project structure becomes increasingly important.</p><p>In a small application, developers can manually create packages and folders whenever a new business entity or feature is introduced. However, in a larger application following architectural patterns such as <strong>CQRS, Domain-Driven Design, and event-driven architecture</strong>, every</p>]]></description><link>https://blog.gyri.tech/automating-a-scalable-command-query-and-event-package-structure-with-a-windows-batch-script/</link><guid isPermaLink="false">6a8c2177a59c17040f4ea799</guid><dc:creator><![CDATA[Sangram Ekshinge]]></dc:creator><pubDate>Mon, 24 Aug 2026 12:58:30 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/08/Automating-batch-script-image.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/08/Automating-batch-script-image.png" alt="Automating a Scalable Command, Query and Event Package Structure with a Batch Script"><p></p><h2 id="introduction">Introduction</h2><p>As backend applications grow, maintaining a consistent project structure becomes increasingly important.</p><p>In a small application, developers can manually create packages and folders whenever a new business entity or feature is introduced. However, in a larger application following architectural patterns such as <strong>CQRS, Domain-Driven Design, and event-driven architecture</strong>, every new feature may require a considerable number of packages. Manually creating these directories can become repetitive, error-prone, and time-consuming. To solve this problem, a reusable <strong>Batch script</strong> can be introduced to automatically generate the required package and directory structure for a new domain entity.</p><p>The script accepts two parameters:</p><ul><li>A base package name</li><li>An entity or feature name</li></ul><p>Based on these inputs, it automatically creates the required <strong>Command, Query, and Event</strong> directory structures.</p><p>This approach provides a simple form of project scaffolding that improves development speed while enforcing architectural consistency across the application.</p><hr><h1 id="why-automate-project-structure-creation">Why Automate Project Structure Creation?</h1><p>Consider a project following a CQRS-based architecture.</p><p>Whenever a new business entity is introduced, developers may need to create directories for:</p><ul><li>Commands</li><li>Controllers</li><li>DTOs</li><li>Entities</li><li>Exception handlers</li><li>Infrastructure</li><li>Repositories</li><li>Queries</li><li>Query handlers</li><li>Query controllers</li><li>Event consumers</li><li>Event handlers</li><li>Shared events</li><li>Notifications</li></ul><p>Creating these directories manually for every entity has several disadvantages.</p><h3 id="1-repetitive-work">1. Repetitive Work</h3><p>Developers repeatedly perform the same directory-creation steps.</p><h3 id="2-human-error">2. Human Error</h3><p>It is easy to:</p><ul><li>Miss a directory</li><li>Create a directory in the wrong location</li><li>Use an inconsistent naming convention</li><li>Create an incorrect package hierarchy</li></ul><h3 id="3-architectural-inconsistency">3. Architectural Inconsistency</h3><p>Different developers may create different structures for different features. Over time, this can make the codebase difficult to navigate.</p><h3 id="4-slower-feature-development">4. Slower Feature Development</h3><p>Although creating directories takes only a few minutes, the accumulated overhead becomes significant when many entities are introduced.</p><h3 id="5-difficult-onboarding">5. Difficult Onboarding</h3><p>New developers need to understand where each component belongs before they can start implementing a feature. Automation reduces this friction.</p><hr><h1 id="what-does-the-script-solve">What Does the Script Solve?</h1><p>The batch script acts as a lightweight <strong>project scaffolding tool</strong>.</p><p>Instead of manually creating the complete structure, a developer provides the required package and entity information. The script then generates the predefined directory hierarchy automatically.</p><p>Conceptually, the workflow is:</p><p><strong>Developer Input</strong></p><p>&#x2192; Base Package</p><p>&#x2192; Entity Name</p><p>&#x2192; Script Processing</p><p>&#x2192; Command Structure</p><p>&#x2192; Query Structure</p><p>&#x2192; Event Structure</p><p>The important point is that the script does not generate business logic.</p><p>It generates the <strong>structural foundation</strong> on which the developer can build the feature.</p><hr><h1 id="high-level-architecture">High-Level Architecture</h1><p>The generated structure is divided into three major areas:</p><ol><li>Command</li><li>Query</li><li>Shared Events</li></ol><p>This separation reflects the responsibilities commonly associated with CQRS and event-driven architectures.</p><p>A simplified conceptual structure looks like this:</p><pre><code>Base Package
&#x2502;
&#x251C;&#x2500;&#x2500; command
&#x2502;   &#x2514;&#x2500;&#x2500; Entity
&#x2502;       &#x251C;&#x2500;&#x2500; api
&#x2502;       &#x251C;&#x2500;&#x2500; dto
&#x2502;       &#x251C;&#x2500;&#x2500; entity
&#x2502;       &#x251C;&#x2500;&#x2500; exceptions
&#x2502;       &#x251C;&#x2500;&#x2500; infrastructure
&#x2502;       &#x2514;&#x2500;&#x2500; repository
&#x2502;
&#x251C;&#x2500;&#x2500; query
&#x2502;   &#x2514;&#x2500;&#x2500; Entity
&#x2502;       &#x251C;&#x2500;&#x2500; api
&#x2502;       &#x251C;&#x2500;&#x2500; dto
&#x2502;       &#x251C;&#x2500;&#x2500; entity
&#x2502;       &#x251C;&#x2500;&#x2500; exceptions
&#x2502;       &#x251C;&#x2500;&#x2500; infrastructure
&#x2502;       &#x2514;&#x2500;&#x2500; repository
&#x2502;
&#x2514;&#x2500;&#x2500; shared
    &#x2514;&#x2500;&#x2500; Entity
        &#x251C;&#x2500;&#x2500; events
        &#x2514;&#x2500;&#x2500; notification
            &#x2514;&#x2500;&#x2500; events</code></pre><p>The exact implementation can evolve as the architecture grows, but the main principle remains the same:</p><blockquote><strong>Create a predictable structure automatically so developers can focus on implementing business functionality rather than repeatedly creating folders.</strong></blockquote><hr><h1 id="understanding-the-script">Understanding the Script</h1><p>The script can be divided into several logical stages.</p><h2 id="1-parameter-validation">1. Parameter Validation</h2><p>The first responsibility of the script is validating the input.</p><p>It expects two parameters:</p><h3 id="package-name">Package Name</h3><p>This represents the base package where the new feature should be created. </p><p>For example, conceptually:<strong> com.company.application</strong></p><h3 id="entity-name">Entity Name</h3><p>This represents the business entity or feature for which the structure should be generated.</p><p>For example:<strong> Employee</strong></p><p>The script checks whether both parameters have been supplied. If the package name is missing, the script stops and displays an error. The same happens if the entity name is missing. This is important because generating a directory structure without the required information could result in an incomplete or incorrect project structure.</p><hr><h1 id="2-converting-package-names-into-directory-paths">2. Converting Package Names into Directory Paths</h1><p>Programming languages such as Java and Kotlin commonly represent packages using dot notation.</p><p>For example:<strong> com.company.application</strong></p><p>However, the operating system represents directories using path separators. </p><p>The script therefore converts the package notation into a directory-compatible path.</p><p>Conceptually:<strong> com.company.application</strong></p><p>becomes:<strong> com\company\application</strong></p><p>This allows the script to map a logical package name directly to the corresponding filesystem structure. This small transformation is important because it makes the script reusable for different package hierarchies.</p><hr><h1 id="3-creating-the-command-structure">3. Creating the Command Structure</h1><p>The first major structure generated by the script is the Command side. The Command side represents operations that generally <strong>change application state</strong>.</p><p>The generated structure provides dedicated locations for areas such as:</p><h3 id="api-commands">API Commands</h3><p>This area can contain command definitions representing business operations.</p><p>Examples conceptually include:</p><ul><li>Create </li><li>Update </li><li>Delete </li></ul><h3 id="api-controllers">API Controllers</h3><p>This area can contain controllers responsible for receiving client requests and initiating commands.</p><h3 id="dto">DTO</h3><p>The DTO package provides a dedicated location for request and response-related data structures.</p><h3 id="entity">Entity</h3><p>This area contains the domain or persistence-related entity representation associated with the feature.</p><h3 id="exception-handlers">Exception Handlers</h3><p>Feature-specific exception handling can be placed here. This prevents business-specific error handling from becoming scattered throughout the project.</p><h3 id="infrastructure">Infrastructure</h3><p>Infrastructure-related implementations associated with the command side can be placed here.</p><h3 id="jpa-repository">JPA Repository</h3><p>Persistence interfaces and implementations related to command-side operations can be maintained here.</p><hr><h1 id="4-creating-the-query-structure">4. Creating the Query Structure</h1><p>The second major structure is the Query side.</p><p>Queries are generally responsible for <strong>retrieving data without changing application state</strong>. The script creates separate locations for query-related responsibilities.</p><h2 id="query-handlers">Query Handlers</h2><p>Query handlers process query requests and coordinate the retrieval of required information. Keeping handlers in a dedicated package makes the read side easier to locate and maintain.</p><h2 id="query-controllers">Query Controllers</h2><p>Controllers expose query functionality through application APIs.</p><h2 id="dto-1">DTO</h2><p>The Query DTO package can contain structures specifically designed for read operations.</p><h2 id="entity-and-enums">Entity and Enums</h2><p>The entity area can contain read-side models, projections, or other representations required by the query layer. The dedicated enum package provides a clear location for feature-specific enumerations.</p><h2 id="exception-handlers-1">Exception Handlers</h2><p>Query-specific exceptions can be handled separately from command-side exceptions.</p><h2 id="notification-consumers">Notification Consumers</h2><p>The notification consumer area provides a place for components that consume notification-related messages.</p><h2 id="notification-handlers">Notification Handlers</h2><p>Notification handling logic can be isolated from the main query processing logic.</p><h2 id="jpa-repository-1">JPA Repository</h2><p>Repositories that use traditional JPA-based persistence can be organized here.</p><h2 id="reactive-repository">Reactive Repository</h2><p>Applications that use reactive data access can maintain reactive repositories separately. This distinction becomes particularly valuable when an application supports both traditional and reactive data-access patterns.</p><hr><h1 id="5-creating-the-event-structure">5. Creating the Event Structure</h1><p>The third area created by the script is the shared event structure. Events are useful in event-driven architectures because they allow one part of an application to communicate that something has happened without requiring direct coupling with every consumer. The generated structure provides two major locations.</p><h2 id="domain-or-feature-events">Domain or Feature Events</h2><p>The events directory provides a centralized location for events associated with the entity.</p><p>Examples conceptually include:</p><ul><li>EntityCreated</li><li>EntityUpdated</li><li>EntityDeleted</li></ul><p>The actual events depend entirely on the application&apos;s business requirements.</p><h2 id="notification-events">Notification Events</h2><p>The notification-events area provides a dedicated location for events related to notifications or communication workflows.</p><p>This separation makes it easier to distinguish between:</p><p><strong>Business events</strong> and<strong> Notification-related events</strong></p><hr><h1 id="why-command-query-and-event-separation-matters">Why Command, Query and Event Separation Matters</h1><p>The most important architectural benefit of this structure is <strong>separation of responsibilities</strong>. Instead of placing every class related to an entity into a single package, the application separates components based on their purpose.</p><p>Conceptually:</p><h3 id="command">Command</h3><p><strong>Change something</strong></p><h3 id="query">Query</h3><p><strong>Read something</strong></p><h3 id="event">Event</h3><p><strong>Communicate that something happened</strong></p><p>This makes the architecture easier to understand and helps prevent unrelated responsibilities from becoming tightly coupled.</p><hr><h1 id="how-to-use-the-script">How to Use the Script</h1><p>The script is designed to be parameter-driven.</p><p>A developer provides:</p><p><strong>Parameter 1:</strong> Base package</p><p><strong>Parameter 2:</strong> Entity name</p><p>The script uses those values to construct the complete directory structure.</p><p>A typical workflow is:</p><h3 id="step-1-%E2%80%94-open-the-project-directory">Step 1 &#x2014; Open the Project Directory</h3><p>The script should be executed from the appropriate project root.</p><p>This ensures that the generated directories appear in the expected source location.</p><h3 id="step-2-%E2%80%94-provide-the-base-package">Step 2 &#x2014; Provide the Base Package</h3><p>The developer provides the application&apos;s base package.</p><h3 id="step-3-%E2%80%94-provide-the-entity-name">Step 3 &#x2014; Provide the Entity Name</h3><p>The developer provides the name of the new entity or feature.</p><h3 id="step-4-%E2%80%94-execute-the-script">Step 4 &#x2014; Execute the Script</h3><p>The script validates the parameters and begins generating directories.</p><h3 id="step-5-%E2%80%94-verify-the-generated-structure">Step 5 &#x2014; Verify the Generated Structure</h3><p>After execution, the developer should verify that:</p><ul><li>The package path is correct</li><li>The entity name is correct</li><li>Command directories exist</li><li>Query directories exist</li><li>Event directories exist</li></ul><h3 id="step-6-%E2%80%94-start-implementation">Step 6 &#x2014; Start Implementation</h3><p>Once the structure is created, developers can begin implementing:</p><ul><li>Commands</li><li>Command handlers</li><li>Queries</li><li>Query handlers</li><li>Controllers</li><li>DTOs</li><li>Entities</li><li>Repositories</li><li>Events</li><li>Consumers</li><li>Exception handling</li></ul><p>The script therefore becomes the <strong>starting point of the feature-development workflow</strong>.</p><hr><h1 id="what-happens-when-an-entity-name-is-provided">What Happens When an Entity Name Is Provided?</h1><p>Suppose a developer wants to introduce a new business entity.</p><p>The developer provides:</p><p><strong>Base Package And Entity Name</strong></p><p>The script combines these values with the predefined architectural paths. The result is a complete feature-specific structure. This is important because the developer does not need to remember every directory required by the architecture. The architecture is effectively encoded into the scaffolding process.</p><hr><h1 id="benefits-of-this-approach">Benefits of This Approach</h1><h2 id="1-consistency">1. Consistency</h2><p>Every feature starts with the same architectural structure. This makes the codebase predictable. A developer familiar with one feature can quickly understand another feature because the package organization follows the same pattern.</p><hr><h2 id="2-faster-development">2. Faster Development</h2><p>Developers no longer need to manually create dozens of directories. The initial setup becomes almost instantaneous.</p><hr><h2 id="3-reduced-human-error">3. Reduced Human Error</h2><p>The script eliminates many common structural mistakes. Developers don&apos;t have to remember:</p><ul><li>Which directories are required</li><li>Where repositories belong</li><li>Where event classes belong</li><li>Where notification handlers belong</li></ul><hr><h2 id="4-easier-code-navigation">4. Easier Code Navigation</h2><p>A predictable structure makes it easier to locate components. For example, when investigating a query-related issue, developers know where query handlers and repositories should exist. Similarly, when investigating an event-related issue, developers know where to look for event definitions and notification events.</p><hr><h2 id="5-easier-onboarding">5. Easier Onboarding</h2><p>New developers can learn one standard feature structure and apply that knowledge across the application. The directory structure becomes part of the team&apos;s architectural documentation.</p><hr><h2 id="6-architectural-enforcement">6. Architectural Enforcement</h2><p>Although a batch script cannot enforce every architectural rule, it establishes a strong starting point. Instead of architecture being only documented in a wiki or design document, part of the architecture is embedded directly into the development workflow.</p><hr><h1 id="the-script-as-a-lightweight-scaffolding-tool">The Script as a Lightweight Scaffolding Tool</h1><p>The script can be considered a simple form of <strong>code scaffolding</strong>. Scaffolding means automatically generating the initial structure required to implement something. Many modern development ecosystems provide sophisticated scaffolding tools. However, not every project needs a complex framework or generator.</p><p>A small batch script can be extremely effective when:</p><ul><li>The architecture is stable</li><li>The directory structure is predictable</li><li>Features repeatedly require the same structure</li></ul><p>In such scenarios, simplicity can be an advantage.</p><hr><h1 id="what-the-script-does-not-do">What the Script Does Not Do</h1><p>It is equally important to understand the limitations. The script creates directories, but it does not implement functionality.</p><p>It does not automatically create:</p><ul><li>Business logic</li><li>Domain rules</li><li>Commands</li><li>Query handlers</li><li>Controllers</li><li>Repository implementations</li><li>Event definitions</li><li>Database schemas</li><li>API documentation</li><li>Unit tests</li><li>Integration tests</li></ul><p>Therefore, it should be viewed as a <strong>structural generator</strong>, not a complete feature generator.</p><hr><h1 id="recommended-development-workflow">Recommended Development Workflow</h1><p>A good workflow can look like this:</p><p><strong>1. Identify the business feature</strong></p><p>&#x2193;</p><p><strong>2. Define the entity</strong></p><p>&#x2193;</p><p><strong>3. Run the scaffolding script</strong></p><p>&#x2193;</p><p><strong>4. Verify the generated structure</strong></p><p>&#x2193;</p><p><strong>5. Create domain entities</strong></p><p>&#x2193;</p><p><strong>6. Define commands</strong></p><p>&#x2193;</p><p><strong>7. Implement command handling</strong></p><p>&#x2193;</p><p><strong>8. Define queries</strong></p><p>&#x2193;</p><p><strong>9. Implement query handling</strong></p><p>&#x2193;</p><p><strong>10. Add repositories</strong></p><p>&#x2193;</p><p><strong>11. Define required events</strong></p><p>&#x2193;</p><p><strong>12. Implement consumers and notification handlers</strong></p><p>&#x2193;</p><p><strong>13. Add exception handling</strong></p><p>&#x2193;</p><p><strong>14. Build and validate the application</strong></p><p>This creates a repeatable development process for every new feature.</p><hr><h1 id="naming-conventions">Naming Conventions</h1><p>Automation works best when naming conventions are clearly defined. The entity name should follow the project&apos;s established naming standards.</p><p>For example, teams should decide whether entity names should be:</p><ul><li>Singular or plural</li><li>PascalCase</li><li>camelCase</li><li>Domain-oriented</li><li>Feature-oriented</li></ul><p>Consistency is particularly important because the entity name becomes part of the generated package hierarchy. A poor naming convention in the input can propagate throughout the generated structure.</p><hr><h1 id="important-considerations">Important Considerations</h1><h2 id="project-root">Project Root</h2><p>The script assumes that it is executed from the expected project location. Running it from an incorrect directory could create the generated structure in the wrong place. Therefore, developers should verify the current working directory before execution.</p><hr><h2 id="existing-directories">Existing Directories</h2><p>The script creates directories that may already exist. In most development environments, this is generally harmless, but teams should still understand how the underlying directory commands behave when the target structure already exists.</p><hr><h2 id="naming-validation">Naming Validation</h2><p>The script validates that parameters are present, but that does not necessarily mean that the values are semantically valid. For example, a malformed package name or an entity name containing unexpected characters could produce an undesirable structure. A future version could add stricter validation.</p><hr><h1 id="when-should-you-use-this-approach">When Should You Use This Approach?</h1><p>This approach is especially useful when:</p><ul><li>The project has a well-defined architecture</li><li>New entities are added frequently</li><li>Every entity follows a similar structure</li><li>Multiple developers work on the same codebase</li><li>Consistency is important</li><li>Manual folder creation has become repetitive</li></ul><p>It may be unnecessary for very small applications where the project structure is simple.</p><hr><h1 id="when-should-you-consider-a-more-advanced-generator">When Should You Consider a More Advanced Generator?</h1><p>A batch script is excellent for simple structural generation.</p><p>However, if the project requires extensive automation, consider moving toward a dedicated generator when you need:</p><ul><li>Source-code templates</li><li>Interactive configuration</li><li>Cross-platform support</li><li>Advanced validation</li><li>Automatic test generation</li><li>Database migration generation</li><li>API generation</li><li>IDE integration</li><li>Multiple architecture templates</li></ul><p>At that point, a dedicated CLI or build-tool-based generator may provide more long-term value.</p><hr><h1 id="architectural-value-beyond-folder-creation">Architectural Value Beyond Folder Creation</h1><p>At first glance, the script may appear to be nothing more than a collection of directory-creation commands. Its real value, however, is architectural. The script captures the team&apos;s expectations about how a new feature should be organized. Without automation, developers need to remember the architecture. With automation, the architecture becomes part of the workflow.</p><p>This creates an important principle:</p><blockquote><strong>Development tooling can act as an executable form of architectural documentation.</strong></blockquote><p>When a developer creates a new feature, the generator immediately demonstrates where different responsibilities belong.</p><hr><h1 id="team-level-benefits">Team-Level Benefits</h1><p>For a development team, standardized scaffolding can have an even greater impact.</p><h3 id="consistent-code-reviews">Consistent Code Reviews</h3><p>Reviewers can focus more on business logic because structural inconsistencies are reduced.</p><h3 id="faster-feature-setup">Faster Feature Setup</h3><p>Developers can start implementing functionality immediately.</p><h3 id="easier-maintenance">Easier Maintenance</h3><p>Future developers can predict where components should be located.</p><h3 id="reduced-knowledge-dependency">Reduced Knowledge Dependency</h3><p>The team becomes less dependent on individual developers remembering the complete package structure.</p><h3 id="better-architectural-discipline">Better Architectural Discipline</h3><p>The standard structure encourages developers to keep commands, queries, and events separated.</p><hr><h1 id="best-practices">Best Practices</h1><p>To get the most value from this approach, teams should follow a few practices.</p><h3 id="keep-the-generator-simple">Keep the Generator Simple</h3><p>A scaffolding tool should reduce complexity, not introduce more complexity.</p><h3 id="keep-the-generated-structure-relevant">Keep the Generated Structure Relevant</h3><p>Every generated directory should have a clear purpose. Avoid creating directories that are never used.</p><h3 id="document-the-conventions">Document the Conventions</h3><p>Developers should understand why the directories exist, not just where they are located.</p><h3 id="review-the-structure-periodically">Review the Structure Periodically</h3><p>As the architecture evolves, the generator should evolve with it.</p><hr><h1 id="a-practical-mental-model">A Practical Mental Model</h1><p>The easiest way to understand this solution is to think of it as a <strong>feature template</strong>. Every new feature starts from the same foundation.</p><p>The script essentially says:</p><p><strong>&quot;Whenever a new entity is introduced, this is the standard architectural structure it should begin with.&quot;</strong></p><p>The developer then fills that structure with the actual implementation.</p><p>This creates a clear separation between:</p><p><strong>Scaffolding</strong> and<strong> Implementation</strong></p><p>The script handles the first part. The developer handles the second.</p><hr><h1 id="conclusion">Conclusion</h1><p>A simple batch script can provide significant value when used to automate a consistent Command, Query and Event structure. Its primary purpose is not to generate business logic. Instead, it removes repetitive structural work and establishes a standardized foundation for feature development.</p><p>The approach provides several benefits:</p><ul><li>Faster feature initialization</li><li>Consistent package organization</li><li>Reduced structural errors</li><li>Easier navigation</li><li>Better onboarding</li><li>Improved architectural consistency</li><li>Repeatable development workflows</li></ul><p>More importantly, it demonstrates a broader engineering principle:</p><blockquote><strong>Whenever developers repeatedly perform the same structural task, that process is a candidate for automation.</strong></blockquote><p>A small amount of automation at the beginning of the development lifecycle can save considerable time over the lifetime of a project. As the system grows, the same idea can evolve from a simple directory generator into a complete scaffolding solution capable of generating source files, tests, configurations, and other feature components. The current approach is therefore a practical first step toward <strong>standardized, repeatable, and architecture-aware feature development</strong>.</p>]]></content:encoded></item><item><title><![CDATA[What Does Good UI/UX Actually Mean?]]></title><description><![CDATA[<p>We spend hours using digital products every day. We order food, book tickets, make payments, shop online, check emails, manage work and scroll through more websites than we probably remember.</p><p>Some of those experiences just work.<br>You open the app, find what you need, get it done and move on.</p>]]></description><link>https://blog.gyri.tech/what-does-good-ui-ux-actually-mean/</link><guid isPermaLink="false">6a7b0a3ea59c17040f4ea54e</guid><dc:creator><![CDATA[Tejas Kiran Patil]]></dc:creator><pubDate>Tue, 18 Aug 2026 13:18:23 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/08/feature-image.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/08/feature-image.jpg" alt="What Does Good UI/UX Actually Mean?"><p>We spend hours using digital products every day. We order food, book tickets, make payments, shop online, check emails, manage work and scroll through more websites than we probably remember.</p><p>Some of those experiences just work.<br>You open the app, find what you need, get it done and move on. Others somehow turn a simple task into a five minute struggle. You can&apos;t find the button you&apos;re looking for. </p><p>The navigation doesn&apos;t make sense. A form asks for information that feels completely unnecessary. You click something and aren&apos;t sure whether anything happened. That&apos;s usually when we start noticing the design.</p><p><strong>That&apos;s an interesting thing about UI/UX. When it&apos;s done well, you often don&apos;t notice it at all. You just use the product.</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/01.jpg" class="kg-image" alt="What Does Good UI/UX Actually Mean?" loading="lazy" width="2000" height="1126" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/01.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/01.jpg 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/01.jpg 1600w, https://blog.gyri.tech/content/images/2026/08/01.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><h2 id="so-ui-and-ux-whats-the-difference">So, UI and UX... what&apos;s the difference?</h2><p>These two terms get used together so often that they&apos;ve almost become one phrase.<br>But they aren&apos;t the same thing.</p><blockquote><strong>UI, User Interface</strong>, is the part you can see and interact with. Buttons, typography, colors, icons, cards, forms, menus, spacing all of that falls under UI.</blockquote><blockquote><strong>UX, User Experience</strong>, is a little broader. It&apos;s about what happens while you&apos;re actually trying to use the product.</blockquote><p>Here&apos;s an everyday example. Say you&apos;re booking an appointment with a doctor online. The color of the Book Appointment button, its size, and where it sits on the page are all UI decisions.</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/appointment-booking.jpg" class="kg-image" alt="What Does Good UI/UX Actually Mean?" loading="lazy" width="2000" height="1000" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/appointment-booking.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/appointment-booking.jpg 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/appointment-booking.jpg 1600w, https://blog.gyri.tech/content/images/2026/08/appointment-booking.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><h3 id="but-think-about-everything-you-need-to-do-around-that-button">But think about everything you need to do around that button:</h3><p>Can you easily find the right doctor?</p><p>Can you see which time slots are available?</p><p>Do you need to create an account before booking?</p><p>What happens after you confirm?</p><p>Do you receive a confirmation?</p><p>That&apos;s the experience. You can have a beautiful interface and still have a terrible booking process. The opposite is also true. Something can technically work but still feel outdated, inconsistent, or difficult to use.</p><p>The best products get both sides right.</p><h3 id="you-shouldnt-have-to-figure-out-how-a-website-works">You Shouldn&apos;t Have to Figure Out How a Website Works</h3><p>Open a website you&apos;ve never visited before. Within a few seconds, you should have a rough idea of where you are and what you can do. Nobody should need to explain: &quot;Okay, first click this. Then open this menu. Ignore that button. Now scroll down.&quot;</p><p>Yet we&apos;ve all used products that feel exactly like that. Sometimes the problem is surprisingly small.</p><p>Take a button that says: <strong>Submit</strong>. Submit what?</p><p>Compare that with: <strong>Book My Appointment</strong>.</p><p>Now you know exactly what&apos;s about to happen. It&apos;s a tiny change, but it removes one small moment of uncertainty. Those small moments add up.</p><h3 id="not-everything-on-the-screen-can-be-important"><strong>Not Everything on the Screen Can Be Important</strong></h3><p>This is something we see quite often when designing websites. Everyone involved in the project has something they want users to notice:</p><ul><li>The marketing team wants the offer to stand out.</li><li>The sales team wants Book a Demo to stand out.</li><li>Someone wants the new feature promoted.</li><li>Someone else wants the newsletter highlighted.</li><li>Then there&apos;s the chatbot.</li></ul><p>And suddenly, the homepage has six things shouting for attention. The problem isn&apos;t that any one of those things is bad. The problem is that users don&apos;t know where to look.</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/bad-ui-vs-good-ui.jpg" class="kg-image" alt="What Does Good UI/UX Actually Mean?" loading="lazy" width="2000" height="1333" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/bad-ui-vs-good-ui.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/bad-ui-vs-good-ui.jpg 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/bad-ui-vs-good-ui.jpg 1600w, https://blog.gyri.tech/content/images/2026/08/bad-ui-vs-good-ui.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><p>A strong interface makes choices on behalf of the user:</p><ul><li>This is the main message.</li><li>This is the primary action.</li><li>This information is useful, but it can come later.</li></ul><p>That hierarchy is what makes a page feel calm and easy to scan. You don&apos;t necessarily need less content. You need clearer priorities.</p><h3 id="every-extra-step-has-a-cost">Every Extra Step Has a Cost</h3><p>Let&apos;s say you&apos;ve downloaded a new app because you want to try one specific feature. </p><p><strong>You open it. </strong></p><p><strong>Create an account. </strong></p><p><strong>Verify your email. </strong></p><p><strong>Come back. </strong></p><p><strong>Complete your profile. </strong></p><p><strong>Choose your interests. </strong></p><p><strong>Set your preferences. </strong></p><p><strong>Choose a plan. </strong></p><p><strong>And finally you reach the feature you originally came for.</strong></p><p>By that point, some people have already left. This is where UX goes far beyond colors and buttons. </p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/bad-ux-vs-good-ux.jpg" class="kg-image" alt="What Does Good UI/UX Actually Mean?" loading="lazy" width="2000" height="1333" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/bad-ux-vs-good-ux.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/bad-ux-vs-good-ux.jpg 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/bad-ux-vs-good-ux.jpg 1600w, https://blog.gyri.tech/content/images/2026/08/bad-ux-vs-good-ux.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><p>Every screen, click, and decision between the user and their goal creates some amount of friction.</p><p>Sometimes it&apos;s necessary. A banking product obviously needs more verification than a weather app. But every step should still earn its place. One question we like to ask when looking at a user flow is: &quot;Do we really need the user to do this right now?&quot;</p><p>If the answer is no, maybe that step can happen later. Or perhaps it doesn&apos;t need to exist at all.</p><h3 id="silence-from-an-interface-is-surprisingly-frustrating">Silence From an Interface Is Surprisingly Frustrating</h3><p>You click Save. Nothing happens. You click it again. Still nothing. Now you&apos;re not sure whether it saved twice, didn&apos;t save at all, or the page is simply slow.</p><p>A tiny piece of feedback would solve the entire problem. <em>Saving...</em>followed by:</p><p><strong><em>&#x2713; Changes saved</em>.</strong></p><p>That&apos;s it. The same applies everywhere.</p><ul><li>Uploading a file? Show the progress.</li><li>Placing an order? Confirm it.</li><li>Processing a payment? Tell the user.</li><li>Something failed? Explain what happened.</li></ul><p>Interfaces should feel like they&apos;re responding to you. When they don&apos;t, people start second-guessing their actions.</p><h3 id="people-are-going-to-make-mistakes">People Are Going to Make Mistakes</h3><p>No matter how carefully you design something, users will eventually do something unexpected. They&apos;ll mistype their email address. Forget a required field. Upload the wrong document. Press the wrong button.</p><p>That&apos;s normal. The experience shouldn&apos;t fall apart because of it.</p><p>There&apos;s a big difference between:</p><blockquote>&quot;Error 402: Invalid request.&quot;</blockquote><p>and:</p><blockquote>&quot;We couldn&apos;t process your payment. Your card hasn&apos;t been charged. Please try again or use another payment method.&quot;</blockquote><p>One leaves you wondering what happened. The other tells you exactly where you stand and what you can do next. That&apos;s good UX too. It&apos;s not only about helping people move forward when everything goes right. It&apos;s also about helping them recover when something goes wrong.</p><h3 id="consistency-sounds-boring-its-actually-incredibly-useful">Consistency Sounds Boring. It&apos;s Actually Incredibly Useful.</h3><p>Imagine if the brake pedal moved every time you got into a different car. You&apos;d eventually figure it out, but you&apos;d have to think about something that normally requires no thought at all.</p><p>Digital products work in a similar way. If buttons, icons, navigation, and forms behave consistently, users start building familiarity. They don&apos;t need to relearn the interface on every screen.</p><p>This is also why design systems matter. A design system isn&apos;t just a beautifully organized Figma page full of buttons and color tokens. It gives the product a shared language. Designers know how components should look. Developers know how they should behave. And users get an experience that feels like one product rather than twenty screens designed independently.</p><h3 id="responsive-doesnt-automatically-mean-mobile-friendly">Responsive Doesn&apos;t Automatically Mean Mobile Friendly</h3><p>This distinction matters. You can make a desktop website technically fit inside a 390 pixel wide screen and call it responsive. That doesn&apos;t mean it&apos;s pleasant to use.</p><blockquote>Maybe the buttons are too small.</blockquote><blockquote>Maybe there&apos;s too much information.</blockquote><blockquote>Maybe a form designed for a large monitor takes forever to complete on a phone.</blockquote><blockquote>Maybe a pop-up covers half the screen.</blockquote><p>Instead of asking: &quot;How do we make this desktop screen fit on mobile?&quot;it can be much more useful to ask: &quot;What is someone likely trying to do here from their phone?&quot;</p><p>Maybe a desktop customer wants to explore every feature. A mobile visitor might just want to call you. Those are different needs, and the design should recognize that.</p><h3 id="accessibility-isnt-something-to-add-at-the-end"><strong>Accessibility Isn&apos;t Something to Add at the End</strong></h3><p>Readable text. Good contrast. Clear labels. Keyboard navigation. Visible focus states. Comfortable touch targets.</p><p>These are often discussed under accessibility, but look at that list again. Almost everything there also makes the product easier to use in general. Good contrast helps someone with low vision, but it also helps someone looking at their phone outside on a sunny afternoon. Large touch targets help people with motor impairments, but they also help someone trying to use a phone with one hand</p><p>Accessibility isn&apos;t a separate layer sitting on top of UX. It&apos;s part of designing an experience that works for real people in real situations.</p><h3 id="does-that-mean-everything-needs-to-look-simple"><strong>Does That Mean Everything Needs to Look Simple?</strong></h3><p>Not at all. This is where discussions about usability sometimes go too far. A usable website doesn&apos;t have to be white, minimal, and boring.</p><p>Visual personality matters. Typography can have character. Colors can be bold. Animation can make interactions feel alive. Illustrations can help tell a story.</p><p><strong>The question isn&apos;t: &quot;Is this decorative?&quot;It&apos;s: &quot;Is this getting in the user&apos;s way?&quot;</strong></p><p>A beautiful animation that helps communicate an idea can improve the experience. A beautiful animation that makes someone wait five seconds before they can read the page probably isn&apos;t helping. You can absolutely create something distinctive without making it difficult to use.</p><h3 id="so-what-does-good-uiux-actually-look-like"><strong>So, What Does Good UI/UX Actually Look Like?</strong></h3><p>There&apos;s no screenshot we can point to and say, &quot;This is what every good interface should look like.&quot;Context matters too much. A banking platform and a fashion store shouldn&apos;t feel identical. Neither should a children&apos;s learning app and an enterprise analytics dashboard.</p><p>But the underlying principles are surprisingly consistent. A strong experience is usually:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/uiux-principal.jpg" class="kg-image" alt="What Does Good UI/UX Actually Mean?" loading="lazy" width="2000" height="1334" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/uiux-principal.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/uiux-principal.jpg 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/uiux-principal.jpg 1600w, https://blog.gyri.tech/content/images/2026/08/uiux-principal.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><p><strong>Clear:</strong> You understand what&apos;s happening.</p><p><strong>Simple:</strong> Unnecessary complexity has been removed.</p><p><strong>Consistent:</strong> Things behave the way you expect them to.</p><p><strong>Useful:</strong> Elements exist for a reason</p><p><strong>Responsive:</strong> The experience works comfortably across devices.</p><p><strong>Accessible:</strong> More people can actually use it.</p><p><strong>Forgiving:</strong> Making a mistake doesn&apos;t leave you stuck.</p><p>You could probably add dozens of design principles to that list. But if those seven are working, you&apos;ve already solved a lot.</p><h3 id="why-does-any-of-this-matter-to-a-business"><strong>Why Does Any of This Matter to a Business?</strong></h3><p>Because users don&apos;t think: &quot;The UX of this company&apos;s checkout flow is inefficient.&quot;They think: &quot;Buying from this company is annoying.&quot;</p><p>They don&apos;t think: &quot;The information architecture of this website needs improvement.&quot;They think: &quot;I can&apos;t find what I&apos;m looking for.&quot;</p><p>That&apos;s the important part. People experience the interface, but they associate that experience with the business behind it.</p><blockquote>If your website makes things easy, your company can feel easier to work with.</blockquote><blockquote>If information is clear, your company can feel more transparent.</blockquote><blockquote>If everything works smoothly, the business feels more professional.</blockquote><p>UI/UX isn&apos;t going to rescue a bad product or a bad business. But poor UX can absolutely make a good product harder to use, harder to understand, and harder to trust.</p><h3 id="maybe-the-best-design-isnt-the-one-people-notice"><strong>Maybe the Best Design Isn&apos;t the One People Notice</strong></h3><p>Designers naturally want to create things people remember. There&apos;s nothing wrong with that. But when we&apos;re designing a product, sometimes the better compliment isn&apos;t: &quot;This looks amazing.&quot;</p><blockquote><strong>It&apos;s: &quot;That was easy.&quot;</strong></blockquote><p>The user found what they needed. They knew what to do. Nothing confused them. Nothing got in their way. They finished what they came to do and moved on.</p><blockquote><strong>It doesn&apos;t sound particularly dramatic. But that&apos;s often exactly what good UI/UX looks like.</strong></blockquote>]]></content:encoded></item><item><title><![CDATA[How We Built Inherited File Permissions Without Storing Effective ACLs Everywhere]]></title><description><![CDATA[<p><em>A practical walkthrough of hierarchical folder permissions, restriction-only inheritance, multi-principal evaluation, and explainable authorization &#x2014; built with Spring WebFlux and React.</em></p>
<hr>
<h2 id="the-problem">The problem</h2>
<p>Most people understand file permissions intuitively:</p>
<blockquote>
<p>If I can read <code>Finance</code>, I should be able to read what&#x2019;s inside it &#x2014; unless someone deliberately locked</p></blockquote>]]></description><link>https://blog.gyri.tech/how-we-built-inherited-file-permissions-without-storing-effective-acls-everywhere/</link><guid isPermaLink="false">6a7c8da4a59c17040f4ea65f</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Wed, 12 Aug 2026 18:26:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1667372283496-893f0b1e7c16?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGRucyUyMGdhdGV3YXklMjBzZWN1cml0eXxlbnwwfHx8fDE3ODY1NDc5NDB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1667372283496-893f0b1e7c16?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGRucyUyMGdhdGV3YXklMjBzZWN1cml0eXxlbnwwfHx8fDE3ODY1NDc5NDB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="How We Built Inherited File Permissions Without Storing Effective ACLs Everywhere"><p><em>A practical walkthrough of hierarchical folder permissions, restriction-only inheritance, multi-principal evaluation, and explainable authorization &#x2014; built with Spring WebFlux and React.</em></p>
<hr>
<h2 id="the-problem">The problem</h2>
<p>Most people understand file permissions intuitively:</p>
<blockquote>
<p>If I can read <code>Finance</code>, I should be able to read what&#x2019;s inside it &#x2014; unless someone deliberately locked a subfolder down.</p>
</blockquote>
<p>That sounds simple until you try to implement it.</p>
<p>Naive approaches fall into two traps:</p>
<ol>
<li>
<p><strong>Store effective permissions on every file</strong><br>
Then every ACL change forces a cascade update across thousands (or millions) of descendants. Group membership changes make it worse.</p>
</li>
<li>
<p><strong>Recompute from scratch with recursive parent walks</strong><br>
Every authorization check becomes a chain of database round-trips. Latency and load explode under concurrency.</p>
</li>
</ol>
<p>We wanted a third path: <strong>store only explicit ACL changes</strong>, calculate effective permissions on demand, and make the result <strong>explainable</strong> in the UI so developers and admins can see <em>why</em> a user has (or lost) <code>DELETE</code>.</p>
<p>This post describes the model and algorithm we implemented in a full-stack proof of concept:</p>
<ul>
<li><strong>Backend:</strong> Spring Boot 4 + WebFlux + Reactive MongoDB</li>
<li><strong>Frontend:</strong> React + Ant Design + Tailwind</li>
<li><strong>Auth for the demo:</strong> <code>X-User-Id</code> header with user switching</li>
</ul>
<hr>
<h2 id="the-permission-model-in-one-sentence">The permission model in one sentence</h2>
<blockquote>
<p>Effective permissions flow from parent to child; a child may <strong>restrict</strong> what it inherited, but must never <strong>broaden</strong> it.</p>
</blockquote>
<p>Permissions used:</p>
<pre><code class="language-text">READ &#xB7; WRITE &#xB7; DELETE &#xB7; SHARE
</code></pre>
<p>ACL modes:</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ALLOW</code></td>
<td>Grants a permission set (usually the first grant on a principal chain)</td>
</tr>
<tr>
<td><code>RESTRICT</code></td>
<td><code>effective = inherited &#x2229; restriction</code></td>
</tr>
</tbody>
</table>
<p>No explicit <code>DENY</code> in v1. Empty <code>RESTRICT</code> (<code>permissions: []</code>) means: <strong>clear all permissions for that subject on this branch</strong>.</p>
<hr>
<h2 id="a-concrete-example">A concrete example</h2>
<p>Hierarchy:</p>
<pre><code class="language-text">Root
 &#x2514;&#x2500;&#x2500; Finance
      &#x2514;&#x2500;&#x2500; Payroll
           &#x2514;&#x2500;&#x2500; salaries-2026.xlsx
</code></pre>
<p>ACLs for Alice:</p>
<table>
<thead>
<tr>
<th>Node</th>
<th>Mode</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Root</td>
<td>ALLOW</td>
<td>READ, WRITE, DELETE, SHARE</td>
</tr>
<tr>
<td>Finance</td>
<td>RESTRICT</td>
<td>READ, WRITE, DELETE</td>
</tr>
<tr>
<td>Payroll</td>
<td>RESTRICT</td>
<td>READ, WRITE</td>
</tr>
<tr>
<td>salaries-2026.xlsx</td>
<td><em>(none)</em></td>
<td>inherits</td>
</tr>
</tbody>
</table>
<p>Result on the file:</p>
<pre><code class="language-text">Alice &#x2192; salaries-2026.xlsx
  READ  &#x2713;
  WRITE &#x2713;
  DELETE &#x2715;  (removed at Payroll)
  SHARE  &#x2715;  (removed at Finance)
</code></pre>
<p>That is the entire product story in four rows of ACL data.</p>
<hr>
<h2 id="what-we-store-and-what-we-don%E2%80%99t">What we store (and what we don&#x2019;t)</h2>
<h3 id="stored">Stored</h3>
<ul>
<li><strong>Nodes</strong> &#x2014; folders/files with <code>parentId</code>, <code>ancestors[]</code>, <code>depth</code>, <code>permissionBoundary</code>, <code>permissionVersion</code></li>
<li><strong>ACL entries</strong> &#x2014; only where permissions <em>change</em></li>
<li><strong>Users / groups</strong> &#x2014; users reference <code>groupIds</code></li>
</ul>
<h3 id="not-stored">Not stored</h3>
<ul>
<li>Effective permissions per user per file</li>
<li>&#x201C;Inherited copy&#x201D; of parent ACLs on every descendant</li>
</ul>
<p>Why? Effective rights depend on <strong>who is asking</strong>. Alice and Bob looking at the same Payroll folder can get different answers. Caching a single effective set on the node would be wrong.</p>
<p>Permission boundaries look like this:</p>
<pre><code class="language-text">Root        &#x2190; ACL
Finance     &#x2190; ACL
Reports     &#x2190; no ACL (inherits Finance)
2026        &#x2190; no ACL
report.pdf  &#x2190; no ACL
</code></pre>
<p>Only Root &#x2192; Finance are boundaries for Alice&#x2019;s chain. Everything under Finance inherits until the next explicit ACL.</p>
<hr>
<h2 id="users-groups-and-principals">Users, groups, and principals</h2>
<p>An ACL targets either a <strong>USER</strong> or a <strong>GROUP</strong>.</p>
<p>When evaluating Alice, we expand:</p>
<pre><code class="language-text">USER:user-alice
GROUP:group-employees
GROUP:group-finance
GROUP:group-managers
</code></pre>
<p>Each principal is evaluated <strong>independently</strong> along the path. The final answer is the <strong>union</strong> of those results.</p>
<p>That matters:</p>
<ul>
<li>Employees might only have <code>READ</code> at Root</li>
<li>Finance Team might get <code>READ, WRITE</code> at Finance</li>
<li>Bob, in both groups, ends up with <code>READ &#x222A; WRITE</code> under Finance</li>
</ul>
<p>If we merged all ACLs into one running set too early, the Employees <code>READ</code> at Root could incorrectly cap Bob&#x2019;s Finance Team grant.</p>
<hr>
<h2 id="materialized-paths-instead-of-recursive-lookups">Materialized paths instead of recursive lookups</h2>
<p>Every node stores:</p>
<pre><code class="language-text">parentId
ancestors[]
depth
</code></pre>
<p>Example:</p>
<pre><code class="language-text">Payroll.ancestors = [Root, Finance]
</code></pre>
<p>Authorization builds the evaluation path in one shot:</p>
<pre><code class="language-text">path = ancestors + [target]
     = Root &#x2192; Finance &#x2192; Payroll &#x2192; salaries-2026.xlsx
</code></pre>
<p>No <code>$graphLookup</code>. No &#x201C;fetch parent, then parent&#x2019;s parent&#x201D; loop on the hot path.</p>
<p>Subtree moves are more expensive (rewrite descendants&#x2019; <code>ancestors</code>), but reads stay cheap &#x2014; the right trade-off for a permission-heavy system.</p>
<hr>
<h2 id="the-acl-calculation-algorithm">The ACL calculation algorithm</h2>
<p>Here is the algorithm, step by step, as implemented in <code>PermissionService</code> &#x2014; walked against <strong>dummy MongoDB documents</strong>.</p>
<p><strong>Request we will evaluate:</strong></p>
<pre><code class="language-http">GET /api/v1/nodes/file-salary-2026/permissions
X-User-Id: user-alice
X-Tenant-Id: tenant-1
</code></pre>
<p><strong>Question:</strong> What is Alice&#x2019;s effective permission set on <code>salaries-2026.xlsx</code>?</p>
<hr>
<h3 id="dummy-data-mongodb-collections">Dummy data (MongoDB collections)</h3>
<h4 id="users"><code>users</code></h4>
<table>
<thead>
<tr>
<th><code>_id</code></th>
<th><code>tenantId</code></th>
<th><code>username</code></th>
<th><code>displayName</code></th>
<th><code>email</code></th>
<th><code>groupIds</code></th>
<th><code>admin</code></th>
<th><code>active</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user-alice</code></td>
<td><code>tenant-1</code></td>
<td><code>alice</code></td>
<td>Alice Smith</td>
<td><a href="mailto:alice@example.com">alice@example.com</a></td>
<td><code>group-employees</code>, <code>group-finance</code>, <code>group-managers</code></td>
<td><code>true</code></td>
<td><code>true</code></td>
</tr>
<tr>
<td><code>user-bob</code></td>
<td><code>tenant-1</code></td>
<td><code>bob</code></td>
<td>Bob Jones</td>
<td><a href="mailto:bob@example.com">bob@example.com</a></td>
<td><code>group-employees</code>, <code>group-finance</code></td>
<td><code>false</code></td>
<td><code>true</code></td>
</tr>
<tr>
<td><code>user-charlie</code></td>
<td><code>tenant-1</code></td>
<td><code>charlie</code></td>
<td>Charlie Brown</td>
<td><a href="mailto:charlie@example.com">charlie@example.com</a></td>
<td><code>group-employees</code>, <code>group-hr</code></td>
<td><code>false</code></td>
<td><code>true</code></td>
</tr>
</tbody>
</table>
<h4 id="groups"><code>groups</code></h4>
<table>
<thead>
<tr>
<th><code>_id</code></th>
<th><code>tenantId</code></th>
<th><code>name</code></th>
<th><code>description</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>group-employees</code></td>
<td><code>tenant-1</code></td>
<td>Employees</td>
<td>All employees</td>
</tr>
<tr>
<td><code>group-finance</code></td>
<td><code>tenant-1</code></td>
<td>Finance Team</td>
<td>Finance department</td>
</tr>
<tr>
<td><code>group-hr</code></td>
<td><code>tenant-1</code></td>
<td>HR Team</td>
<td>Human resources</td>
</tr>
<tr>
<td><code>group-managers</code></td>
<td><code>tenant-1</code></td>
<td>Managers</td>
<td>Management</td>
</tr>
</tbody>
</table>
<h4 id="nodes-subset-used-for-this-evaluation"><code>nodes</code> (subset used for this evaluation)</h4>
<table>
<thead>
<tr>
<th><code>_id</code></th>
<th><code>type</code></th>
<th><code>name</code></th>
<th><code>parentId</code></th>
<th><code>ancestors</code></th>
<th><code>depth</code></th>
<th><code>permissionBoundary</code></th>
<th><code>permissionVersion</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>folder-root</code></td>
<td>FOLDER</td>
<td>Root</td>
<td><code>null</code></td>
<td><code>[]</code></td>
<td>0</td>
<td><code>true</code></td>
<td>1</td>
</tr>
<tr>
<td><code>folder-finance</code></td>
<td>FOLDER</td>
<td>Finance</td>
<td><code>folder-root</code></td>
<td><code>[folder-root]</code></td>
<td>1</td>
<td><code>true</code></td>
<td>1</td>
</tr>
<tr>
<td><code>folder-payroll</code></td>
<td>FOLDER</td>
<td>Payroll</td>
<td><code>folder-finance</code></td>
<td><code>[folder-root, folder-finance]</code></td>
<td>2</td>
<td><code>true</code></td>
<td>1</td>
</tr>
<tr>
<td><code>file-salary-2026</code></td>
<td>FILE</td>
<td>salaries-2026.xlsx</td>
<td><code>folder-payroll</code></td>
<td><code>[folder-root, folder-finance, folder-payroll]</code></td>
<td>3</td>
<td><code>false</code></td>
<td>1</td>
</tr>
</tbody>
</table>
<h4 id="aclentries-subset-relevant-to-alice-this-path"><code>acl_entries</code> (subset relevant to Alice / this path)</h4>
<table>
<thead>
<tr>
<th><code>_id</code></th>
<th><code>nodeId</code></th>
<th><code>subjectType</code></th>
<th><code>subjectId</code></th>
<th><code>permissions</code></th>
<th><code>mode</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>acl-root-alice</code></td>
<td><code>folder-root</code></td>
<td>USER</td>
<td><code>user-alice</code></td>
<td>READ, WRITE, DELETE, SHARE</td>
<td>ALLOW</td>
</tr>
<tr>
<td><code>acl-root-employees</code></td>
<td><code>folder-root</code></td>
<td>GROUP</td>
<td><code>group-employees</code></td>
<td>READ</td>
<td>ALLOW</td>
</tr>
<tr>
<td><code>acl-finance-alice</code></td>
<td><code>folder-finance</code></td>
<td>USER</td>
<td><code>user-alice</code></td>
<td>READ, WRITE, DELETE</td>
<td>RESTRICT</td>
</tr>
<tr>
<td><code>acl-finance-employees</code></td>
<td><code>folder-finance</code></td>
<td>GROUP</td>
<td><code>group-employees</code></td>
<td><em>(empty)</em></td>
<td>RESTRICT</td>
</tr>
<tr>
<td><code>acl-finance-group</code></td>
<td><code>folder-finance</code></td>
<td>GROUP</td>
<td><code>group-finance</code></td>
<td>READ, WRITE</td>
<td>ALLOW</td>
</tr>
<tr>
<td><code>acl-payroll-alice</code></td>
<td><code>folder-payroll</code></td>
<td>USER</td>
<td><code>user-alice</code></td>
<td>READ, WRITE</td>
<td>RESTRICT</td>
</tr>
</tbody>
</table>
<p><em>(No ACL on <code>file-salary-2026</code> &#x2014; it inherits.)</em></p>
<hr>
<h3 id="step-1-%E2%80%94-load-the-target-node">Step 1 &#x2014; Load the target node</h3>
<p><strong>Query mapping</strong></p>
<pre><code class="language-text">nodes.find({ tenantId: &quot;tenant-1&quot;, _id: &quot;file-salary-2026&quot; })
</code></pre>
<p><strong>Output (from dummy <code>nodes</code>)</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>_id</code></td>
<td><code>file-salary-2026</code></td>
</tr>
<tr>
<td><code>name</code></td>
<td><code>salaries-2026.xlsx</code></td>
</tr>
<tr>
<td><code>ancestors</code></td>
<td><code>[folder-root, folder-finance, folder-payroll]</code></td>
</tr>
<tr>
<td><code>permissionVersion</code></td>
<td><code>1</code></td>
</tr>
</tbody>
</table>
<p><strong>Cache key candidate</strong></p>
<pre><code class="language-text">tenant-1:user-alice:file-salary-2026:1
</code></pre>
<p>Assume cache miss &#x2192; continue evaluation.</p>
<hr>
<h3 id="step-2-%E2%80%94-resolve-principals">Step 2 &#x2014; Resolve principals</h3>
<p><strong>Input:</strong> <code>users</code> row <code>user-alice</code></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>_id</code></td>
<td><code>user-alice</code></td>
</tr>
<tr>
<td><code>groupIds</code></td>
<td><code>group-employees</code>, <code>group-finance</code>, <code>group-managers</code></td>
</tr>
</tbody>
</table>
<p><strong>Principal expansion</strong></p>
<pre><code class="language-text">principals = [
  USER:user-alice,
  GROUP:group-employees,
  GROUP:group-finance,
  GROUP:group-managers
]
</code></pre>
<p><strong>Running map initialized</strong></p>
<table>
<thead>
<tr>
<th>Principal key</th>
<th><code>running</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<hr>
<h3 id="step-3-%E2%80%94-build-the-evaluation-path">Step 3 &#x2014; Build the evaluation path</h3>
<p><strong>Mapping from target node</strong></p>
<pre><code class="language-text">path = ancestors + [target]
     = [folder-root, folder-finance, folder-payroll] + [file-salary-2026]
</code></pre>
<p><strong>Output path (ordered root &#x2192; leaf)</strong></p>
<table>
<thead>
<tr>
<th>#</th>
<th><code>nodeId</code></th>
<th><code>name</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><code>folder-root</code></td>
<td>Root</td>
</tr>
<tr>
<td>2</td>
<td><code>folder-finance</code></td>
<td>Finance</td>
</tr>
<tr>
<td>3</td>
<td><code>folder-payroll</code></td>
<td>Payroll</td>
</tr>
<tr>
<td>4</td>
<td><code>file-salary-2026</code></td>
<td>salaries-2026.xlsx</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="step-4-%E2%80%94-prefetch-acls-for-path-nodes">Step 4 &#x2014; Prefetch ACLs for path nodes</h3>
<p><strong>Query mapping</strong></p>
<pre><code class="language-text">acl_entries.find({
  tenantId: &quot;tenant-1&quot;,
  nodeId: { $in: [
    &quot;folder-root&quot;,
    &quot;folder-finance&quot;,
    &quot;folder-payroll&quot;,
    &quot;file-salary-2026&quot;
  ]}
})
</code></pre>
<p><strong>Grouped output (<code>aclByNode</code>)</strong></p>
<table>
<thead>
<tr>
<th><code>nodeId</code></th>
<th>Matching ACL ids</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>folder-root</code></td>
<td><code>acl-root-alice</code>, <code>acl-root-employees</code></td>
</tr>
<tr>
<td><code>folder-finance</code></td>
<td><code>acl-finance-alice</code>, <code>acl-finance-employees</code>, <code>acl-finance-group</code></td>
</tr>
<tr>
<td><code>folder-payroll</code></td>
<td><code>acl-payroll-alice</code></td>
</tr>
<tr>
<td><code>file-salary-2026</code></td>
<td><em>(none)</em></td>
</tr>
</tbody>
</table>
<hr>
<h3 id="step-5-%E2%80%94-walk-the-path-per-principal">Step 5 &#x2014; Walk the path per principal</h3>
<p>At each node, for each principal:</p>
<ol>
<li>Filter ACLs to that principal</li>
<li>Split into ALLOW / RESTRICT (union within mode)</li>
<li>Apply first-grant or intersect rules</li>
</ol>
<h4 id="node-1-%E2%80%94-folder-root-root">Node 1 &#x2014; <code>folder-root</code> (Root)</h4>
<p><strong>ACLs at this node vs Alice&#x2019;s principals</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th>Matching ACL</th>
<th>Mode</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>acl-root-alice</code></td>
<td>ALLOW</td>
<td>R W D S</td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>acl-root-employees</code></td>
<td>ALLOW</td>
<td>R</td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td>&#x2014;</td>
<td>&#x2014;</td>
<td>inherit</td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td>&#x2014;</td>
<td>&#x2014;</td>
<td>inherit</td>
</tr>
</tbody>
</table>
<p><strong>Per-principal evaluation</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th><code>before</code></th>
<th>Rule</th>
<th><code>after</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>&#x2205;</code></td>
<td>first ALLOW &#x2192; set</td>
<td><code>{R,W,D,S}</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>&#x2205;</code></td>
<td>first ALLOW &#x2192; set</td>
<td><code>{R}</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>&#x2205;</code></td>
<td>no ACL &#x2192; inherit</td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
<td>no ACL &#x2192; inherit</td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<p><strong>Combined after Root</strong> (<code>UNION</code>)</p>
<pre><code class="language-text">{R,W,D,S} &#x222A; {R} &#x222A; &#x2205; &#x222A; &#x2205; = {R, W, D, S}
</code></pre>
<p><strong>Step snapshot</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>localGrant</code></td>
<td>R W D S</td>
</tr>
<tr>
<td><code>localMode</code></td>
<td>ALLOW</td>
</tr>
<tr>
<td><code>effectiveAfter</code></td>
<td>R W D S</td>
</tr>
</tbody>
</table>
<hr>
<h4 id="node-2-%E2%80%94-folder-finance-finance">Node 2 &#x2014; <code>folder-finance</code> (Finance)</h4>
<p><strong>ACLs at this node vs Alice&#x2019;s principals</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th>Matching ACL</th>
<th>Mode</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>acl-finance-alice</code></td>
<td>RESTRICT</td>
<td>R W D</td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>acl-finance-employees</code></td>
<td>RESTRICT</td>
<td><code>[]</code> (empty)</td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>acl-finance-group</code></td>
<td>ALLOW</td>
<td>R W</td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td>&#x2014;</td>
<td>&#x2014;</td>
<td>inherit</td>
</tr>
</tbody>
</table>
<p><strong>Per-principal evaluation</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th><code>before</code></th>
<th>Rule</th>
<th>Computation</th>
<th><code>after</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>{R,W,D,S}</code></td>
<td>RESTRICT &#x2229;</td>
<td><code>{R,W,D,S} &#x2229; {R,W,D}</code></td>
<td><code>{R,W,D}</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>{R}</code></td>
<td>RESTRICT &#x2229; empty</td>
<td><code>{R} &#x2229; &#x2205;</code></td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>&#x2205;</code></td>
<td>first ALLOW &#x2192; set</td>
<td>ALLOW <code>{R,W}</code></td>
<td><code>{R,W}</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
<td>no ACL</td>
<td>inherit</td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<p><strong>Combined after Finance</strong></p>
<pre><code class="language-text">{R,W,D} &#x222A; &#x2205; &#x222A; {R,W} &#x222A; &#x2205; = {R, W, D}
</code></pre>
<p>SHARE is gone (Alice USER restricted). Employees principal cleared. Finance group still contributes R+W.</p>
<p><strong>Step snapshot</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>effectiveAfter</code></td>
<td>R W D</td>
</tr>
</tbody>
</table>
<hr>
<h4 id="node-3-%E2%80%94-folder-payroll-payroll">Node 3 &#x2014; <code>folder-payroll</code> (Payroll)</h4>
<p><strong>ACLs at this node vs Alice&#x2019;s principals</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th>Matching ACL</th>
<th>Mode</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>acl-payroll-alice</code></td>
<td>RESTRICT</td>
<td>R W</td>
</tr>
<tr>
<td>others</td>
<td>&#x2014;</td>
<td>&#x2014;</td>
<td>inherit</td>
</tr>
</tbody>
</table>
<p><strong>Per-principal evaluation</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th><code>before</code></th>
<th>Rule</th>
<th>Computation</th>
<th><code>after</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>{R,W,D}</code></td>
<td>RESTRICT &#x2229;</td>
<td><code>{R,W,D} &#x2229; {R,W}</code></td>
<td><code>{R,W}</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>&#x2205;</code></td>
<td>no ACL</td>
<td>inherit</td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>{R,W}</code></td>
<td>no ACL</td>
<td>inherit</td>
<td><code>{R,W}</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
<td>no ACL</td>
<td>inherit</td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<p><strong>Combined after Payroll</strong></p>
<pre><code class="language-text">{R,W} &#x222A; &#x2205; &#x222A; {R,W} &#x222A; &#x2205; = {R, W}
</code></pre>
<p>DELETE is gone (Alice USER restricted at Payroll).</p>
<p><strong>Step snapshot</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>effectiveAfter</code></td>
<td>R W</td>
</tr>
</tbody>
</table>
<hr>
<h4 id="node-4-%E2%80%94-file-salary-2026-salaries-2026xlsx">Node 4 &#x2014; <code>file-salary-2026</code> (salaries-2026.xlsx)</h4>
<p><strong>ACLs at this node:</strong> none for any principal.</p>
<p><strong>Per-principal evaluation</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th><code>before</code></th>
<th>Rule</th>
<th><code>after</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>{R,W}</code></td>
<td>inherit</td>
<td><code>{R,W}</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>&#x2205;</code></td>
<td>inherit</td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>{R,W}</code></td>
<td>inherit</td>
<td><code>{R,W}</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
<td>inherit</td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<p><strong>Combined after file</strong></p>
<pre><code class="language-text">{R,W} &#x222A; &#x2205; &#x222A; {R,W} &#x222A; &#x2205; = {R, W}
</code></pre>
<p><strong>Step snapshot</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>localGrant</code></td>
<td>&#x2014;</td>
</tr>
<tr>
<td><code>localMode</code></td>
<td><code>null</code></td>
</tr>
<tr>
<td><code>effectiveAfter</code></td>
<td>R W</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="step-6-%E2%80%94-union-all-principals-final">Step 6 &#x2014; Union all principals (final)</h3>
<p><strong>Final <code>running</code> map</strong></p>
<table>
<thead>
<tr>
<th>Principal</th>
<th>Final set</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>USER:user-alice</code></td>
<td><code>{READ, WRITE}</code></td>
</tr>
<tr>
<td><code>GROUP:group-employees</code></td>
<td><code>&#x2205;</code></td>
</tr>
<tr>
<td><code>GROUP:group-finance</code></td>
<td><code>{READ, WRITE}</code></td>
</tr>
<tr>
<td><code>GROUP:group-managers</code></td>
<td><code>&#x2205;</code></td>
</tr>
</tbody>
</table>
<p><strong>Effective permission calculation</strong></p>
<pre><code class="language-text">effective(Alice, file-salary-2026)
  = {R,W} &#x222A; &#x2205; &#x222A; {R,W} &#x222A; &#x2205;
  = { READ, WRITE }
</code></pre>
<p><strong>API-shaped output</strong></p>
<pre><code class="language-json">{
  &quot;nodeId&quot;: &quot;file-salary-2026&quot;,
  &quot;effectivePermissions&quot;: [&quot;READ&quot;, &quot;WRITE&quot;],
  &quot;evaluationPath&quot;: [
    { &quot;nodeId&quot;: &quot;folder-root&quot;,    &quot;nodeName&quot;: &quot;Root&quot;,               &quot;effectiveAfter&quot;: [&quot;READ&quot;,&quot;WRITE&quot;,&quot;DELETE&quot;,&quot;SHARE&quot;] },
    { &quot;nodeId&quot;: &quot;folder-finance&quot;, &quot;nodeName&quot;: &quot;Finance&quot;,            &quot;effectiveAfter&quot;: [&quot;READ&quot;,&quot;WRITE&quot;,&quot;DELETE&quot;] },
    { &quot;nodeId&quot;: &quot;folder-payroll&quot;, &quot;nodeName&quot;: &quot;Payroll&quot;,            &quot;effectiveAfter&quot;: [&quot;READ&quot;,&quot;WRITE&quot;] },
    { &quot;nodeId&quot;: &quot;file-salary-2026&quot;,&quot;nodeName&quot;: &quot;salaries-2026.xlsx&quot;,&quot;effectiveAfter&quot;: [&quot;READ&quot;,&quot;WRITE&quot;] }
  ]
}
</code></pre>
<p><strong>Human reading</strong></p>
<pre><code class="language-text">Alice &#x2192; salaries-2026.xlsx
  READ  &#x2713;
  WRITE &#x2713;
  DELETE &#x2715;  (removed at Payroll on USER:user-alice)
  SHARE  &#x2715;  (removed at Finance on USER:user-alice)
</code></pre>
<hr>
<h3 id="step-7-%E2%80%94-cache">Step 7 &#x2014; Cache</h3>
<p><strong>Put</strong></p>
<pre><code class="language-text">key   = tenant-1:user-alice:file-salary-2026:1
value = { READ, WRITE }
</code></pre>
<p>Next identical request hits cache until an ACL change bumps <code>permissionVersion</code> on the boundary (and descendants), which changes the key and forces recompute.</p>
<hr>
<h3 id="algorithm-rules-quick-reference">Algorithm rules (quick reference)</h3>
<pre><code class="language-text">if running is empty:
    if ALLOW exists:   running = ALLOW
    else if RESTRICT:  running = RESTRICT
else:
    if RESTRICT exists: running = running &#x2229; RESTRICT
    else if ALLOW:      running = running &#x2229; ALLOW
else:
    inherit (no change)

effective(user, node) = &#x22C3; running[principal]
</code></pre>
<p><strong>Empty RESTRICT</strong> (<code>permissions: []</code>) &#x2192; intersect with <code>&#x2205;</code> &#x2192; that principal is cleared on the branch (see Employees at Finance above).</p>
<hr>
<h2 id="diagram-of-the-flow">Diagram of the flow</h2>
<pre><code class="language-text">HTTP request (X-User-Id)
        &#x2502;
        &#x25BC;
 CurrentUserProvider &#x2192; UserPrincipal
        &#x2502;
        &#x25BC;
 AuthorizationService.canAccess(READ|WRITE|DELETE|SHARE)
        &#x2502;
        &#x25BC;
 PermissionService.getEffectivePermissions
        &#x2502;
        &#x251C;&#x2500;&#x2500; cache hit? &#x2192; return
        &#x2502;
        &#x251C;&#x2500;&#x2500; resolve principals
        &#x251C;&#x2500;&#x2500; load path from ancestors[]
        &#x251C;&#x2500;&#x2500; load ACLs for path
        &#x251C;&#x2500;&#x2500; per-principal walk (ALLOW / RESTRICT / inherit)
        &#x251C;&#x2500;&#x2500; UNION
        &#x2514;&#x2500;&#x2500; cache put
        &#x2502;
        &#x25BC;
 allow or 403
</code></pre>
<p>Controllers never embed permission logic. There is one calculation path.</p>
<hr>
<h2 id="making-permissions-explainable">Making permissions explainable</h2>
<p>A boolean <code>canDelete</code> is not enough for a demo &#x2014; or for debugging production ACLs.</p>
<p>We return:</p>
<ul>
<li><strong>Effective set</strong> &#x2014; what the user has now</li>
<li><strong>Sources</strong> &#x2014; which node/subject/mode contributed each permission</li>
<li><strong>Evaluation path</strong> &#x2014; node-by-node effective snapshot</li>
</ul>
<p>The UI renders this as:</p>
<pre><code class="language-text">&#x2713; READ     inherited from Root
&#x2713; WRITE    restricted at Payroll
&#x2715; DELETE   removed at Payroll
&#x2715; SHARE    removed at Finance

Root      R W D S
  &#x2193;
Finance   R W D
  &#x2193;
Payroll   R W
  &#x2193;
file      R W
</code></pre>
<p>When you switch users in the header, the same file tells a different story. That is the point of the product demo.</p>
<hr>
<h2 id="frontend-permission-aware-browsing">Frontend: permission-aware browsing</h2>
<p>The left folder tree is not a static org chart. It calls a tree API that <strong>filters by READ</strong>. Branches you cannot see disappear.</p>
<p>The file list only shows children you can read.<br>
The right-hand accordion shows effective permissions + ACL entries for the selected node.<br>
Admins can create users/groups and assign memberships; ACL edits require WRITE on the node.</p>
<p>Uploaded files land on a configurable local path:</p>
<pre><code class="language-yaml">filesystem:
  storage:
    local-path: ./data/files
</code></pre>
<p>Metadata stays in MongoDB; bytes stay on disk under <code>{tenantId}/{nodeId}/{filename}</code>.</p>
<hr>
<h2 id="design-choices-worth-calling-out">Design choices worth calling out</h2>
<h3 id="why-restrictions-instead-of-arbitrary-overrides">Why restrictions instead of arbitrary overrides?</h3>
<p>If a child could invent <code>DELETE</code> without the parent having it, &#x201C;folder security&#x201D; becomes theater. Restriction-only inheritance matches how people think about shared drives: lock down as you go deeper, don&#x2019;t escalate.</p>
<h3 id="why-not-deny-yet">Why not DENY yet?</h3>
<p><code>RESTRICT</code> with an empty set covers &#x201C;this subject gets nothing here.&#x201D; Explicit DENY (override even a wider group grant) is a future mode that would apply as a final mask and must also appear in the explanation path.</p>
<h3 id="why-versioned-cache-keys">Why versioned cache keys?</h3>
<p>Invalidating &#x201C;everything under Finance&#x201D; in Redis is painful. Versioning the node (and descendants) means old keys simply stop matching. Local cache today; Redis tomorrow behind the same interface.</p>
<h3 id="why-reactive-end-to-end">Why reactive end-to-end?</h3>
<p>Authorization fans out into many small I/O ops (node, ACLs, cache). WebFlux + Reactor keeps that composition explicit without blocking threads on every check. No <code>.block()</code> on the request path.</p>
<hr>
<h2 id="lessons-from-building-the-poc">Lessons from building the POC</h2>
<ol>
<li><strong>Explainability is a feature</strong>, not a debug dump. If you cannot show the path, admins will not trust the model.</li>
<li><strong>Principals must be separate until the end.</strong> Early merging of user + group ACLs creates subtle escalation bugs.</li>
<li><strong>Store boundaries, compute leaves.</strong> Millions of files do not need millions of ACL documents.</li>
<li><strong>UI and API must agree.</strong> Sorting folders first, filtering trees by READ, and allowing empty RESTRICT all had to land in both layers.</li>
<li><strong>Demo auth is fine if the principal abstraction is real.</strong> Swap <code>X-User-Id</code> for JWT/OIDC later without rewriting <code>PermissionService</code>.</li>
</ol>
<hr>
<h2 id="closing">Closing</h2>
<p>Hierarchical permissions are less about clever data structures and more about a crisp rule:</p>
<pre><code class="language-text">Parent effective
      &#x2502;
      &#x25BC;
Child restriction   &#x2192;   INTERSECT
      &#x2502;
      &#x25BC;
Descendant inherits
</code></pre>
<p>Add multi-principal union, materialized paths, and versioned caching, and you get a system that is fast enough to query, cheap enough to update, and clear enough to debug.</p>
<p>If you are designing a drive, DMS, or multi-tenant workspace, start with that rule &#x2014; then make the evaluation path visible. The algorithm only earns trust when the UI can show its work.</p>
<hr>
<h3 id="stack-reference">Stack reference</h3>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Choice</th>
</tr>
</thead>
<tbody>
<tr>
<td>API</td>
<td>Spring Boot 4, WebFlux, Reactive MongoDB</td>
</tr>
<tr>
<td>UI</td>
<td>React, TypeScript, Vite, Ant Design, Tailwind</td>
</tr>
<tr>
<td>Store</td>
<td>MongoDB collections: <code>nodes</code>, <code>acl_entries</code>, <code>users</code>, <code>groups</code></td>
</tr>
<tr>
<td>Files</td>
<td>Local filesystem path from <code>filesystem.storage.local-path</code></td>
</tr>
</tbody>
</table>
<p><em>Built as a runnable proof of concept for inherited, restriction-only, explainable file permissions.</em></p>
]]></content:encoded></item><item><title><![CDATA[Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud]]></title><description><![CDATA[<blockquote><strong>Introduction :</strong></blockquote><p>Document upload is a common requirement in modern applications. In a multi-tenant application, document storage must also maintain clear separation among tenants, users, and system-level documents.</p><p>In our application, we use <strong>Spring Boot, Kotlin, MongoDB, and Nextcloud</strong> to manage documents.</p><p>During <strong>tenant registration</strong>, document-storage information such as the Nextcloud</p>]]></description><link>https://blog.gyri.tech/document-upload-and-storage-using-spring-boot-kotlin-and-nextcloud/</link><guid isPermaLink="false">6a7af7b3a59c17040f4ea4d0</guid><dc:creator><![CDATA[Shubham Kiran Lokhande]]></dc:creator><pubDate>Wed, 12 Aug 2026 07:06:26 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/08/Main-Image.png" medium="image"/><content:encoded><![CDATA[<blockquote><strong>Introduction :</strong></blockquote><img src="https://blog.gyri.tech/content/images/2026/08/Main-Image.png" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud"><p>Document upload is a common requirement in modern applications. In a multi-tenant application, document storage must also maintain clear separation among tenants, users, and system-level documents.</p><p>In our application, we use <strong>Spring Boot, Kotlin, MongoDB, and Nextcloud</strong> to manage documents.</p><p>During <strong>tenant registration</strong>, document-storage information such as the Nextcloud user, authentication details, URL, user entities, and system entities is configured.</p><hr><blockquote><strong><em>The main idea is :</em></strong></blockquote><ul><li><strong>Nextcloud </strong>stores the actual documents.</li><li><strong>MongoDB </strong>stores document metadata.</li><li><strong>Tenant configuration </strong>defines where and how documents are stored.</li><li><strong>Spring Boot WebFlux </strong>manages the complete upload flow.</li></ul><blockquote><strong>Complete Document Upload Flow :</strong></blockquote><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/Complete-Flow-2-2.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="578" height="877"></figure><hr><blockquote><strong><em>1. Document Configuration During Tenant Registration</em></strong></blockquote><p>When a tenant is registered, the document storage configuration is also added. The configuration contains information such as:</p><ul><li>Nextcloud user</li><li>Authentication details</li><li>Nextcloud URL</li><li>User entities</li><li>System entities</li></ul><p>This configuration tells the application which document types belong to users and which belong to the system.</p><hr><blockquote><strong><em>2. User Entity Documents</em></strong></blockquote><p>User entity documents are documents that belong to a specific user. The storage structure is:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/User-Entity-flow-2.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1726" height="911" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/User-Entity-flow-2.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/User-Entity-flow-2.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/User-Entity-flow-2.png 1600w, https://blog.gyri.tech/content/images/2026/08/User-Entity-flow-2.png 1726w" sizes="(min-width: 720px) 720px"></figure><p></p><p>For example:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/User-Entity-Example-2.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1536" height="1024" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/User-Entity-Example-2.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/User-Entity-Example-2.png 1000w, https://blog.gyri.tech/content/images/2026/08/User-Entity-Example-2.png 1536w" sizes="(min-width: 720px) 720px"></figure><p>Here: </p><ul><li>syc &#x2192; Tenant</li><li>users &#x2192; User document category</li><li>fe6b7664-05e1-4221-be25-2045cb9da3c8 &#x2192; User ID</li><li>PROJECT_REPORT &#x2192; User entity</li><li>abc.pdf &#x2192; Uploaded document</li></ul><p>This structure ensures that user documents remain associated with the correct tenant and user.</p><hr><blockquote><strong><em>3. System Entity Documents</em></strong></blockquote><p>System entity documents belong to the tenant&#x2019;s system rather than to a specific user. The storage structure is:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/System-Entity-Flow.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1726" height="911" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/System-Entity-Flow.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/System-Entity-Flow.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2026/08/System-Entity-Flow.png 1600w, https://blog.gyri.tech/content/images/2026/08/System-Entity-Flow.png 1726w" sizes="(min-width: 720px) 720px"></figure><p>For example:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/System-Entiity-Example-2.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1536" height="1024" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/System-Entiity-Example-2.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/System-Entiity-Example-2.png 1000w, https://blog.gyri.tech/content/images/2026/08/System-Entiity-Example-2.png 1536w" sizes="(min-width: 720px) 720px"></figure><p>Here:</p><ul><li>syc &#x2192; Tenant </li><li>system &#x2192; System document category</li><li>syc-system-1 &#x2192; Default system object ID</li><li>VEHICLE_LOG_DOCUMENT &#x2192; System entity </li><li>abc.pdf &#x2192; Uploaded document</li></ul><p>The same approach can be used for other system-level document entities.</p><hr><blockquote><strong><em>4. Default SYSTEM Tenant</em></strong></blockquote><p>The application also has a special default <strong>SYSTEM</strong> tenant for documents that are not associated with a normal tenant.</p><p>Its structure is different from both tenant user documents and tenant system documents.</p><p>For example:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/System-TenantExample-2-1.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1536" height="1024" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/System-TenantExample-2-1.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/System-TenantExample-2-1.png 1000w, https://blog.gyri.tech/content/images/2026/08/System-TenantExample-2-1.png 1536w" sizes="(min-width: 720px) 720px"></figure><p>Here:</p><ul><li>SYSTEM &#x2192; Default system tenant</li><li>default-system-1 &#x2192; Default system object ID</li><li>SYSTEM &#x2192; System entity</li><li>abc.pdf &#x2192; Uploaded document</li></ul><p>This provides a dedicated location for application-level system documents.</p><hr><blockquote><strong><em>5. Document Upload Process</em></strong></blockquote><p>Once the destination is determined, the application then creates the corresponding storage path.</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/Document-Uplaod-Flow-1-1.jpg" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1344" height="2016" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/Document-Uplaod-Flow-1-1.jpg 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/Document-Uplaod-Flow-1-1.jpg 1000w, https://blog.gyri.tech/content/images/2026/08/Document-Uplaod-Flow-1-1.jpg 1344w" sizes="(min-width: 720px) 720px"></figure><p>For example, if a user uploads a PROJECT_REPORT, the application identifies:</p><ul><li>Tenant &#x2192; syc</li><li>User &#x2192; User ID</li><li>Entity &#x2192; PROJECT_REPORT</li><li>File &#x2192; abc.pdf</li></ul><p>and creates the corresponding storage path.</p><hr><blockquote><strong><em>6. Benefits of the Folder Structure</em></strong></blockquote><p>The folder structure is designed to provide:</p><p><strong>Tenant Separation -</strong> Each tenant has its own document space.</p><p><strong>User Separation - </strong>User documents are stored under the corresponding user ID.</p><p><strong>System Separation - </strong>System documents are stored separately from user documents.</p><p><strong>Entity Organization - </strong>Documents are grouped according to their configured entity.</p><p><strong>Easy Retrieva</strong>l - The application can determine the document locationbased on its tenant, user/system, entity, and document information.</p><hr><blockquote><strong><em>7. Check or Create Folder</em></strong></blockquote><p>Before uploading a document, the application checks whether the required folder exists.</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/Folder-Create-Flow.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1454" height="1082" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/Folder-Create-Flow.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/Folder-Create-Flow.png 1000w, https://blog.gyri.tech/content/images/2026/08/Folder-Create-Flow.png 1454w" sizes="(min-width: 720px) 720px"></figure><p>This prevents the application from trying to upload a document to a non-existent location.</p><hr><blockquote><strong><em>8. Store Document in Nextcloud</em></strong></blockquote><p>Once the required folder is available, the actual document is uploaded to Nextcloud.</p><p><strong>Application &#x2192; Document Service &#x2192; Nextcloud &#x2192; Tenant/User Folder &#x2192; Actual Document</strong></p><p>Nextcloud is responsible for storing the physical document, while the application manages the document&#x2019;s business context and storage location.</p><hr><blockquote><strong><em>9. Store Document Metadata</em></strong></blockquote><p>After a successful upload, the application stores the document metadata in MongoDB.</p><p>The metadata can contain information such as:</p><ul><li>File name</li><li>File type</li><li>Tenant ID</li><li>User ID</li><li>Entity</li><li>Document path</li><li>Creation information</li></ul><p>This separation allows the application to manage document-related information without storing the actual file in MongoDB.</p><hr><blockquote><strong><em>10. Reactive Processing</em></strong></blockquote><p>The application uses <strong>Spring WebFlux</strong>, so document operations follow a reactive programming model.</p><p><strong>Check Folder  &#x2192;Create Folder  &#x2192; Upload Document  &#x2192;Save Metadata.</strong></p><p>These operations are handled using reactive processing, avoiding unnecessary blocking operations.</p><p>New developers working on this module should therefore follow the reactive approach used throughout the application.</p><hr><blockquote><strong><em>11. Error Handling</em></strong></blockquote><p>A document upload can fail at different stages of the process:</p><p><strong>Validation &#x2192; Folder Creation &#x2192; Nextcloud Upload &#x2192; Metadata Storage in MongoDB</strong></p><p>Common problems include:</p><ul><li>Invalid file</li><li>File size limit exceeded</li><li>Incorrect folder path</li><li>Folder creation failure</li><li>Nextcloud authentication failure</li><li>Document upload failure</li><li>MongoDB failure</li></ul><p>Proper logging and error handling are important for identifying where the upload process failed.</p><p>Sensitive information, such as authentication tokens and confidential document information, should never be written to logs.<a></a></p><hr><blockquote><strong><em>12. Document View and Thumbnail </em></strong></blockquote><p>After storing documents in Nextcloud, the application provides separate APIs for viewing the actual document and displaying its thumbnail.</p><p><strong>1. Document View API</strong></p><p>The Document View API is used when the application needs to display or download the <strong>actual document</strong>.</p><p>For example:</p><pre><code class="language-text">GET /api/v1/document/{id}/view
</code></pre><p>The API identifies the document using its ID, retrieves the document metadata from MongoDB, and uses the stored Nextcloud file path and authentication details to access the actual file.</p><p>This is useful when users need to open or view the complete document.</p><p><strong>2. Document Thumbnail API</strong></p><p>The Thumbnail API is used to display a small preview or icon of a document, such as in document lists, cards, or dashboards.</p><pre><code class="language-text">GET /api/v1/document/{id}/thumbnail
</code></pre><p>The API first retrieves the document metadata and checks whether Nextcloud provides a preview for the file. If a preview is available, the API requests the thumbnail from the Nextcloud preview endpoint.</p><p><strong>Why Use Separate APIs?</strong></p><p>Using separate APIs for the actual document and its thumbnail provides better performance and a cleaner user experience.</p><ul><li><strong>Document View API</strong> &#x2192; Returns the actual/full document.</li><li><strong>Thumbnail API</strong> &#x2192; Returns a small preview image.</li><li><strong>MongoDB</strong> &#x2192; Stores and provides document metadata.</li><li><strong>Nextcloud</strong> &#x2192; Stores the actual document and generates previews.</li><li><strong>Placeholder</strong> &#x2192; Used when a document preview is unavailable.</li></ul><p>The overall flow is:</p><pre><code class="language-text">                                Document ID
                                    &#x2193;
                      Find Document Metadata in MongoDB
                                    &#x2193;
                   Get Nextcloud File Path &amp; Authentication
                                    &#x2193;
                     &#x250C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2510;
                     &#x2502;                             &#x2502;
                     &#x25BC;                             &#x25BC;
              View Document API               Thumbnail API
                     &#x2193;                             &#x2193;
             Nextcloud Actual File           Nextcloud Preview
                     &#x2193;                             &#x2193;
               Full Document                Small Preview / Icon</code></pre><p>This approach avoids loading large documents when only a small preview is required, which helps reduce unnecessary network and resource usage.</p><hr><blockquote><strong><em>Conclusion</em></strong></blockquote><p>The document storage architecture can be summarized as:</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2026/08/Document-Storage-Architecture.png" class="kg-image" alt="Document Upload and Storage Using Spring Boot, Kotlin, and Nextcloud" loading="lazy" width="1122" height="1402" srcset="https://blog.gyri.tech/content/images/size/w600/2026/08/Document-Storage-Architecture.png 600w, https://blog.gyri.tech/content/images/size/w1000/2026/08/Document-Storage-Architecture.png 1000w, https://blog.gyri.tech/content/images/2026/08/Document-Storage-Architecture.png 1122w" sizes="(min-width: 720px) 720px"></figure><p>For the default <strong>SYSTEM</strong> tenant, application-level documents are stored separately:</p><p><code>SYSTEM/default-system-1/SYSTEM/</code></p><p>This architecture provides a clear and organized approach to managing documents across <strong>tenants, users, system entities, and application-level system data</strong>.</p><p>The overall flow is straightforward:</p><p><strong>Tenant Configuration &#x2192; Identify Document Context &#x2192; Build Storage Path &#x2192; Check/Create Folder &#x2192; Upload to Nextcloud &#x2192; Save Metadata in MongoDB</strong></p><p>Understanding this flow provides new developers with a solid foundation for working with the document management module.</p>]]></content:encoded></item><item><title><![CDATA[Installing and Setting Up Oh My Zsh on Ubuntu]]></title><description><![CDATA[<p><strong>Introduction</strong></p><p>After successfully installing Zsh, the next step is often installing Oh My Zsh.<br>Oh My Zsh is a popular open-source framework for managing Zsh configurations. It simplifies customization by providing themes, plugins, aliases, and useful productivity features without requiring extensive manual configuration.<br>This guide explains the prerequisites, installation process,</p>]]></description><link>https://blog.gyri.tech/installing-and-setting-up-oh-my-zsh-on-ubuntu/</link><guid isPermaLink="false">6a2108da5600f10407326bb4</guid><dc:creator><![CDATA[Niranjan Kolwankar]]></dc:creator><pubDate>Wed, 29 Jul 2026 05:25:20 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/06/68747470733a2f2f6f686d797a73682e73332e616d617a6f6e6177732e636f6d2f6f6d7a2d616e73692d6769746875622e706e67.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2026/06/68747470733a2f2f6f686d797a73682e73332e616d617a6f6e6177732e636f6d2f6f6d7a2d616e73692d6769746875622e706e67.webp" alt="Installing and Setting Up Oh My Zsh on Ubuntu"><p><strong>Introduction</strong></p><p>After successfully installing Zsh, the next step is often installing Oh My Zsh.<br>Oh My Zsh is a popular open-source framework for managing Zsh configurations. It simplifies customization by providing themes, plugins, aliases, and useful productivity features without requiring extensive manual configuration.<br>This guide explains the prerequisites, installation process, and initial setup of Oh My Zsh on Ubuntu while breaking down every command used during the process.</p><hr><p><strong>What is Oh My Zsh?</strong></p><p>Oh My Zsh is a community-driven framework built on top of Zsh.<br>It helps users:<br>        &#x2022; Manage Zsh configurations easily<br>        &#x2022; Install and use themes<br>        &#x2022; Enable plugins<br>        &#x2022; Improve terminal productivity<br>        &#x2022; Customize the shell without editing complex configuration files<br>Instead of manually configuring every feature, Oh My Zsh provides a ready-to-use environment that can be customized later.</p><hr><p><strong>Prerequisites</strong></p><p>Before installing Oh My Zsh, the following requirements should be met:</p><p><em><strong>Requirement 1: Zsh Must Be Installed</strong></em><br>Verify that Zsh is installed.<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 the installed version.<br>Example output:<br><em>zsh 5.9</em><br>If a version number is displayed, Zsh is installed successfully.</p><p><em><strong>Requirement 2: Git Must Be Installed</strong></em><br>Oh My Zsh uses Git to download and manage its files.<br>Check whether Git is installed:<br><em>git --version<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>git</em> = Runs the Git executable.<br>   &#x2022; --<em>version</em> = Displays the installed Git version.<br>Example output:<br><em>git version 2.43.0</em><br>If a version number appears, Git is already installed.</p><p><em><strong>Installing Git (If Not Installed)</strong></em><br>If Git is not installed, install it using:<br><em>sudo apt install git -y<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>sudo</em> = Executes the command with administrator privileges.<br>   &#x2022; <em>apt</em> = Ubuntu package manager.<br>   &#x2022; <em>install</em> = Installs a package.<br>   &#x2022; <em>git</em> = Package name.<br>   &#x2022; -<em>y</em> = Automatically confirms installation prompts.<br>This command downloads and installs Git.</p><hr><p><strong>Verifying Git Installation</strong></p><p>After installation, verify Git again:<br><em>git --version</em><br>Successful installation displays the installed version.</p><hr><p><strong>Installing Oh My Zsh</strong></p><p>Once both Zsh and Git are available, Oh My Zsh can be installed.<br>Execute the following command:<br><em>sh -c &quot;$(curl -fsSL </em><a href="https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/?ref=blog.gyri.tech"><em>https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/</em></a><br><em>tools/install.sh)&quot;</em></p><hr><p><strong>Understanding the Installation Command</strong></p><p>At first glance, this command may look complicated. Breaking it into smaller parts makes it easier to understand.</p><p><strong>Part 1: curl</strong><br><em>curl URL<br><strong>What is curl?</strong></em><br>curl is a command-line utility used to transfer data from servers and websites.<br>In this case, it downloads the Oh My Zsh installation script.</p><p><strong>Part 2: curl Options</strong><br><em>curl -fsSL URL<br><strong>Understanding the Options</strong></em><br>   &#x2022; -f = Fails silently if an error occurs.<br>   &#x2022; -s = Silent mode (hides progress information).<br>   &#x2022; -S = Displays errors even when silent mode is enabled.<br>   &#x2022; -L = Follows redirects automatically.<br>These options ensure that the installation script is downloaded cleanly.</p><p><strong>Part 3: Command Substitution</strong><br><em>$(curl -fsSL URL)</em><br>The <strong>$()</strong> syntax is called <strong>command substitution</strong>.<br>It means:<br>Execute the command inside the brackets and use its output.<br>The downloaded installation script becomes the output.</p><p><strong>Part 4: sh -c</strong><br><em>sh -c &quot;command&quot;<br><strong>Understanding the Command</strong></em><br>   &#x2022; sh = Shell interpreter.<br>   &#x2022; -c = Execute the command provided as a string.<br>In this installation process, sh executes the downloaded installation script.</p><p><strong>Complete Flow</strong><br>The entire command performs the following actions:</p><ol><li>Downloads the latest Oh My Zsh installation script from GitHub.</li><li>Passes the script to the shell.</li><li>Executes the installation automatically.</li></ol><hr><p><strong>What Happens During Installation?</strong></p><p>During installation, Oh My Zsh performs several actions automatically:</p><p><em><strong>Creates the Oh My Zsh Directory</strong><br>~/.oh-my-zsh</em><br>This directory contains:<br>        &#x2022; Themes<br>        &#x2022; Plugins<br>        &#x2022; Scripts<br>        &#x2022; Configuration files</p><p><em><strong>Creates a Zsh Configuration File</strong><br>~/.zshrc</em><br>If a .zshrc file already exists, Oh My Zsh creates a backup before making changes.</p><p><em><strong>Changes the Default Theme</strong></em><br>The default theme is usually:<br><em>robbyrussell</em><br>This theme adds useful information such as:<br>        &#x2022; Current directory<br>        &#x2022; Git branch information<br>        &#x2022; Prompt customization</p><hr><p><strong>Verifying Installation</strong></p><p>After installation completes, verify that the Oh My Zsh directory exists.<br><em>ls -la ~/.oh-my-zsh<br><strong>Understanding the Command</strong></em><br>   &#x2022; <em>ls</em> = Lists files and directories.<br>   &#x2022; -<em>l</em> = Detailed view.<br>   &#x2022; -<em>a </em>= Shows hidden files.<br>   &#x2022; <em>~/.oh-my-zsh </em>= Oh My Zsh installation directory.<br>If installation was successful, the directory contents will be displayed.</p><hr><p><strong>Understanding the .zshrc File</strong></p><p>One of the most important files in a Zsh setup is:<br><em>~/.zshrc</em><br>This file controls:<br>        &#x2022; Themes<br>        &#x2022; Plugins<br>        &#x2022; Aliases<br>        &#x2022; Environment variables<br>        &#x2022; Terminal customization<br>Whenever a new terminal session starts, Zsh reads this file and applies the configured settings.</p><hr><p><strong>Reloading Configuration Changes</strong></p><p>Whenever changes are made to .zshrc , they can be applied without restarting the terminal.<br><em>source ~/.zshrc<br><strong>Understanding the Command</strong></em><br>   &#x2022; source = Executes commands from a file in the current shell session.<br>   &#x2022; ~/.zshrc = Zsh configuration file.<br>This command reloads the configuration immediately.</p><hr><p><strong>Benefits of Using Oh My Zsh</strong></p><p>After installation, users gain access to:<br><strong><em>Themes</em></strong><br>Customize the appearance of the terminal.<br><strong><em>Plugins</em></strong><br>Add functionality for tools such as:<br>        &#x2022; Git<br>        &#x2022; Docker<br>        &#x2022; Kubernetes<br>        &#x2022; Node.js<br>        &#x2022; Python<br><strong><em>Better Productivity</em></strong><br>Frequently used commands become easier to execute through aliases and shortcuts.<br><em><strong>Easier Configuration</strong></em><br>Most customizations can be performed by editing a single file:<br><em>~/.zshrc</em></p><hr><p><strong>Key Takeaways</strong></p><p>Installing Oh My Zsh introduces several important Linux concepts:<br>        &#x2022; Git-based software installation<br>        &#x2022; Downloading scripts using curl<br>        &#x2022; Command substitution using $()<br>        &#x2022; Shell configuration files<br>        &#x2022; Theme and plugin management<br>Understanding these concepts provides a solid foundation for further terminal customization.</p><hr>]]></content:encoded></item><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></channel></rss>