<?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:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[mwhyte.dev]]></title><description><![CDATA[I share my thoughts, tips, and experiences on software development, design patterns, and best practices.]]></description><link>https://www.mwhyte.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!rl2G!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fd110ac-391c-4434-b56b-7ddf4377d972_600x600.png</url><title>mwhyte.dev</title><link>https://www.mwhyte.dev</link></image><generator>Substack</generator><lastBuildDate>Tue, 12 May 2026 11:05:41 GMT</lastBuildDate><atom:link href="https://www.mwhyte.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[mwhyte.dev]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[mwhytedev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[mwhytedev@substack.com]]></itunes:email><itunes:name><![CDATA[Michael Whyte]]></itunes:name></itunes:owner><itunes:author><![CDATA[Michael Whyte]]></itunes:author><googleplay:owner><![CDATA[mwhytedev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[mwhytedev@substack.com]]></googleplay:email><googleplay:author><![CDATA[Michael Whyte]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Software Design Principles]]></title><description><![CDATA[A brief overview of commonly used Software Design Principles]]></description><link>https://www.mwhyte.dev/p/software-design-principles</link><guid isPermaLink="false">https://www.mwhyte.dev/p/software-design-principles</guid><dc:creator><![CDATA[Michael Whyte]]></dc:creator><pubDate>Fri, 21 Mar 2025 23:39:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/03e7de93-9525-40bf-a4d4-1f7b3dd2d4f9_1600x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>SOLID</h2><p>The SOLID principles are fundamental to object-oriented design, fostering modular, testable, and adaptable code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7Ak-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7Ak-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 424w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 848w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 1272w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7Ak-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png" width="1456" height="702" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:702,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7Ak-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 424w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 848w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 1272w, https://substackcdn.com/image/fetch/$s_!7Ak-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35cd1522-31d5-450c-8cca-6f1022db28d4_1596x770.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>1. <strong>Single Responsibility Principle (SRP)</strong></h3><blockquote><p>A class should have one and only one reason to change, meaning that a class should have only one job.</p></blockquote><p>This principle ensures that a class has a single responsibility, making it easier to maintain and modify without impacting other parts of the system. For example, a <em>ReportGenerator</em> class should handle report creation, while a <em>ReportSaver</em> class saves reports to storage.</p><p></p><h3>2. <strong>Open/Closed Principle (OCP)</strong></h3><blockquote><p>Objects or entities should be open for extension but closed for modification.</p></blockquote><p>This principle encourages adding new functionality through extension rather than modifying existing code. For instance, if you have a <em>Shape</em> interface, new shapes like <em>Circle</em> or <em>Square</em> can be added without altering the existing implementation.</p><p></p><h3>3. <strong>Liskov Substitution Principle (LSP)</strong></h3><blockquote><p>Derived classes must be substitutable for their base classes</p></blockquote><p>A subclass should not break the behaviour expected from its superclass. For example, if a <em>Bird</em> class has a method <em>fly()</em>, a <em>Penguin</em> subclass shouldn't override it with a "not implemented" exception&#8212;it breaks the principle.</p><p></p><h3>4. <strong>Interface Segregation Principle (ISP)</strong></h3><blockquote><p>A client should never be forced to implement an interface that it doesn&#8217;t use, or clients shouldn&#8217;t be forced to depend on methods they do not use.</p></blockquote><p>Instead of having one large interface, split it into smaller, more specific interfaces. For example, instead of a <em>Vehicle</em> interface with unrelated methods like <em>fly()</em> and <em>drive()</em>, create separate interfaces like <em>FlyingVehicle</em> and <em>DrivingVehicle</em>.</p><p></p><h3>5. <strong>Dependency Inversion Principle (DIP)</strong></h3><blockquote><p>Depend on abstractions, not on concretions</p></blockquote><p>This principle suggests that high-level modules should not depend on low-level modules. Both should depend on abstractions. Dependency injection frameworks often embody this principle.</p><div><hr></div><p></p><h2>DRY (Don&#8217;t Repeat Yourself)</h2><blockquote><p>Every piece of knowledge must have a single, unambiguous, authoritative representation within a system</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CWHU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CWHU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 424w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 848w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 1272w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CWHU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png" width="1296" height="575" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:575,&quot;width&quot;:1296,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CWHU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 424w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 848w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 1272w, https://substackcdn.com/image/fetch/$s_!CWHU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0459354-8349-41ce-b4b8-b6d0299e7a20_1296x575.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Duplication in code leads to inconsistencies and additional maintenance efforts. If you find yourself repeating code, abstract it into reusable functions or modules.</p><div><hr></div><p></p><h2>YAGNI (You Ain&#8217;t Gonna Need It)</h2><blockquote><p>Don&#8217;t implement something until you actually need it</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R2L3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R2L3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 424w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 848w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 1272w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R2L3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png" width="1456" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R2L3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 424w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 848w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 1272w, https://substackcdn.com/image/fetch/$s_!R2L3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dfec0c7-5793-4c50-8c0d-5ca44cad1029_1704x504.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Avoid speculative development. Over-engineering adds unnecessary complexity and often wastes time. Build only what you need for the current requirements.</p><div><hr></div><p></p><h2><strong>KISS (Keep It Simple, Stupid)</strong></h2><blockquote><p>Simplicity is the ultimate sophistication</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3SO1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3SO1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 424w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 848w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 1272w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3SO1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png" width="1456" height="384" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:384,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3SO1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 424w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 848w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 1272w, https://substackcdn.com/image/fetch/$s_!3SO1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac6239c-56bb-45b6-ad46-01bb30f1c7bd_1728x456.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Write code that is simple to read, understand, and maintain. While overly complex solutions may seem clever, they often lead to bugs and technical debt.</p><div><hr></div><p></p><h2><strong>Law of Demeter (LoD)</strong></h2><blockquote><p>Talk only to your immediate friends</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Lyh_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Lyh_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 424w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 848w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 1272w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Lyh_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png" width="1456" height="408" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:408,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Lyh_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 424w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 848w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 1272w, https://substackcdn.com/image/fetch/$s_!Lyh_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5febddce-4165-487c-a01d-7ae1d4c2b45b_1800x504.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A module should only communicate with its direct dependencies and not with the dependencies of those dependencies. For example, avoid chaining method calls like <code>a.getB().getC().doSomething();</code>. Instead, encapsulate such interactions.</p><div><hr></div><p></p><h2><strong>Composition Over Inheritance</strong></h2><blockquote><p>Favor composition over inheritance</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GvR6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GvR6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 424w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 848w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 1272w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GvR6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png" width="1152" height="674" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:674,&quot;width&quot;:1152,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GvR6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 424w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 848w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 1272w, https://substackcdn.com/image/fetch/$s_!GvR6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4989fe1f-b154-4ab0-866f-eaf69747e219_1152x674.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Inheritance often leads to tightly coupled code and an inflexible hierarchy. Composition, on the other hand, promotes reusability by combining behaviours through interfaces or mixins. For example, instead of a monolithic <code>Animal</code> class with dozens of subclasses, create composable behaviours like <code>Swimmer</code>, <code>Flyer</code>, or <code>Walker</code>.</p><div><hr></div><p></p><h2><strong>Encapsulate What Varies</strong></h2><blockquote><p>Identify the aspects of your application that vary and separate them from what stays the same</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZZ65!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZZ65!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 424w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 848w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 1272w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZZ65!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png" width="1456" height="651" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:651,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZZ65!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 424w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 848w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 1272w, https://substackcdn.com/image/fetch/$s_!ZZ65!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f214c33-7cd5-4ff8-adf6-eafb7a960d4d_1562x698.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This principle is fundamental to design patterns like Strategy and Factory. For example, if you have different ways to calculate discounts, encapsulate the logic into separate classes instead of hardcoding it.</p><div><hr></div><p></p><h2><strong>Hollywood Principle</strong></h2><blockquote><p>Don&#8217;t call us, we&#8217;ll call you</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DQ_J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DQ_J!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 424w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 848w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 1272w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DQ_J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png" width="1010" height="344" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:344,&quot;width&quot;:1010,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DQ_J!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 424w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 848w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 1272w, https://substackcdn.com/image/fetch/$s_!DQ_J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8125b174-a1cd-42a1-9077-c3a37cc66082_1010x344.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This principle encourages frameworks or inversion of control, where the framework dictates the flow. For instance, in an event-driven system, the framework invokes your code in response to specific events.</p><div><hr></div><p></p><h2><strong>Program Against Abstractions</strong></h2><blockquote><p>Depend on interfaces or abstract classes rather than concrete implementations</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fb5J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fb5J!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 424w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 848w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 1272w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fb5J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png" width="1156" height="722" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:722,&quot;width&quot;:1156,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fb5J!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 424w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 848w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 1272w, https://substackcdn.com/image/fetch/$s_!fb5J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4bcd549-ce5b-4185-8c89-2d6940fff66f_1156x722.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This principle promotes flexibility and testability. For example, instead of directly using a concrete <em>FileLogger</em> class, depend on a <em>Logger</em> interface, which allows you to swap implementations easily.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.mwhyte.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Books</h2><p>Some recommended books to explore the concepts further:</p><p><a href="http://www.amazon.co.uk/dp/0132350882/ref=nosim?tag=mwhytedev-21">Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin) (Robert C. Martin Series)</a></p><p><a href="http://www.amazon.co.uk/dp/0135957052/ref=nosim?tag=mwhytedev-21">Pragmatic Programmer, The: Your journey to mastery, 20th Anniversary Edition</a></p><p><a href="http://www.amazon.co.uk/dp/1449373321/ref=nosim?tag=mwhytedev-21">Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems</a></p><div><hr></div><h3></h3>]]></content:encoded></item><item><title><![CDATA[Creating Google Cloud Functions With Kotlin]]></title><description><![CDATA[Serverless with Kotlin: Building and Deploying HTTP Functions on GCP]]></description><link>https://www.mwhyte.dev/p/google-cloud-functions-kotlin</link><guid isPermaLink="false">https://www.mwhyte.dev/p/google-cloud-functions-kotlin</guid><dc:creator><![CDATA[Michael Whyte]]></dc:creator><pubDate>Fri, 21 Mar 2025 23:08:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/29993f4f-af09-427e-b488-925fa879156a_1600x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QeWf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QeWf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 424w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 848w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QeWf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96737,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QeWf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 424w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 848w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!QeWf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcb0fa87-71ea-4ec7-abcb-e88ece4692df_1600x1200.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>In May 2020, Google announced that Java 11 was coming to <a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">Google Cloud Functions</a>.</p><p>Naturally, I thought, &#8220;Excellent, but can I write functions in other JVM-based languages like Kotlin?&#8221;</p><p>Thankfully, there is no such limitation. You can write your functions in any JVM language of your choosing.</p><div><hr></div><p>The source code for this example can be found on GitHub:</p><p><a href="https://github.com/mwhyte-dev/kotlin-google-cloud-function">https://github.com/mwhyte-dev/kotlin-google-cloud-function</a></p><div><hr></div><h2>Prerequisites</h2><p>You&#8217;ll need some things to follow along with this example:</p><ul><li><p>Java 11</p></li><li><p>Gradle</p></li><li><p>Access to GCP with a sample <a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects">project created</a></p></li><li><p>gcloud <a href="https://cloud.google.com/sdk/#Quick_Start">installed</a> and authenticated</p></li><li><p>The source code is cloned and imported into your IDE of choice</p></li></ul><div><hr></div><h2>Main Function</h2><p>The main function is a straightforward one to get us started &#8212; a basic HTTP endpoint that will return the string &#8220;FUNCTION COMPLETE&#8221; when triggered:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wD_g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wD_g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 424w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 848w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 1272w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wD_g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png" width="1456" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:189965,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wD_g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 424w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 848w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 1272w, https://substackcdn.com/image/fetch/$s_!wD_g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd3620610-7e44-4513-b5b0-250fc7a57132_1516x1066.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/mwhyte-dev/kotlin-google-cloud-function/blob/main/src/main/kotlin/dev/mwhyte/function/App.kt">App.kt</a></figcaption></figure></div><p></p><p>This class extends <code>HttpFunction</code> from the <a href="https://cloud.google.com/functions/docs/functions-framework">functions-framework</a> library. The service function takes an <code>HttpRequest</code> an <code>HttpResponse</code> object as parameters.</p><p>For non-HTTP ways to <a href="https://cloud.google.com/functions/docs/calling">trigger a cloud function</a>, you can use a <code>RawBackgroundFunction</code> or a typed variant.</p><p>For example: <code>BackgroundFunction&lt;PubSubMessage&gt;</code></p><p>Some other options for triggering functions include Cloud Pub/Sub, Cloud Storage, Cloud Firestore, and various Firebase-based triggers.</p><p>You can find examples of other triggering mechanisms in the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">functions-framework-java</a> readme.</p><div><hr></div><h2>Gradle Build File</h2><p>We use&nbsp;<a href="https://docs.gradle.org/current/userguide/kotlin_dsl.html">Gradle&#8217;s Kotlin DSL</a>&nbsp;to configure our project for this example.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1twj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1twj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 424w, https://substackcdn.com/image/fetch/$s_!1twj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 848w, https://substackcdn.com/image/fetch/$s_!1twj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 1272w, https://substackcdn.com/image/fetch/$s_!1twj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1twj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png" width="1456" height="2114" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2114,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:646776,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1twj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 424w, https://substackcdn.com/image/fetch/$s_!1twj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 848w, https://substackcdn.com/image/fetch/$s_!1twj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 1272w, https://substackcdn.com/image/fetch/$s_!1twj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c09d6a-94a4-4063-a2d3-f9931612567d_2074x3012.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/mwhyte-dev/kotlin-google-cloud-function/blob/main/build.gradle.kts">build.gradle.kts</a></figcaption></figure></div><div><hr></div><h3>Dependencies</h3><p>There are a few critical dependencies.</p><ul><li><p><a href="https://github.com/GoogleCloudPlatform/functions-framework-java">The Functions-framework-</a>API allows us to write lightweight functions that run across many&nbsp;environments, including Google Cloud Functions and Cloud Run.</p></li><li><p>Java-function-invoker enables us to run the function locally for testing.</p></li></ul><div><hr></div><h3>runFunction Task</h3><p>The <code>runFunction</code> task in the Gradle build file triggers the java-function-invoker, which wraps the function and serves it using a Jetty web server.</p><p>To run the function locally, call runFunction using the Gradle wrapper:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cHcl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cHcl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 424w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 848w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 1272w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cHcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png" width="1456" height="406" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:406,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:337146,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cHcl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 424w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 848w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 1272w, https://substackcdn.com/image/fetch/$s_!cHcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9875da06-8253-4153-b5ad-517ddb2325f9_3270x912.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Optionally, you can override some args:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lInd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lInd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 424w, https://substackcdn.com/image/fetch/$s_!lInd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 848w, https://substackcdn.com/image/fetch/$s_!lInd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 1272w, https://substackcdn.com/image/fetch/$s_!lInd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lInd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png" width="1456" height="433" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:433,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111148,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lInd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 424w, https://substackcdn.com/image/fetch/$s_!lInd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 848w, https://substackcdn.com/image/fetch/$s_!lInd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 1272w, https://substackcdn.com/image/fetch/$s_!lInd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97644f4f-1852-4201-9477-ec21d072c28d_1862x554.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>buildFunction Task</h3><p>The buildFunction task in the Gradle build file works with the <a href="https://github.com/johnrengelman/shadow">Gradle Shadow</a> plugin to create a fat jar and copy it to the build/deploy directory. The jar is then ready to be uploaded to GCP Cloud Storage.</p><p>To execute this, use the Gradle wrapper:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!odsw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!odsw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 424w, https://substackcdn.com/image/fetch/$s_!odsw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 848w, https://substackcdn.com/image/fetch/$s_!odsw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 1272w, https://substackcdn.com/image/fetch/$s_!odsw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!odsw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png" width="1456" height="212" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:212,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48733,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!odsw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 424w, https://substackcdn.com/image/fetch/$s_!odsw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 848w, https://substackcdn.com/image/fetch/$s_!odsw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 1272w, https://substackcdn.com/image/fetch/$s_!odsw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07fa8b27-04e4-4064-8957-b108a756d135_2400x350.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2>Deploying with gcloud</h2><p>So you&#8217;ve tested your function locally, built it, and now it's time to deploy. The <a href="https://cloud.google.com/sdk/docs/downloads-interactive">gcloud command-line utility</a> makes deployment easy.</p><p>First, ensure you are deploying to the GCP Project of your choice:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NCHc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NCHc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 424w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 848w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 1272w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NCHc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png" width="1130" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1130,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:23754,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NCHc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 424w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 848w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 1272w, https://substackcdn.com/image/fetch/$s_!NCHc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92ede7c8-5db3-42a1-a52a-4d95bd968cfa_1130x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Then select the region you wish to deploy to :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Bm5q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Bm5q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 424w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 848w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 1272w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Bm5q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png" width="1034" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1034,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22722,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Bm5q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 424w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 848w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 1272w, https://substackcdn.com/image/fetch/$s_!Bm5q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F975124f0-2aeb-40cf-9e59-e3b162629744_1034x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><p>Lastly, deploy the function:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QNn7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QNn7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 424w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 848w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 1272w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QNn7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png" width="1268" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1268,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82857,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QNn7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 424w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 848w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 1272w, https://substackcdn.com/image/fetch/$s_!QNn7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd95718f9-4cdb-472b-9cfc-0f931b25ac74_1268x400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Note: The entry-point argument is the function's fully qualified class name, and the source is the location of our fat jar.</p><p>If you open the GCP console and navigate to Cloud Functions, you&#8217;ll see the function:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YkPU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YkPU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 424w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 848w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 1272w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YkPU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png" width="1456" height="172" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:172,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YkPU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 424w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 848w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 1272w, https://substackcdn.com/image/fetch/$s_!YkPU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c643022-1c46-49e8-bcad-4112cdf90fad_2048x242.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><p>You can open it here and view various information, including the trigger. Alternatively, you can run a <code>describe</code> from gcloud:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0NRB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0NRB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 424w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 848w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 1272w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0NRB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png" width="938" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:938,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19647,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0NRB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 424w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 848w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 1272w, https://substackcdn.com/image/fetch/$s_!0NRB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc8a42c-8f9c-42f8-9094-b02306e61939_938x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>By hitting the trigger URL, you will see the function return the expected response:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!atia!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!atia!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 424w, https://substackcdn.com/image/fetch/$s_!atia!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 848w, https://substackcdn.com/image/fetch/$s_!atia!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 1272w, https://substackcdn.com/image/fetch/$s_!atia!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!atia!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png" width="1456" height="235" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:235,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37684,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.mwhyte.dev/i/159295503?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!atia!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 424w, https://substackcdn.com/image/fetch/$s_!atia!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 848w, https://substackcdn.com/image/fetch/$s_!atia!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 1272w, https://substackcdn.com/image/fetch/$s_!atia!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aace552-de59-47ae-b70a-0fc49f88aca6_1536x248.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.mwhyte.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>References</h2><p>We&#8217;ve seen how easy it is to deploy a Kotlin function to GCP. To explore the topic further, I&#8217;d recommend the following documentation:</p><p><a href="https://github.com/mwhyte-dev/kotlin-google-cloud-function">https://github.com/mwhyte-dev/kotlin-google-cloud-function</a></p><p><a href="https://cloud.google.com/functions/docs">Cloud Run functions documentation | Cloud Run functions Documentation | Google Cloud</a></p><p><a href="https://github.com/GoogleCloudPlatform/functions-framework-java">https://github.com/GoogleCloudPlatform/functions-framework-java</a></p><p><a href="https://github.com/GoogleCloudPlatform/functions-framework-java/issues/35">https://github.com/GoogleCloudPlatform/functions-framework-java/issues/35</a></p>]]></content:encoded></item><item><title><![CDATA[Spring Security Redirect Based on User Roles]]></title><description><![CDATA[Direct users to pages based on spring user roles]]></description><link>https://www.mwhyte.dev/p/spring-security-redirect-based-on-roles</link><guid isPermaLink="false">https://www.mwhyte.dev/p/spring-security-redirect-based-on-roles</guid><dc:creator><![CDATA[Michael Whyte]]></dc:creator><pubDate>Fri, 21 Mar 2025 23:01:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f0790f6e-e025-458f-8559-69ee182b9167_1600x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So far, we've built a basic Spring Boot application, enabled Spring Security, and created a <a href="https://blog.mwhyte.dev/p/basic-login-form-with-spring-security-thymeleaf-and-java">basic login form</a>.</p><p>In the last lesson, we expanded on the first by adding different user roles and showing and hiding front-end content based on these roles <a href="https://blog.mwhyte.dev/p/user-roles-and-thymeleaf-extras">(User Roles and Thymeleaf Extras).</a></p><p>Today, we'll look at redirecting users with different roles to different pages after logging in.</p><div><hr></div><h2>Admin.html</h2><p>Following our previous example, we've created a new HTML file called <em>admin.html</em>. When our admin users log in, we will redirect them to this new page.</p><pre><code><code>  &lt;!DOCTYPE html&gt;
  &lt;html xmlns="&lt;http://www.w3.org/1999/xhtml&gt;" xmlns:th="&lt;http://www.thymeleaf.org&gt;"&gt;

  &lt;head&gt;
      &lt;title&gt;codenerve.com - Welcome!&lt;/title&gt;
      &lt;meta charset="UTF-8"&gt;
      &lt;title&gt;Admin&lt;/title&gt;
      &lt;link href="&lt;https://fonts.googleapis.com/css?family=Open+Sans:400,700&gt;" rel="stylesheet"&gt;
      &lt;link rel="stylesheet" href="css/style.css"&gt;
  &lt;/head&gt;
      &lt;body&gt;
          &lt;h1 th:inline="text"&gt;Hello [[${#httpServletRequest.remoteUser}]]!&lt;/h1&gt;

          &lt;div&gt;
               Custom administrator page.
          &lt;/div&gt;


          &lt;br/&gt;
          &lt;form th:action="@{/logout}" method="post"&gt;
              &lt;input type="submit" value="Sign Out"/&gt;
          &lt;/form&gt;
      &lt;/body&gt;
  &lt;/html&gt;
</code></code></pre><div><hr></div><h2>MvcConfig</h2><p>To serve the new <em>admin.html</em> page, we must add this page to our <em>MvcConfig</em> class.</p><p>As with the previous examples, this is accomplished by creating a class extending <em>WebMvcConfigurerAdapter</em> and overriding the <em>addViewControllers</em> method.</p><p>This time, we are adding all the earlier pages of our app and the new admin page:</p><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/admin").setViewName("admin");
        registry.addViewController("/login").setViewName("login");
    }
}

</code></code></pre><div><hr></div><h2>WebSecurityConfig</h2><p><strong>The Constructor</strong></p><p>To decide what to do when different user roles log in. We've created a new field of type <em>AuthenticationSuccessHandler</em>. We're setting this new configuration bean via constructor injection.</p><p><strong>configure method</strong></p><p>This method is responsible for explicitly overriding and configuring HttpSecurity. We've added two lines from the last example.</p><p>First, we've added a new <em>antMatcher</em> under the <em>authorizeRequests</em> section, and we've told spring security only to allow a user with the <em>&#8220;ADMIN&#8221; </em>role access to all endpoints starting with <em>&#8220;/admin&#8221;</em>:</p><pre><code><code>.antMatchers("/admin").hasRole("ADMIN")
</code></code></pre><p>Secondly, we've added our <em>CustomAuthenticationSuccessHandler</em> under the <em>formLogin</em> section to tell Spring security to ask this <em>CustomAuthenticationSuccessHandler</em> what to do when a successful login occurs:</p><pre><code><code>.successHandler(authenticationSuccessHandler)
</code></code></pre><h3><strong>configureGlobal method</strong></h3><p>The <em>configureGlobal</em> method is our in-memory registry of users. We've added two users. One with the primary <em>USER</em> role and the other with the <em>ADMIN</em> role.</p><p>Full example:</p><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    public WebSecurityConfig(AuthenticationSuccessHandler authenticationSuccessHandler) {
        this.authenticationSuccessHandler = authenticationSuccessHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/css/**", "/images/**", "/favicon.ico").permitAll()
                .antMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .successHandler(authenticationSuccessHandler)
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and().csrf().disable(); // we'll enable this in a later blog post
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("{noop}pass").roles("USER")
                .and()
                .withUser("admin").password("{noop}pass").roles("ADMIN");
    }
}

</code></code></pre><div><hr></div><h2>CustomAuthenticationSuccessHandler</h2><p>As you can see from our sample code below, this class implements Springs <em>AuthenticationSuccessHandler</em> class and overrides the <em>onAuthenticationSuccess</em> method.</p><p>Once a user successfully logs in, the <em>onAuthenticationSuccess</em> method is called, and the user's role is checked. If the user's role is admin, we redirect to the <em>/admin</em> HTTP endpoint; otherwise, we redirect them to the <em>/index</em> endpoint.</p><p>Our <em>MvcConfig</em> takes over and serves the correct HTML page based on the <em>viewController</em> we created previously.</p><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;

@Configuration
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        Set&lt;String&gt; roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());

        if (roles.contains("ROLE_ADMIN")) {
            httpServletResponse.sendRedirect("/admin");
        } else {
            httpServletResponse.sendRedirect("/index");
        }
    }
}

</code></code></pre><div><hr></div><h2>Demo</h2><p>Check <a href="https://github.com/mwhyte-dev/spring-security/tree/3.redirect-based-on-role">out the source code</a>, open the Application class, and right-click to run the demo.</p><p>To start the example, port 8080 must be available on your machine. If it is not, you can change this default in the application.properties file using:</p><pre><code><code>server.port=8081
</code></code></pre><p>Set this to whatever value you wish.</p><p>Alternatively, you can watch this short video:</p><div id="youtube2-jTku174kytg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;jTku174kytg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/jTku174kytg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h2>Unit Testing</h2><p>As always, we've amended and added some additional tests to cover the new functionality:</p><pre><code><code>package dev.mwhyte.spring.sec;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.FormLoginRequestBuilder;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTests {
&#9;@Autowired
&#9;private MockMvc mockMvc;

&#9;@Test
&#9;public void loginWithValidUserThenAuthenticated() throws Exception {
&#9;&#9;FormLoginRequestBuilder login = formLogin()
&#9;&#9;&#9;&#9;.user("user")
&#9;&#9;&#9;&#9;.password("pass");

&#9;&#9;mockMvc.perform(login)
&#9;&#9;&#9;&#9;.andExpect(authenticated().withUsername("user"));
&#9;}

&#9;@Test
&#9;public void loginWithInvalidUserThenUnauthenticated() throws Exception {
&#9;&#9;FormLoginRequestBuilder login = formLogin()
&#9;&#9;&#9;&#9;.user("invalid")
&#9;&#9;&#9;&#9;.password("invalidpassword");

&#9;&#9;mockMvc.perform(login)
&#9;&#9;&#9;&#9;.andExpect(unauthenticated());
&#9;}

&#9;@Test
&#9;public void accessUnsecuredResourceThenOk() throws Exception {
&#9;&#9;mockMvc.perform(get("/css/style.css"))
&#9;&#9;&#9;&#9;.andExpect(status().isOk());
&#9;}

&#9;@Test
&#9;public void accessSecuredResourceUnauthenticatedThenRedirectsToLogin() throws Exception {
&#9;&#9;mockMvc.perform(get("/hello"))
&#9;&#9;&#9;&#9;.andExpect(status().is3xxRedirection())
&#9;&#9;&#9;&#9;.andExpect(redirectedUrlPattern("**/login"));
&#9;}

&#9;@Test
&#9;@WithMockUser
&#9;public void accessSecuredResourceAuthenticatedThenOk() throws Exception {
&#9;&#9;mockMvc.perform(get("/index"))
&#9;&#9;&#9;&#9;.andExpect(status().isOk());
&#9;}

&#9;@Test
&#9;@WithMockUser(roles = "USER")
&#9;public void loginWithRoleUserThenExpectAdminPageForbidden() throws Exception {
&#9;&#9;mockMvc.perform(get("/admin"))
&#9;&#9;&#9;&#9;.andExpect(status().isForbidden());
&#9;}

&#9;@Test
&#9;@WithMockUser(roles = "ADMIN")
&#9;public void loginWithRoleAdminThenExpectAdminContent() throws Exception {
&#9;&#9;mockMvc.perform(get("/admin"))
&#9;&#9;&#9;&#9;.andExpect(status().isOk())
&#9;&#9;&#9;&#9;.andExpect(content().string(containsString("Custom administrator page.")));
&#9;}

&#9;@Test
&#9;public void loginWithRoleUserThenExpectIndexPageRedirect() throws Exception {
&#9;&#9;FormLoginRequestBuilder login = formLogin()
&#9;&#9;&#9;&#9;.user("user")
&#9;&#9;&#9;&#9;.password("pass");

&#9;&#9;mockMvc.perform(login)
&#9;&#9;&#9;&#9;.andExpect(authenticated().withUsername("user"))
&#9;&#9;&#9;&#9;.andExpect(redirectedUrl("/index"));
&#9;}

&#9;@Test
&#9;public void loginWithRoleAdminThenExpectAdminPageRedirect() throws Exception {
&#9;&#9;FormLoginRequestBuilder login = formLogin()
&#9;&#9;&#9;&#9;.user("admin")
&#9;&#9;&#9;&#9;.password("pass");

&#9;&#9;mockMvc.perform(login)
&#9;&#9;&#9;&#9;.andExpect(authenticated().withUsername("admin"))
&#9;&#9;&#9;&#9;.andExpect(redirectedUrl("/admin"));
&#9;}
}

</code></code></pre><p>Here is a quick recap of the Spring test support we're using:</p><ul><li><p><em>@ExtendWith(SpringExtension.class)</em> &#8212; Tells JUnit5 to run unit tests with Spring's testing support</p></li><li><p><em>@SpringBootTest</em> &#8212; Run as spring boot app. i.e. load application.properties and spring beans</p></li><li><p><em>@AutoConfigureMockMvc</em> creates a Test helper class called MockMvc. From this, we can imitate a front-end client making requests to the server.</p></li><li><p><em>@WithMockUser &#8212;</em> Provides the ability to mock certain users. An authenticated user in our case.</p></li><li><p><em>FormLoginRequestBuilder &#8212;</em> A utility class that allows us to create a form-based login request.</p></li></ul><p>Next, we will cover Spring Security's Cross-Site Request Forgery (CSRF) protection.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.mwhyte.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Resources</h2><p><a href="https://github.com/mwhyte-dev/spring-security/tree/3.redirect-based-on-role">GitHub - mwhyte-dev/spring-security at 3.redirect-based-on-role</a></p><p><a href="https://blog.mwhyte.dev/p/basic-login-form-with-spring-security-thymeleaf-and-java">Build a Basic Login Form With Spring Security, Thymeleaf, and Java</a></p><p><a href="https://blog.mwhyte.dev/p/user-roles-and-thymeleaf-extras">Spring Security&#8202;&#8212;&#8202;User Roles and ThymeLeaf Extras</a></p><p><a href="https://spring.io/projects/spring-security">Spring Security</a></p><p><a href="https://www.thymeleaf.org/doc/articles/springsecurity.html">Thymeleaf + Spring Security integration basics - Thymeleaf</a></p>]]></content:encoded></item><item><title><![CDATA[User Roles and ThymeLeaf Extras]]></title><description><![CDATA[Today, we'll look at adding user roles and some excellent features of the Thymeleaf library, such as the ability to show and hide content based on these roles.]]></description><link>https://www.mwhyte.dev/p/user-roles-and-thymeleaf-extras</link><guid isPermaLink="false">https://www.mwhyte.dev/p/user-roles-and-thymeleaf-extras</guid><dc:creator><![CDATA[Michael Whyte]]></dc:creator><pubDate>Tue, 18 Mar 2025 23:46:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/57c27ab3-1f0e-4f7a-bcda-c97b9d16adf3_1600x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><p>The last lesson taught us how to use spring security to build a <a href="https://blog.mwhyte.dev/p/basic-login-form-with-spring-security-thymeleaf-and-java">basic login form</a>.</p><p>Today, we'll be looking at adding user roles and some excellent features of the Thymeleaf library, including the ability to show and hide content based on these roles.</p><div><hr></div><h3>Dependencies</h3><p>As with all Spring boot applications, some starter libraries make adding jars to your classpath easy.</p><pre><code><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="&lt;http://maven.apache.org/POM/4.0.0&gt;" xmlns:xsi="&lt;http://www.w3.org/2001/XMLSchema-instance&gt;"
&#9;&#9; xsi:schemaLocation="&lt;http://maven.apache.org/POM/4.0.0&gt; &lt;http://maven.apache.org/xsd/maven-4.0.0.xsd&gt;"&gt;
&#9;&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

&#9;&lt;groupId&gt;dev.mwhyte&lt;/groupId&gt;
&#9;&lt;artifactId&gt;spring-security-basic&lt;/artifactId&gt;
&#9;&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;

&#9;&lt;name&gt;spring-security-basic&lt;/name&gt;
&#9;&lt;description&gt;An introduction to spring security&lt;/description&gt;

&#9;&lt;parent&gt;
&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
&#9;&#9;&lt;version&gt;2.6.1&lt;/version&gt;
&#9;&lt;/parent&gt;

&#9;&lt;properties&gt;
&#9;&#9;&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&#9;&#9;&lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt;
&#9;&#9;&lt;java.version&gt;17&lt;/java.version&gt;
&#9;&lt;/properties&gt;

&#9;&lt;dependencies&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&#9;&#9;&lt;/dependency&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
&#9;&#9;&lt;/dependency&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
&#9;&#9;&lt;/dependency&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.thymeleaf.extras&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;thymeleaf-extras-springsecurity5&lt;/artifactId&gt;
&#9;&#9;&#9;&lt;version&gt;3.0.4.RELEASE&lt;/version&gt;
&#9;&#9;&lt;/dependency&gt;

&#9;&#9;&lt;!-- TEST --&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
&#9;&#9;&#9;&lt;scope&gt;test&lt;/scope&gt;
&#9;&#9;&lt;/dependency&gt;
&#9;&#9;&lt;dependency&gt;
&#9;&#9;&#9;&lt;groupId&gt;org.springframework.security&lt;/groupId&gt;
&#9;&#9;&#9;&lt;artifactId&gt;spring-security-test&lt;/artifactId&gt;
&#9;&#9;&#9;&lt;scope&gt;test&lt;/scope&gt;
&#9;&#9;&lt;/dependency&gt;
&#9;&lt;/dependencies&gt;

&#9;&lt;build&gt;
&#9;&#9;&lt;plugins&gt;
&#9;&#9;&#9;&lt;plugin&gt;
&#9;&#9;&#9;&#9;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&#9;&#9;&#9;&#9;&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
&#9;&#9;&#9;&lt;/plugin&gt;
&#9;&#9;&lt;/plugins&gt;
&#9;&lt;/build&gt;
&lt;/project&gt;

</code></code></pre><div><hr></div><h3>WebSecurityConfig</h3><p>First, we'll add another user to our application's <em>WebSecurityConfig.configureGlobal method</em>.</p><p>This time, giving them a new role as <em>admin</em>. The existing user has been granted the user role &#8212;<em> roles("USER")</em> :</p><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/css/**", "/images/**", "/favicon.ico").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/index")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and().csrf().disable(); // we'll enable this in a later blog post
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("{noop}pass").roles("USER")
                .and()
                .withUser("admin").password("{noop}pass").roles("ADMIN");
    }
}

</code></code></pre><p>This now gives us two available users to log in with. Each user is configured with a different role that we can now use to hide and show different content.</p><p>The remainder of this class remains unchanged, and the key points can be summarised as:</p><p><strong>authorizeRequests() &#8212; Allows us to configure which resources on our web server to secure.</strong></p><p><br>Our example code shows we have allowed unsecured access to our CSS, images directory, and favicon.</p><p>All other resources are secured and can only be accessed by an authenticated user.</p><p></p><p><em><strong>formLogin()</strong></em><strong> &#8212; Tells Spring Security we wish to use a login form.</strong></p><p><br>We provide the URL we want to redirect to if the authentication is successful, and finally, we permit access to the login and logout endpoints.</p><p></p><p>We are disabling cross-site request forgery protection, which is enabled by default. We will cover this later in the series.</p><div><hr></div><h3>Thymeleaf: Authorize</h3><p>Next, we'll use the Thymeleaf attribute <em>sec:authorize</em> to check user roles before rendering the divs in our <em>index.html</em> page.</p><p>To do this, we first need to add the thymeleaf XML namespace to our&nbsp;<a href="https://github.com/mwhyte-dev/spring-security/blob/2.thymeleaf-extras/src/main/resources/templates/index.html">index.html</a>.</p><pre><code><code>&lt;html xmlns="&lt;http://www.w3.org/1999/xhtml&gt;"
      xmlns:th="&lt;http://www.thymeleaf.org&gt;"
      xmlns:sec="&lt;http://www.thymeleaf.org/extras/spring-security&gt;"/&gt;

</code></code></pre><p>Then, we can use the <em>sec:authorize</em> attribute to check a user&#8217;s roles (what they are permitted to see):</p><pre><code><code>&lt;div sec:authorize="hasRole('ADMIN')"&gt;This content is only shown to administrators.&#167;&#167;&lt;/div&gt;
&lt;div sec:authorize="hasRole('USER')"&gt;This content is only shown to users.&lt;/div&gt;
</code></code></pre><p>As you can see, the attribute <em>sec:authorize</em> is added to each div, and we use the Spring security dialect to check users' Spring security roles. Content will only be rendered if the logged-in user has that role, i.e., <em>hasRole</em> returns true.</p><p>It is important to note that content is not just hidden from view but will not be rendered when our application server returns the page to the browser.</p><div><hr></div><h3>Thymeleaf: Authentication</h3><p>Another useful Thymeleaf feature is the <em>sec:authentication</em> attribute. This attribute can return various security-related metadata.</p><p>In the example below, we can retrieve the user's username and roles and display these in our HTML.</p><pre><code><code>&lt;div&gt;
  User: &lt;span sec:authentication="name"&gt;NOT FOUND&lt;/span&gt; Spring Roles:
  &lt;span sec:authentication="principal.authorities"&gt;NOT FOUND&lt;/span&gt;
&lt;/div&gt;
</code></code></pre><p>For more attribute options, see <a href="https://github.com/thymeleaf/thymeleaf-extras-springsecurity">the Thymeleaf documentation</a></p><div><hr></div><h2>Demo</h2><p>To run the demo, <a href="https://github.com/mwhyte-dev/spring-security/tree/2.thymeleaf-extras">check out the source code</a>, open the Application class and right-click run.</p><p>To start the example, port 8080 must be available on your machine. If it is not, you can change this default in the application.properties file using:</p><pre><code><code>server.port=8081
</code></code></pre><p>Set this to whatever value you wish.</p><p>Alternatively, you can watch this short video:</p><div id="youtube2-khAjDT9szTM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;khAjDT9szTM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/khAjDT9szTM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h3>Unit Testing</h3><p>We've added a few more unit tests to cover this new functionality:</p><pre><code><code>package dev.mwhyte.spring.sec;

import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.FormLoginRequestBuilder;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTests {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void loginWithValidUserThenAuthenticated() throws Exception {
        FormLoginRequestBuilder login = formLogin()
                .user("user")
                .password("pass");

        mockMvc.perform(login)
                .andExpect(authenticated().withUsername("user"));
    }

    @Test
    public void loginWithInvalidUserThenUnauthenticated() throws Exception {
        FormLoginRequestBuilder login = formLogin()
                .user("invalid")
                .password("invalidpassword");

        mockMvc.perform(login)
                .andExpect(unauthenticated());
    }

    @Test
    public void accessUnsecuredResourceThenOk() throws Exception {
        mockMvc.perform(get("/css/style.css"))
                .andExpect(status().isOk());
    }

    @Test
    public void accessSecuredResourceUnauthenticatedThenRedirectsToLogin() throws Exception {
        mockMvc.perform(get("/hello"))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrlPattern("**/login"));
    }

    @Test
    @WithMockUser
    public void accessSecuredResourceAuthenticatedThenOk() throws Exception {
        mockMvc.perform(get("/index"))
                .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(roles = "USER")
    public void loginWithRoleUserThenExpectUserSpecificContent() throws Exception {
        mockMvc.perform(get("/index"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("This content is only shown to users.")))
                .andExpect(content().string(doesNotContainString("This content is only shown to administrators.")));
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    public void loginWithRoleAdminThenExpectAdminSpecificContent() throws Exception {
        mockMvc.perform(get("/index"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("This content is only shown to administrators.")))
                .andExpect(content().string(doesNotContainString("This content is only shown to users.")));
    }

    private Matcher&lt;String&gt; doesNotContainString(String s) {
        return CoreMatchers.not(containsString(s));
    }

}

</code></code></pre><p>We've created a small utility method <em>doesNotContainString</em>. Using this and Hamcrests's containsString method, we can check whether the content is rendered (or not) based on a particular user role.</p><p>Crucially, the spring test annotation <em>@WithMockUser</em> allows us to mock certain users&#8212;an authenticated user with user or admin roles in our scenario.</p><p>Here is a quick recap of the Spring test support we are using:</p><ul><li><p><em>@ExtendWith(SpringExtension.class)</em> - Tells JUnit5 to run unit tests with Spring's testing support</p></li><li><p><em>@SpringBootTest</em> &#8212; Run as spring boot app. i.e. load application.properties and spring beans</p></li><li><p><em>@AutoConfigureMockMvc</em> creates a Test helper class called MockMvc. From this, we can imitate a front-end client making requests to the server.</p></li><li><p><em>@WithMockUser </em>&#8212; It allows us to mock certain users, such as authenticated users.</p></li><li><p><em>FormLoginRequestBuilder</em> &#8212; A utility class that allows us to create a form-based login request.</p></li></ul><p></p><p>Next, we will continue to cover Spring Security's user roles, but this time, we will redirect admins to their admin page (<em>admin.html</em>) and secure this page so that only admin users can access it.</p><div><hr></div><h2>Resources</h2><p><a href="https://github.com/mwhyte-dev/spring-security/tree/2.thymeleaf-extras">GitHub - mwhyte-dev/spring-security at 2.thymeleaf-extras</a></p>]]></content:encoded></item><item><title><![CDATA[Build a Basic Login Form With Spring Security, Thymeleaf, and Java]]></title><description><![CDATA[This short tutorial will show you how to build a basic login form using Spring Boot, Spring Security, and Thymeleaf.]]></description><link>https://www.mwhyte.dev/p/basic-login-form-with-spring-security-thymeleaf-and-java</link><guid isPermaLink="false">https://www.mwhyte.dev/p/basic-login-form-with-spring-security-thymeleaf-and-java</guid><dc:creator><![CDATA[Michael Whyte]]></dc:creator><pubDate>Tue, 18 Mar 2025 23:26:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5574f6a4-8670-4ad3-8f90-d6c19118f8e9_1600x1200.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello Java developers! If you are developing Spring boot-based applications, <a href="https://spring.io/projects/spring-security">Spring Security</a> is the de facto standard for securing your Spring-based applications.</p><p>In this short tutorial, we'll build a basic login form using Spring Boot, Spring Security, and Thymeleaf.</p><p>First, let's briefly cover some project files that are of less interest:</p><ul><li><p><em>Application.java</em> contains our main method and the <em>@SpringBootApplication </em>annotation. The standard way to start a spring boot application.</p></li><li><p>A CSS stylesheet is located under&nbsp;<em>src/main/resources/static/css/</em>to make the demo pretty.</p></li><li><p>Some Thymeleaf HTML templates are under <em>src/main/resources/</em> for demo purposes.</p></li><li><p>Finally, an <em>application.properties</em> file is located under <em>src/main/resources, </em>where we can pass configuration parameters to our spring boot application. A complete list of configuration options can be found <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html">here</a>.</p></li></ul><div><hr></div><h3>Dependencies</h3><p>As with all Spring boot applications, many 'Starter' libraries make it easy to add jars to your classpath:</p><ul><li><p><em>spring-boot-starter-parent</em> &#8212; brings in all the required spring dependencies and manages their versions</p></li><li><p><em>spring-boot-starter-thymeleaf</em> &#8212; adds thymeleaf-spring5 and thymeleaf-extras-java8time dependencies (<a href="http://www.thymeleaf.org/">more on thymeleaf</a>)</p></li><li><p><em>spring-boot-starter-security</em> &#8212; adds spring-security-config, spring-security-web, and spring-aop dependencies</p></li><li><p><em>spring-boot-starter-test</em> &#8212; adds spring-boot-test, junit, <a href="http://hamcrest.org/">hamcrest</a> and <a href="https://site.mockito.org/">mockito</a></p></li><li><p><em>spring-security-test</em> &#8212; adds the ability to mock user and user roles</p></li></ul><div><hr></div><p>Here is the entire pom.xml file:</p><pre><code><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="&lt;http://maven.apache.org/POM/4.0.0&gt;"
  xmlns:xsi="&lt;http://www.w3.org/2001/XMLSchema-instance&gt;"
  xsi:schemaLocation="&lt;http://maven.apache.org/POM/4.0.0&gt; &lt;http://maven.apache.org/xsd/maven-4.0.0.xsd&gt;"&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

  &lt;groupId&gt;dev.mwhyte&lt;/groupId&gt;
  &lt;artifactId&gt;spring-security-basic&lt;/artifactId&gt;
  &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;

  &lt;name&gt;spring-security-basic&lt;/name&gt;
  &lt;description&gt;An introduction to spring security&lt;/description&gt;

  &lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.6.1&lt;/version&gt;
  &lt;/parent&gt;

  &lt;properties&gt;
    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt;
    &lt;java.version&gt;17&lt;/java.version&gt;
  &lt;/properties&gt;

  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- TEST --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
      &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.security&lt;/groupId&gt;
      &lt;artifactId&gt;spring-security-test&lt;/artifactId&gt;
      &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;

  &lt;build&gt;
    &lt;plugins&gt;
      &lt;plugin&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
      &lt;/plugin&gt;
    &lt;/plugins&gt;
  &lt;/build&gt;
&lt;/project&gt;

</code></code></pre><div><hr></div><h3>MvcConfig</h3><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("login");
    registry.addViewController("/index").setViewName("index");
    registry.addViewController("/login").setViewName("login");
  }
}

</code></code></pre><p>The <em>MvcConfig.java</em> class implements Spring's&nbsp;<em>WebMvcConfigurer</em>&nbsp;interface. It allows you to override the method&nbsp;<em>addViewControllers</em>&nbsp;(and others)&nbsp;to configure simple automated controllers. In this example, we have mapped them to our Thymeleaf views (under <em>src/main/resources</em>).</p><div><hr></div><h3>WebSecurityConfig</h3><pre><code><code>package dev.mwhyte.spring.sec.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginPage("/login")
        .defaultSuccessUrl("/index")
        .permitAll()
        .and()
        .logout()
        .permitAll()
        .and().csrf().disable(); // we'll enable this in a later blog post
  }

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .inMemoryAuthentication()
        .withUser("user").password("{noop}pass").roles("USER");
  }
}

</code></code></pre><p>A lot is happening in this class as it contains the main security configuration to enable and configure our basic login form.</p><p>Extending <em>WebSecurityConfigurerAdapter</em>, another abstract base class, allows you to override certain aspects of Spring security's default configuration. In our case, we are going to override the method <em>configure</em>.</p><p></p><h4>configure(HttpSecurity http) method</h4><p>Note the parameter passed to this method. There are several different <em>overloaded</em> configure methods. We will be overriding and configuring <em>HttpSecurity</em><code> </code>specifically</p><ul><li><p><em>authorizeRequests()</em> &#8212; Allows us to configure which resources on our web server to secure. You can see from our example code that we have allowed unsecured access to our CSS directory and requested that all other resources be secured and can only be accessed by an authenticated user.</p></li><li><p><em>formLogin() &#8212; </em>Tells Spring Security we wish to use a login form. We provide the URL we want to redirect to if the authentication is successful, and finally, we permit access to the login and logout endpoints.</p></li><li><p>We are disabling cross-site request forgery protection, which is enabled by default. We will cover this later in the series.</p></li></ul><p></p><h4>configureGlobal method</h4><p><em>This method allows us to autowire an AuthenticationManager</em> class globally throughout our application. We&#8217;ll use a basic in-memory approach with one user and one user role for this example.</p><div><hr></div><h3>Thymeleaf namespace</h3><p>A few things to note in our <em>login.html</em> file:</p><p>The <em>thymeleaf</em> Spring-security namespace:</p><pre><code><code>&lt;html
  xmlns="&lt;http://www.w3.org/1999/xhtml&gt;"
  xmlns:th="&lt;http://www.thymeleaf.org&gt;"
  xmlns:sec="&lt;http://www.thymeleaf.org/thymeleaf-extras-springsecurity3&gt;"
&gt;&lt;/html&gt;

</code></code></pre><p>The <em>thymeleaf</em> action instructs the form to make a POST request to the URL provided (<em>/login</em>):</p><pre><code><code>&lt;form th:action="@{/login}" method="post" class="form login"&gt;&lt;/form&gt;

</code></code></pre><p>The <em>thymeleaf</em> <em>if </em>condition can check for URL parameters, error and log out responses and display content if they return true:</p><pre><code><code>&lt;div class="text--center" th:if="${param.error}"&gt;Invalid username and password.&lt;/div&gt;
&lt;div class="text--center" th:if="${param.logout}"&gt;You have been logged out.&lt;/div&gt;

</code></code></pre><p>Complete list of what's available on the thymeleaf <a href="http://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#handling-the-command-object">website</a></p><div><hr></div><h3>Unit Testing</h3><pre><code><code>package dev.mwhyte.spring.sec;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.FormLoginRequestBuilder;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTests {

  @Autowired
  private MockMvc mockMvc;

  @Test
  public void loginWithValidUserThenAuthenticated() throws Exception {
    FormLoginRequestBuilder login = formLogin()
        .user("user")
        .password("pass");

    mockMvc.perform(login)
        .andExpect(authenticated().withUsername("user"));
  }

  @Test
  public void loginWithInvalidUserThenUnauthenticated() throws Exception {
    FormLoginRequestBuilder login = formLogin()
        .user("invalid")
        .password("invalidpassword");

    mockMvc.perform(login)
        .andExpect(unauthenticated());
  }

  @Test
  public void accessUnsecuredResourceThenOk() throws Exception {
    mockMvc.perform(get("/css/style.css"))
        .andExpect(status().isOk());
  }

  @Test
  public void accessSecuredResourceUnauthenticatedThenRedirectsToLogin() throws Exception {
    mockMvc.perform(get("/hello"))
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrlPattern("**/login"));
  }

  @Test
  @WithMockUser
  public void accessSecuredResourceAuthenticatedThenOk() throws Exception {
    mockMvc.perform(get("/index"))
        .andExpect(status().isOk());
  }
}

</code></code></pre><p><em>@ExtendWith(SpringExtension.class) &#8212; </em>Tells JUnit5 to run unit tests with Spring's testing support</p><p><em>@SpringBootTest &#8212;</em><code> </code>Run as spring boot app. i.e. load <em>application.properties </em>and spring beans</p><p><em>@AutoConfigureMockMvc &#8212; </em>Creates a Test helper class called <em>MockMvc</em>. We can imitate a front-end client making requests to the server from this.</p><p><em>@WithMockUser &#8212;</em><code> </code>Provides the ability to mock certain users&#8212;an authenticated user in our case.</p><p><em>FormLoginRequestBuilder &#8212; </em>A utility class that allows us to create a form-based login request.</p><div><hr></div><h3>Demo</h3><p>To run the demo, you can find the complete code for this example on <a href="https://github.com/mwhyte-dev/spring-security/tree/1.basic-form-login">GitHub</a></p><p>Open the <em>Application.java</em> class and right-click run. The port <em>8080</em> must be available on your machine to start the demo. If it is not, you can change this default in the application.properties file using:</p><pre><code><code>server.port=8081
</code></code></pre><p>Set this to whatever value you wish.</p><p>Alternatively, you can watch this short video:</p><div id="youtube2-aQ6QTQdCfeQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;aQ6QTQdCfeQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/aQ6QTQdCfeQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Next, we will cover Spring Security's user roles and the ability to hide and show content on our site based on the user's entitlements.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.mwhyte.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><div><hr></div><h3>Resources</h3><p><a href="https://github.com/mwhyte-dev/spring-security/tree/1.basic-form-login">GitHub - mwhyte-dev/spring-security at 1.basic-form-login</a></p><p><a href="https://spring.io/projects/spring-security">Spring Security</a></p><p><a href="https://www.thymeleaf.org/doc/articles/springsecurity.html">Thymeleaf + Spring Security integration basics - Thymeleaf</a></p>]]></content:encoded></item></channel></rss>