<?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[Clinton's Substack]]></title><description><![CDATA[Turning Coffee into Code]]></description><link>https://clintonbush.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!rs2B!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F474d5f5b-aa4a-439f-a223-348fb8a329f7_339x339.png</url><title>Clinton&apos;s Substack</title><link>https://clintonbush.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 10 Apr 2026 14:25:31 GMT</lastBuildDate><atom:link href="https://clintonbush.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Clinton Bush]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[clintonbush@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[clintonbush@substack.com]]></itunes:email><itunes:name><![CDATA[Clinton Bush]]></itunes:name></itunes:owner><itunes:author><![CDATA[Clinton Bush]]></itunes:author><googleplay:owner><![CDATA[clintonbush@substack.com]]></googleplay:owner><googleplay:email><![CDATA[clintonbush@substack.com]]></googleplay:email><googleplay:author><![CDATA[Clinton Bush]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Crafting a Course]]></title><description><![CDATA[Learning to Teach, Part 2]]></description><link>https://clintonbush.substack.com/p/crafting-a-course</link><guid isPermaLink="false">https://clintonbush.substack.com/p/crafting-a-course</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Fri, 07 Feb 2025 01:44:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Back-planning</h2><p>I usually function well under pressure, but crafting a year-long Web Development course in two months was daunting. I had no experience teaching in a classroom, no experience teaching teenagers, and no material to work with. I was up the proverbial creek without a paddle, and my canoe was taking on water. The only relevant experience I had came from my time in the Army, where I learned the art of <em>back-planning</em>.</p><p>The process of <em>back-planning</em> involves (1) determining your end goal, (2) determining the more granular goals that enable you to achieve that goal, and (3) repeating the process for each of those goals. You are done when your goals are small enough to be actionable tasks.</p><p>The final consideration is <em>feasibility</em>. Is it feasible to deliver the curriculum, assign the tasks, perform the assessments, and expect the students to grasp the planned material in the available time?</p><h2>Teaching Strategies</h2><p>I have read a number of books on teaching, but the most helpful was <em>Your First Year As a High School Teacher: Making the Transition from Total Novice to Successful Professional</em> by Lynne Rominger, Suzanne Packard Laughrea, and Natalie Elkin (I provide a link to this book on Amazon at the end.) It didn&#8217;t delve into pedagogy, learning styles, psychology, or any of those high-browed topics. Instead, it is a book that teaches the mechanics of being a teacher. It starts at the very beginning: landing the job and signing a contract. Then, it walks you through every aspect of the profession, including human resources, payroll, preparing your classroom, creating materials and assessments, dealing with the students, talking with parents, and more. Much of this was not pertinent to my specific situation (working part-time), but it is a great book.</p><p>My first revelation from this book was the importance of decorating your classroom. I am an engineer by trade and, generally speaking, consider decorations to be frivolous. Every chapter in the book includes a number of asides called &#8220;Teacher&#8217;s Rules,&#8221; and one that particularly caught my attention was:</p><blockquote><p>Whatever you do, avoid <em>not decorating</em> and leaving your walls and bulletin boards completely devoid of any personality. The sterile environment will turn kids off. Your goal is to create a warm environment that welcomes kids and makes them feel at home in your room and yet doesn&#8217;t distract them from their work. (Rominger et. al., ch. 4)</p></blockquote><p>Chapter Seven&#8217;s explanation of state curriculum frameworks was another helpful lesson from this book. I&#8217;ll delve into this later, but suffice it to say that each state has a library of course curricula to guide lesson planning. Most (maybe all?) publish these online. This chapter also emphasizes the value of asking other teachers to share their curricula.</p><blockquote><p>Don&#8217;t reinvent the wheel! Good teachers are willing to share their curriculum work with others so that no one has to duplicate existing work. Why spend two hours creating a 100-question multiple-choice test when three usable tests are already in existence? (Rominger et. al., ch. 7)</p></blockquote><p>While this was not particularly helpful in my case (because web development had never been taught at my school), borrowing could save you a tremendous amount of time. The authors do note that you should take the time to adapt whatever you borrow to your own teaching style.</p><p>Chapter Eight explores lesson planning. The book does a commendable job of explaining back-planning and how learning objectives, outcomes, and assessments factor in. It also includes several examples that exemplify project plans and lesson plans. As a final note, they provide advice on adapting your plans to students having difficulty grasping the concept. Personally, I found that leaving a couple of &#8220;floating&#8221; weeks is helpful. This allowed me to spend some additional time on topics the students found particularly difficult. Next year, I can adjust my overall schedule based on my experiences and leave only a single floating week.</p><p>Finally, I found Chapter 11 particularly helpful because, while I felt confident in my course plans and mastery of the material, I was completely inexperienced with teaching high schoolers. The social aspects of teaching (i.e., classroom management) weighed most heavily on my mind. The authors really shined here. They began the chapter by describing the common teacher personalities, moved on to approaches for developing a good rapport with your students, and ended with helpful advice on dealing with trying situations. If you are new to teaching and nothing else I have said convinced you to buy this book, I hope this does. <strong>Chapter 11 is invaluable.</strong></p><h2>Developing the Curriculum</h2><p>Public schools will provide teachers with a curriculum and accompanying texts. Many private schools also have preselected textbooks and courses. My situation was quite different, however, because I had to teach the first-ever offering of an elective at a private school and had no budget for purchasing course material. <em>Obviously, my first stop was Google.</em></p><p>I found many paid Web Development courses and curricula but few freely available ones. Of the free ones I found, these were the most promising:</p><ul><li><p>Harvard University&#8217;s CS50 Web: <a href="https://cs50.harvard.edu/web/2020/">https://cs50.harvard.edu/web/2020/</a></p></li><li><p>IBM&#8217;s Web Development Course: <a href="https://skillsbuild.org/students/course-catalog/web-development">https://skillsbuild.org/students/course-catalog/web-development</a></p></li><li><p>Mozilla Developer Network&#8217;s: <a href="https://developer.mozilla.org/en-US/curriculum/">https://developer.mozilla.org/en-US/curriculum/</a></p></li></ul><p>Harvard&#8217;s offering is tailored to highly motivated, self-taught individuals. It <em>can</em> be taught by high school teachers, and Harvard has created online communities where you can find support, but I did not find it particularly well-suited to these purposes.</p><p>IBM&#8217;s offering is quite impressive. It is tightly integrated with IBM&#8217;s learning management system (SkillsBuild), offers rubrics, PowerPoints, prebuilt activities, and much more, and includes courses covering a wide range of IT and Computer Science subjects. IBM offers all of this free of charge to individual students and verified schools.</p><p>Finally, Mozilla has a very extensive curriculum for web development that covers HTML5, CSS3, and JavaScript. However, it lacks PowerPoints, videos, rubrics, and other such material. Despite these shortcomings, I chose to use its material to familiarize my students with the Mozilla Developer Network website. MDN is the authoritative source for technical data on any of the web standards, and every developer should be proficient in navigating the site. Furthermore, their curriculum is relatively high-level and permits a lot of freedom in how one delivers it. </p><p>Having a list of things to teach wasn&#8217;t enough; I also wanted to ensure my course met or exceeded established standards. Most states publish their curriculum standards online. Georgia&#8217;s can be found at the Georgia Department of Education&#8217;s (GaDoe) SuitCase website: <a href="https://case.georgiastandards.org/">https://case.georgiastandards.org/</a>. To verify my course&#8217;s compliance with these standards, I created a matrix that mapped each Georgia standard to items from MDN&#8217;s curriculum (fig. 1), thereby ensuring my class would meet the state standards.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TMrA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TMrA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 424w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 848w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 1272w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TMrA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png" width="728" height="365.77560975609754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1030,&quot;width&quot;:2050,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:235807,&quot;alt&quot;:&quot;weekly_plans_page_1.png&quot;,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="weekly_plans_page_1.png" title="weekly_plans_page_1.png" srcset="https://substackcdn.com/image/fetch/$s_!TMrA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 424w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 848w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.png 1272w, https://substackcdn.com/image/fetch/$s_!TMrA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec83772-c1dc-404c-8396-178efbc42a59_2050x1030.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">Fig. 1: Year-long Lesson Plans with Mappings to Georgia Standards</figcaption></figure></div><p>Some of the state curriculum requirements did not make sense, either for my course or for the school. For example, <em>Course Standard 1</em> focuses on employability and communication skills. These will be met either indirectly during the course via presentation assignments or have been thoroughly satisfied in other classes because the school follows a classical curriculum that emphasizes such skills.</p><p>Another instance where I trimmed out some of the state curriculum is <em>Course Standard 6: Use a server-side language to build a multi-page website incorporating a web form, at least two templates with shared portions, and a data-driven home page (Server Side Languages).</em> There is simply no way to fit this into a year-long course while thoroughly teaching the rest of the curriculum. After reading <em>Course Standard 6</em> and choosing to omit it, I renamed my course to <em>Front-end </em>Web Development. Course Standard 6 could itself be an entire year-long course titled <em>Backend </em>Web Development.</p><h2>Creating Materials to Present the Course</h2><p>I won&#8217;t spend much time on this because I don&#8217;t find building PowerPoints and handouts particularly interesting. However, I will point out one extremely effective decision I made: cheat sheets. During my time in the Army, I attended a lot of training, most of which was unbelievably compressed because of time constraints. Nearly every course culminated in a final field exercise where Soldiers applied all of the classroom training they had received. Because of the compressed nature of the course, we were always encouraged to keep &#8220;cheat sheets&#8221; in our hip pockets for reference during the exercises. Often, these would be similar to an aviation checklist where steps for certain activities (maybe programming a SINCGARS crypto radio or establishing a satellite connection on a Command Post Node) were recorded. Another classic example is keeping a blank 9-line MEDIVAC template in your pocket so you don&#8217;t forget to transmit critical information when under stress.</p><p>I only saw my students twice a week for one hour each session, hardly enough to personally deliver the entire body of material. So, I followed the Army&#8217;s example and created &#8220;Weekly Companions&#8221; for the students that summarized the least they needed to know. Here are a few pages from one of these weekly summaries:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8nE1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8nE1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 424w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 848w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 1272w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8nE1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png" width="764" height="325" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:325,&quot;width&quot;:764,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;cb512cb6b37842099fd276bd8320c78e.png&quot;,&quot;title&quot;:&quot;cb512cb6b37842099fd276bd8320c78e.png&quot;,&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="cb512cb6b37842099fd276bd8320c78e.png" title="cb512cb6b37842099fd276bd8320c78e.png" srcset="https://substackcdn.com/image/fetch/$s_!8nE1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 424w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 848w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.png 1272w, https://substackcdn.com/image/fetch/$s_!8nE1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58663ae2-5a8a-4662-98e6-79bef2b269b7_764x325.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">Fig. 2: Page 1 of a <em>Weekly Companion</em> Summarizing Material Students Should Know</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RnKY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RnKY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 424w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 848w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 1272w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RnKY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png" width="738" height="422" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:422,&quot;width&quot;:738,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;d6f731ca79740c0a25bf6bad3c9ce53f.png&quot;,&quot;title&quot;:&quot;d6f731ca79740c0a25bf6bad3c9ce53f.png&quot;,&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="d6f731ca79740c0a25bf6bad3c9ce53f.png" title="d6f731ca79740c0a25bf6bad3c9ce53f.png" srcset="https://substackcdn.com/image/fetch/$s_!RnKY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 424w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 848w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.png 1272w, https://substackcdn.com/image/fetch/$s_!RnKY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24bb10d8-d62a-410c-ac8d-ff3fc883c74f_738x422.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">Fig. 3: Appendix of a Weekly Companion Defining Important HTML5 Tags</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q1I_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q1I_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 424w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 848w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q1I_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png" width="745" height="408" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:408,&quot;width&quot;:745,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;252797e3c57000e1a254ed4fb186cab7.png&quot;,&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="252797e3c57000e1a254ed4fb186cab7.png" title="252797e3c57000e1a254ed4fb186cab7.png" srcset="https://substackcdn.com/image/fetch/$s_!Q1I_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 424w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 848w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1I_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32ca5777-ed37-4e35-8a24-b0d5c38d0dd8_745x408.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">Fig. 4: Appendix of a Weekly Companion Defining and Demonstrating CSS3 Syntax</figcaption></figure></div><p>I also permitted students to use the MDN website when taking weekly exams. The tech industry changes so rapidly that I find it unreasonable and pointless to expect students to memorize all of the content. Instead, I expect them to be able to find the answers they need and intelligently apply that information.</p><h2>Creating Practical Assignments</h2><p>My next task was creating formative assessments of their skills. Because it is a coding class, they need to use coding tools. In this case, GitHub was the perfect solution. Not only should future web developers be proficient with <code>git,</code> but GitHub also offers a relatively obscure educational platform: GitHub Classroom. GitHub Classroom permits creating assignments (essentially a repository template), scoring those assignments, and even automating the grading process using automated tests and GitHub Actions. While I never got the integration to work just right, it can also connect directly to your Learning Management System for importing grades.</p><p>I want to call out one very important practice I follow and recommend: rubrics. If possible, <em>always</em> provide rubrics with assignments and <em>always</em> use those rubrics when grading. They give students clear guidance on what is expected and enable you to grade submissions objectively.</p><p>This is a rubric for an assignment that required students to use HTML and CSS to create a student ID:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!v_CG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v_CG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 424w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 848w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 1272w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v_CG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png" width="841" height="738" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:738,&quot;width&quot;:841,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a9e6d22fb686d0c20a358e72eb18283b.png&quot;,&quot;title&quot;:&quot;a9e6d22fb686d0c20a358e72eb18283b.png&quot;,&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="a9e6d22fb686d0c20a358e72eb18283b.png" title="a9e6d22fb686d0c20a358e72eb18283b.png" srcset="https://substackcdn.com/image/fetch/$s_!v_CG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 424w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 848w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.png 1272w, https://substackcdn.com/image/fetch/$s_!v_CG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F153835bf-5071-4f63-b3c5-41d230ee8f38_841x738.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">Fig. 5: Rubric for a Web Development Assignment</figcaption></figure></div><h2>Creating Summative Assessments</h2><p>I did not place a lot of importance on exams in the class; in fact, I weighted them far less than the performance assignments. Instead, I created the exams to serve as a final review of the material covered each week. I created the exams in Google Forms, allowed the students to complete them outside of class, and encouraged them to use the MDN website and any material I provided in class (e.g., presentations, hand-outs, etc.) This may seem unorthodox, but knowing how to find and apply references is more important than rote memorization. I have found this to be true during my 14 years as a professional developer.</p><p>Note that the tests may have been &#8220;open book&#8221; and &#8220;take home,&#8221; but they were not easy. I made most of the questions scenario-based such that they required critical thinking and problem-solving to come to the right answer. I also made an attempt to inject a bit of humor where possible.</p><p>Here are a few examples:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JDsw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JDsw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 424w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 848w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 1272w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JDsw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png" width="779" height="498" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:498,&quot;width&quot;:779,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;b438380dc07f86d6f5c708c1154ed069.png&quot;,&quot;title&quot;:&quot;b438380dc07f86d6f5c708c1154ed069.png&quot;,&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="b438380dc07f86d6f5c708c1154ed069.png" title="b438380dc07f86d6f5c708c1154ed069.png" srcset="https://substackcdn.com/image/fetch/$s_!JDsw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 424w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 848w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.png 1272w, https://substackcdn.com/image/fetch/$s_!JDsw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dc0bf2a-afef-44cd-bc4c-3b6bf5053fa7_779x498.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">Fig. 6: Example Scenario-based Question</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LG_E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LG_E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 424w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 848w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 1272w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LG_E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png" width="779" height="376" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcc2b746-6033-4853-b756-776a3f43d850_779x376.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:376,&quot;width&quot;:779,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;aef00f19383a8dfcee050dd5d3684be3.png&quot;,&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="aef00f19383a8dfcee050dd5d3684be3.png" title="aef00f19383a8dfcee050dd5d3684be3.png" srcset="https://substackcdn.com/image/fetch/$s_!LG_E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 424w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 848w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.png 1272w, https://substackcdn.com/image/fetch/$s_!LG_E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc2b746-6033-4853-b756-776a3f43d850_779x376.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">Fig. 7: Example Scenario-based Question</figcaption></figure></div><h2>Organizing and Planning</h2><p>Having a course&#8217;s lessons, content, assignments, and exams created is a great start, but it is all for naught if you cannot properly schedule it into the school year. I decided to follow the pedagogical standard of lesson plans and unit plans. Fortunately, the MDN curriculum offered a great outline for the course that I used as my starting point.</p><p>Somewhat counterintuitively, I created my lesson plans first. I organized them as a week-by-week schedule for the entire class. I was only able to plan in this order because MDN provided such a great foundation.</p><p>Below is one page from my schedule/lesson plans. Each week outlines the material covered, assignments due, and links to supplementary material like videos. I also mapped each week to the Georgia Standards it satisfies.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QyCf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QyCf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 424w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 848w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 1272w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QyCf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png" width="1008" height="683" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:683,&quot;width&quot;:1008,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;18c693d13e365ad455211f1da18fed16.png&quot;,&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="18c693d13e365ad455211f1da18fed16.png" title="18c693d13e365ad455211f1da18fed16.png" srcset="https://substackcdn.com/image/fetch/$s_!QyCf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 424w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 848w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.png 1272w, https://substackcdn.com/image/fetch/$s_!QyCf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa925fcf-c0a0-4a8f-8d12-dcce8ee24ff8_1008x683.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">Fig. 8: Lesson Plans for Weeks 2 and 3</figcaption></figure></div><p>When I first started this document, I quickly noticed a pattern where several weeks would revolve around a common theme. For example, in the image above, you can see that Week 2 is &#8220;Part 2&#8221; of the &#8220;Web Development&#8221; theme. Weeks 1 and 2 provide a basic introduction to the concepts covered by the course and the tools the students will be using. I drew three immediate conclusions from this:</p><ol><li><p>They share a common theme (obviously)</p></li><li><p>A single summative assessment would be sufficient</p></li><li><p>While I estimate it will take two weeks to cover the material, it would not confuse the students to finish lesson 1 in week 2 or start lesson 2 in week 1, if necessary</p></li></ol><p>It follows that Weeks 1 and 2 should logically comprise a unit of instruction. Elise Phillips at <a href="https://www.WillToTeach.com">The Will to Teach</a> wrote two excellent blog posts, one on Unit Plans and one on Lesson Plans. She emphasized the importance of unit plans in creating coherent instruction so students acquire a holistic (and unfragmented) understanding of a topic or concept.</p><h2>Conclusion</h2><p>I do not claim to be a professional educator or even an experienced layman. I did, however, learn a lot about course development. My goal for this post was to share some of the techniques, resources, and tools I used to accomplish a seemingly impossible goal of building a quality, year-long course in two months.</p><p>In summary:</p><ul><li><p>Back-plan: figure out what you want students to walk away knowing and plan what it takes to get there</p></li><li><p>Re-use: don&#8217;t reinvent the wheel &#8212; use the web, other teachers&#8217; materials, etc.</p></li><li><p>Group like things: organize lessons that share a common theme into holistic units</p></li><li><p>Avoid dogmatism: don&#8217;t do things a certain way just because they&#8217;ve always been done that way (e.g., maybe open book, open notes, take-home tests are okay)</p></li><li><p>Rubrics: provide them with every assignment and use them to grade &#8212; they keep things fair and avoid many arguments with students over the justness of a grade</p></li></ul><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://clintonbush.substack.com/p/into-the-classroom&quot;,&quot;text&quot;:&quot;Read Part 1, \&quot;Into the Classroom\&quot;&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://clintonbush.substack.com/p/into-the-classroom"><span>Read Part 1, "Into the Classroom"</span></a></p><div><hr></div><p>Phillips, E. (n.d.). <em>Creating Effective Unit Plans: A Guide for Teachers</em>. The Will to Teach. Retrieved February 4, 2025, from <a href="https://willtoteach.com/creating-effective-unit-plans-a-guide-for-teachers/">https://willtoteach.com/creating-effective-unit-plans-a-guide-for-teachers/</a></p><p>Rominger, L., Packard, S., &amp; Elkin, N. (2009). <em>Your first year as a high school teacher&#8239;: making the transition from a total novice to a successful professional.</em> Three Rivers Press. <a href="https://www.amazon.com/Your-First-Year-School-Teacher-ebook/dp/B002361KXU">https://www.amazon.com/Your-First-Year-School-Teacher-ebook/dp/B002361KXU</a></p>]]></content:encoded></item><item><title><![CDATA[Into the Classroom]]></title><description><![CDATA[Learning to Teach, Part 1]]></description><link>https://clintonbush.substack.com/p/into-the-classroom</link><guid isPermaLink="false">https://clintonbush.substack.com/p/into-the-classroom</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Wed, 04 Dec 2024 00:51:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/07933e72-46e2-4e80-b3d8-e67b9a3cdae4_1600x899.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Eleven-year-old me sat in front of a 1998 Hewlett-Packard, typing out multiple-choice tests on different topics related to HTML 4. It was another late night, as that was the only time I had to devote to my interests, which were (1) building my Harry Potter fan site and (2) preparing course materials I would never deliver. I went so far as to build an early form of eLearning, a website compiled into Microsoft&#8217;s CHM (Compiled HTML) format. It contained all of the material my tests covered and a helpful index.</p><div class="pullquote"><p>I had spent 13 years in the Army Reserve, spoken to hundreds of Soldiers many times, pitched multi-million dollar Department of Defense contracts, and done my fair share of public speaking. But teenagers terrified me!</p></div><p>Becoming a career educator was never in my plans, but I always had a desire to teach others about my passions: web and software development. Twenty-one years later, I accepted my first job as an educator&#8212;a part-time, online teaching position at a local community college. This only served to whet my appetite for teaching because most online adjunct positions require &#8220;facilitators,&#8221; not teachers.</p><p>I spent nearly two years at that college. They graciously allowed professors to add content and assignments <em>to the course</em>; <em>only you could not remove anything</em>. I made the most of that policy with my &#8220;Introduction to Computers&#8221; class.</p><p>Being located in a small South Georgia town meant most of my students were working adults, many of whom were seniors. While the provided content covered basic Microsoft Office applications, it had many gaps, including subjects like navigating the web, compressing files using ZIP, working with PDFs, identifying trustworthy sources of information, and other essential skills. I added these to the course as small additional assignments and extra credit assignments.</p><p>Still, I wanted to play a more active role in crafting my courses. I did not want to facilitate; I wanted to teach! I left that college and went to work for Southern New Hampshire University, teaching online computer science courses. I was very excited about working in a university setting and instructing <em>computer science</em> courses. Sadly, my excitement was short-lived. I found SNHU even more restrictive than the college. Their course content was totally immutable. I could not even correct assignments or course content that was blatantly wrong (which created a headache for the students and me).</p><p>My fondest moments at SNHU were those rare occasions when a student requested office hours over Microsoft Teams&#8212;for a few short minutes, I actually <em>taught</em>. SNHU&#8217;s format is excellent for self-driven, distance learners, and the school pays quite well. However, it is utterly prohibitive to any teacher-student relationship beyond the weekly discussions and assignment feedback.</p><p>After three years at SNHU, it was time for me to find something else.</p><p>During the Summer of 2023, my wife and I enrolled our 10-year-old son in a local Christian school that followed a classical curriculum. We had grown tired of public education&#8217;s growing ratio of students to teachers, the increasingly progressive nature of its course material, and the never-ending exchange of one unproven educational approach for another. Coming out of the COVID era, like private schools across the nation, this school had seen a boon in enrollment numbers and a growing demand for a greater variety of subjects offered.</p><p>A few months later, a close friend of mine, John, told me he was in talks with the headmaster about taking a full-time position teaching marketing, digital graphics, and other related subjects. Like me, he had always been drawn to education. John ran a successful marketing agency for 15 years, often with more business than one man could handle. Sadly, the COVID economy drastically reduced the demand for tailored marketing strategies in our area, and his company never fully recovered. Around May of 2024, John accepted a full-time job as both an electives teacher and the Dean of Students. As you can imagine, this piqued my interest!</p><p>That August, I decided to take a chance and try my hand at teaching high school. I will not lie and tell you I was 100% comfortable with the idea. In fact, the thought of managing a classroom of teenagers terrified me. I had spent 13 years in the Army Reserve, spoken to hundreds of Soldiers many times, pitched multi-million dollar Department of Defense contracts, and done my fair share of public speaking. But <em>teenagers</em> terrified me! Regardless, I wanted to do it.</p><p>First, I asked my full-time employer if I could shape my schedule to allow two hours away, two afternoons per week. Graciously, they told me I was free to follow my dreams as long as I gave them 40 hours per week. Riding that high, I immediately fired my r&#233;sum&#233; off to the headmaster and asked if there might be a part-time position. He responded by saying that the school was very excited about the prospect of offering more vocational classes. Interestingly, he was also excited to add a few additional male teachers to the faculty. With the addition of John, their number of male teachers had risen to a staggering total of two.</p><p>By this time, the school had already begun its Fall semester, but they were so excited about adding another elective that they told me I would start in October, the beginning of their second quarter.</p><p>Needless to say, I had a lot of work to do over the next two months! </p><p><strong>In the next part of this series, I will tell you how I organized and crafted a year-long web development course that prepares students as well as any boot camp and is aligned with Georgia&#8217;s state curriculum!</strong></p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://clintonbush.substack.com/p/crafting-a-course&quot;,&quot;text&quot;:&quot;Read Part 2, \&quot;Crafting a Course\&quot;&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://clintonbush.substack.com/p/crafting-a-course"><span>Read Part 2, "Crafting a Course"</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Microservices Anti-patterns, Part 3]]></title><description><![CDATA[Avoiding Anti-patterns when Migrating from a Monolith to Microservices]]></description><link>https://clintonbush.substack.com/p/microservices-anti-patterns-part-ccd</link><guid isPermaLink="false">https://clintonbush.substack.com/p/microservices-anti-patterns-part-ccd</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Sat, 15 Jun 2024 00:03:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bMwE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Are Microservices the Right Path?</h2><p>Splitting monolithic applications into microservices is likely the most common source of microservices, a natural and appropriate state of affairs. In fact, I would argue that starting a system as a monolith is more appropriate than jumping straight to microservices. There is nothing inherently wrong with monolithic architectures. They are often simpler to maintain, easier to reason about, easier to deploy, and require far less infrastructure. Therefore, make the most of your monolith before going down this road.</p><h3>Squeeze all the Goodness out of Your Monolith</h3><p>Teams can make meaningful improvements without going to microservices. Some examples include (Richardson, 2020):</p><ul><li><p>Optimize your development process.</p><ul><li><p>Are teams actually <em>agile</em> or just &#8220;doing&#8221; <em>agile</em>?</p></li><li><p>Do developers have the right tools?</p></li><li><p>Are business processes getting in the way?</p></li></ul></li><li><p>Optimize your CI/CD processes. Is everything automated?</p></li><li><p>Improve team autonomy.</p><ul><li><p>Is the system modularized?</p></li><li><p>Can Maven/Gradle subprojects be used to allow independent development?</p></li><li><p>Are teams cross-functional? Are there silos that need to be torn down?</p></li></ul></li><li><p>Can obsolete system components be replaced with modern alternatives?</p></li><li><p>Can the monolith be refactored to allow horizontal scaling?</p><ul><li><p>Can a caching database (e.g., Redis) be used for session sharing?</p></li><li><p>Can the system migrate to a stateless API?</p></li><li><p>Is an API gateway being used?</p></li><li><p>Is the front end separated from the back end?</p></li></ul></li></ul><h3>Will Microservices Solve Your Problems?</h3><p>Before splitting your monolith, ensure doing so is the best available option. As I wrote at the end of the last article, determine the problems that need to be solved, know the issues that microservices can solve, and ensure sufficient overlap exists to justify moving to microservices. If the answer to this question isn&#8217;t immediately apparent, take a few minutes to read this article from Segment.com: <a href="https://segment.com/blog/goodbye-microservices/">Goodbye Microservices: From 100s of Problem Children to 1 Superstar</a>. They started with microservices and decided a monolith worked better.</p><h2>An Overview of the Process</h2><p>Assuming you made the most of your monolith, found microservices offer some solutions to your problems, and decided to move forward, it&#8217;s time to form a plan. Broadly, I propose you employ the <em>Strangler Fig Pattern</em>, described by Chris Richardson (n.d.), while mixing in a healthy dose of <em>Domain-Driven Design</em>. This process comprises six steps:</p><ol><li><p>Model the business processes of the monolith</p></li><li><p>Identify <em>bounded contexts</em> and prioritize them for extraction</p></li><li><p>Extract the first context&#8217;s domain model and logic into a new module</p></li><li><p>Separate the New Module with an Anti-corruption Layer</p></li><li><p>Extract the module into a new service and run that service in parallel with the monolith to confirm it functions correctly</p></li><li><p>Remove the module from the monolith, leaving behind only an <em>Anti-corruption Layer</em> that leverages the new service&#8217;s APIs</p></li></ol><h3>Step 1: Model the Business Processes</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bMwE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bMwE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 424w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 848w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 1272w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bMwE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png" width="1456" height="916" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:916,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:349152,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bMwE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 424w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 848w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.png 1272w, https://substackcdn.com/image/fetch/$s_!bMwE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4bd2079-a883-47f9-a769-ca102d315504_2506x1576.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">Figure 1. Event Storming &#8212; <a href="https://spring.io/blog/2018/04/11/event-storming-and-spring-with-a-splash-of-ddd">Spring.io</a></figcaption></figure></div><p>It is impossible to make an intelligent decision about where to start decomposing a monolith without first determining what it is <em>supposed to be</em> doing. Simply dividing the monolith&#8217;s code up as is will almost invariably lead to a buggy <em>distributed monolith</em>. Instead, do the homework of modeling the processes the monolith supports. As discussed in the <a href="https://clintonbush.substack.com/p/microservices-anti-patterns-part-a54">last article</a>, <em>Event Storming</em> is a powerful and simple method of (re)discovering the system&#8217;s requirements while simultaneously modeling the business processes driving those requirements.</p><h3>Step 2: Identify <em>Bounded Contexts</em> and Prioritize them for Extraction</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!meEh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!meEh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 424w, https://substackcdn.com/image/fetch/$s_!meEh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 848w, https://substackcdn.com/image/fetch/$s_!meEh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!meEh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!meEh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png" width="1456" height="1065" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1065,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:268589,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!meEh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 424w, https://substackcdn.com/image/fetch/$s_!meEh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 848w, https://substackcdn.com/image/fetch/$s_!meEh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!meEh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7473011b-1187-4d25-9480-ac97426754ec_1652x1208.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">Figure 2. Event Storming and Bounded Contexts &#8212; <a href="https://spring.io/blog/2018/04/11/event-storming-and-spring-with-a-splash-of-ddd">Spring.io</a></figcaption></figure></div><p><em>Event Storming&#8217;s</em> format for the business process model (the sticky note wall chart) makes identifying <em>bounded contexts</em> (and candidate services) simple. The previous article mentioned several wholistic approaches to discovering <em>bounded contexts</em>.</p><p>Once you&#8217;ve drawn those boundaries, prioritize them. The <em>Strangler Fig Pattern</em> is a methodical, slow, and piecemeal approach to decomposing a monolithic application into small services. It emphasizes low risk over expediency. Thus, it would be best if you prioritized your work to deliver the most significant business value upfront.</p><p>How you determine that value depends on your organization. I suggest reflecting on why you are considering microservices and going from there. For example, are you trying to increase the rate of software delivery, increase horizontal scalability, reduce operations costs, or improve organizational agility? Whatever the answer is, prioritize the decomposition process to emphasize those goals.</p><h3>Step 3: Extract the First Bounded Context into a New Module</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-EHK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-EHK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 424w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 848w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 1272w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-EHK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png" width="1456" height="421" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:421,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:175385,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-EHK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 424w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 848w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.png 1272w, https://substackcdn.com/image/fetch/$s_!-EHK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e492668-90ff-491d-a6ae-8f5a2deecdf2_2841x822.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">Figure 3. Extracting the New Bounded Context to a New Module</figcaption></figure></div><p>Now that you have a target, it&#8217;s time to start hacking at the code. I suggest performing this step in two phases:</p><ol><li><p>Extract domain models of the bounded context to the new module</p></li><li><p>Extract the actual business logic to the new module</p></li></ol><p>Extracting the domain models will likely include renaming entities to match the ideal system&#8217;s <em>ubiquitous language</em>. This step may also include changes to the database. Your goal is to fully extract the domain model (both in source code and database). Later, the tables (or collections) will be moved into their own schema or database.</p><h3>Step 4: Separate the New Module with an Anti-corruption Layer</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hSxN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hSxN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 424w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 848w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 1272w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hSxN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png" width="1456" height="406" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d84c6845-de41-4355-9425-1fee9bf67391_2800x780.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;:150691,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hSxN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 424w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 848w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.png 1272w, https://substackcdn.com/image/fetch/$s_!hSxN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd84c6845-de41-4355-9425-1fee9bf67391_2800x780.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>Step 3 involves the surgical transplant of domain models and code out of their current home and into a new home (the new module). Step 4 builds a wall around the new module by creating an anti-corruption layer and forcing all communications with the new module to go through that layer. In practice, this often looks like a service class(es) through which all calls to the underlying logic are funneled.</p><p>Additionally, rather than using the domain models (now exclusively owned by the new module), the remainder of the monolith should exchange DTOs with the anti-corruption layer. This will initially look like duplicate code but will make extracting the module into a separate service much easier.</p><h3>Step 5: Extract the Module into a New Service</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sjOV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sjOV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 424w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 848w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sjOV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png" width="1456" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:173748,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sjOV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 424w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 848w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!sjOV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32fff754-5711-4280-ac39-9b907d4e88b7_2948x1012.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">Figure 4. Extracting the Module into a New Service</figcaption></figure></div><p>Step 3 isolated a section of the monolith into a new module, effectively realizing the <em>bounded context</em> identified by Step 2. Step 4 created a single interface through which the context may be accessed. Step 5 will create a new service (a separate application) using the new module.</p><p>Importantly, <em>do not</em> remove the module from the monolith yet. Instead, both the new module and the new service should run in parallel (see <em>Figure 4</em>). This provides an opportunity to compare the results of the new service to those of the module, ensuring it functions as expected.</p><h3>Step 6: Remove the Module from the Monolith</h3><p>Having verified the new service is fully functional, we can now remove the module from the monolith. This step should be as simple. In an ideal scenario, it only entails eliminating remnants of the module <em>except</em> for the <em>Anti-corruption Layer</em> and refactoring the <em>Anti-corruption Layer</em> to direct all calls to the new service. Figure 5<em> shows this graphically.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9w3X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9w3X!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 424w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 848w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 1272w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9w3X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png" width="1456" height="557" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:557,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51767,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9w3X!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 424w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 848w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.png 1272w, https://substackcdn.com/image/fetch/$s_!9w3X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce3b98bf-f252-41ea-8f3c-d8bdee417b53_2589x991.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">Figure 5. Removing the Module from the Monolith</figcaption></figure></div><p>Note that an API gateway is shown as the interface between the monolith and the new module, but this could be any enterprise interface. Alternatives include event buses, message queues, message channels, gRPC, or polling solutions.</p><h2>A Case Study</h2><p>In the last article of this series, I used a fictitious <em>Patient Records Portal</em> application as a running example. I will continue that narrative by examining a monolithic application that contains the <em>Billing System bounded context</em>. The <em>Billing System</em> is a sizable, monolithic application that performs many functions. In fact, it is large enough to justify its own domain analysis.</p><p>The team chose the <em>Billing System</em> because, although it is a monolith, it is already neatly divided into modules. They hope to decompose it into microservices to improve scalability and availability (i.e., they want to scale the busier parts of the app horizontally).</p><h3>Steps 1 and 2: Model the Business Processes and Identify Bounded Contexts</h3><p>Because this article is not about <em>Event Storming</em>, I will not go into the actual modeling process. Instead, assume the team already performed an <em>Event Storming</em> session and discovered these <em>bounded contexts</em>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YMqu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YMqu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 424w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 848w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 1272w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YMqu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png" width="781" height="778" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:778,&quot;width&quot;:781,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9820,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YMqu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 424w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 848w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.png 1272w, https://substackcdn.com/image/fetch/$s_!YMqu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff32c67b7-5901-424e-bcf0-a10e5fc9e446_781x778.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">Figure 6. Bounded Contexts of the Patient Billing Monolith</figcaption></figure></div><p><em>Figure 6</em> shows the team discovered five <em>bounded contexts</em>:</p><ul><li><p>Service Coding: services provided to patients are coded and charged by the <em>Debit Entry</em> context</p></li><li><p>Insurance: negotiating reimbursement for coded services and processing those payment transactions; the <em>Credit Entry</em> context handles successful transactions</p></li><li><p>Credit Entry: processed payments are posted to accounts</p></li><li><p>Debit Entry: processed charges are posted to accounts</p></li><li><p>Statement Generation: produces account statements showing all processed credits and debits</p></li></ul><p>After careful consideration, the team first extracted the <em>Statement Generation</em> context. They identified several reasons for this, including:</p><ul><li><p>The <em>Statement Generation</em> context is the most loosely coupled portion of the system.</p></li><li><p>Statements are generated in massive batches each month to be mailed out, and horizontal scaling would significantly speed up this activity.</p></li><li><p>They felt this context offers a low-risk learning opportunity before moving on to more complex parts of the system.</p></li></ul><p>After digging into the <em>Statement Generation</em> code, the team was able to draw up a crude diagram of the overall <em>Billing System</em> and, specifically, the <em>Statement Generation</em> module:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SBFN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SBFN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 424w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 848w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 1272w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SBFN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png" width="1456" height="1814" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1814,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147149,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SBFN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 424w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 848w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.png 1272w, https://substackcdn.com/image/fetch/$s_!SBFN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f40969e-052b-4a8c-913b-0ab7d3e9dec0_1501x1870.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">Figure 7. Model of Statement Generation Module, as is</figcaption></figure></div><h3>Step 3: Extract the Bounded Context into a New Module</h3><p>The team studied the diagram they created and decided the <em>Statement Generation</em> model would best be decomposed into two microservices:</p><ol><li><p>Account Ledger Service:</p><ul><li><p>Flattens credits and debits into a simplified model tailored to the needs of the <em>Statement Generation Service</em></p></li><li><p>Eliminates coupling with the monolith that <em>Shared Persistence</em> would create</p></li></ul></li><li><p>Statement Generation Service:</p><ul><li><p>Uses the <em>Account Ledger Service</em> to fetch data for generating account statements</p></li><li><p>Prints account statements tagged for printing</p></li></ul></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8EBG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8EBG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 424w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 848w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 1272w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8EBG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png" width="1456" height="1453" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1453,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:231406,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8EBG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 424w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 848w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.png 1272w, https://substackcdn.com/image/fetch/$s_!8EBG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b75289-223c-4616-bb68-89669bb52c5f_1864x1860.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">Figure 8. Extract the Bounded Context into a New Module</figcaption></figure></div><p>First, the team extracts the <code>Statement</code> model into the <code>statement-generation</code> module.</p><p>Next, they create a new <code>AccountLineItem</code> model in the new <code>account-ledger</code> module that allows them to decouple the <code>statement-generation</code> context from other parts of the monolith like <code>Credit</code>, <code>Debit</code>, and <code>Account</code>.</p><p>Once the domain model is extracted, the team extracts actual business logic. First, they move the <code>StatementQueryService</code> into the new <code>account-ledger</code> module and rename it to <code>AccountLedgerService</code>. To further decouple it, they have it return <code>AccountLineItem</code> objects rather than account IDs. Because this new service is also responsible for maintaining a context-specific representation of the state of accounts, the team adds methods for crediting and debiting amounts. The <code>credit-entry</code> and <code>debit-entry</code> modules now call these methods to keep the <code>AccountLineItems</code> in sync with the rest of the system.</p><h3>Step 4: Separate the New Module with an Anti-corruption Layer</h3><p>Thankfully, the code is already organized, so the beginning of an <em>Anti-corruption Layer</em> exists in the <code>StatementController</code>. The team decides to further isolate the new <code>statement-generation</code> module from the rest of the monolith by injecting an asynchronous interface between the <code>StatementController</code> and the prospective services &#8212; the <em>Statement Generation Service</em> and the <em>Account Ledger Service</em>. This will make Step 5 decidedly easier and will further enable horizontal scaling.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vw0O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vw0O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 424w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 848w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 1272w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vw0O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png" width="1456" height="1192" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/031eb781-3685-4d27-a335-32c30f270611_2357x1929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1192,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:265821,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vw0O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 424w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 848w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.png 1272w, https://substackcdn.com/image/fetch/$s_!vw0O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F031eb781-3685-4d27-a335-32c30f270611_2357x1929.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">Figure 9. Separate the New Module with an Anti-corruption Layer</figcaption></figure></div><p>The team does not want to modify the monolith more than necessary, so adding logic to handle retries for failed messages is out of the question. Additionally, they are dealing with financial data and must guarantee that the <code>StatementController</code>&#8217;s outgoing messages are delivered. Therefore, the team employs the <em>Guaranteed Delivery</em> pattern (Hohpe &amp; Woolf, 2003, p. 122) with the &#8220;Generate/Print Statement Queue.&#8221; They will code the <code>StatementController</code> to push a message on the queue. The message will direct the <code>StatementGenerator</code> to generate a new account statement and, optionally, print that statement.</p><p>The <code>StatementGenerator</code> service will monitor this queue and process the commands to generate statements. After a statement is generated and stored in the <code>statements</code> table, the <code>StatementGenerator</code> will send a &#8220;Statement Generated&#8221; message onto the &#8220;Statement Generated Message Channel&#8221; (Hohpe &amp; Woolf, 2003, p. 60). A message header is added for those messages that need to be printed. The &#8220;Print Message Filter&#8221; listens to this channel and only delivers messages with the &#8220;print&#8221; header to <code>StatementPrinter</code>. This is an implementation of Hohpe &amp; Woolf&#8217;s (2003) <em>Message Filter</em> (p. 236).</p><p>Finally, the team must replace the direct calls from the <code>credit-entry</code> and <code>debit-entry</code> modules to <code>AccountLedgerService</code> with an asynchronous interface. As with the <code>StatementController</code>, they use a message queue, backed by disk storage for guaranteed delivery, named &#8220;Ledger Entry Queue.&#8221;</p><h3>Step 5: Extract the Modules into New Services</h3><p>The <code>statement-generation</code> and <code>account-ledger</code> modules are ready for extraction. The team creates two new projects and copies each module into a project. They also create two new database schemas (one per service) and create the appropriate tables there. They also copy the original <code>Statements</code> table&#8217;s data into the new schema so its history is carried over.</p><p>The <code>AccountLedgerService</code>&#8217;s <code>findOutstandingAccountsByDates(...)</code> method will be called synchronously by an HTTP GET request. To accommodate this, the team establishes an API Gateway in front of the <code>AccountLedgerService</code> to proxy those requests.</p><p>Next, the team duplicates their messaging infrastructure so the modules still in the monolith can run alongside the new services. They also comment out the code that prints statements in the new <em>Statement Generation Service</em>, but they add a logging command so they can verify that the statements <em>would have been printed</em>. This allows for testing and verification before moving production operations to the new services.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_CMi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_CMi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 424w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 848w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 1272w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_CMi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png" width="1456" height="745" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:745,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:401621,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_CMi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 424w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 848w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.png 1272w, https://substackcdn.com/image/fetch/$s_!_CMi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e725972-95b0-4fca-a7fe-3fc7cef2174a_3506x1793.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">Figure 10. Extract the Modules into New Services</figcaption></figure></div><p>Finally, the team containerizes the new services and deploys them and the updated monolith&#8217;s code. They work with the testers to confirm the new services are functioning correctly.</p><h3>Step 6: Remove the Modules from the Monolith</h3><p>After testing the new services for two weeks, the team decided it was safe to switch to them entirely and remove their corresponding modules from the monolith. This was a simple matter of removing the modules from the monolith. They also removed the lines of code from the <em>Anti-corruption Layer</em> that were calling the <code>account-ledger</code> module.</p><p>Next, the team built and deployed new versions of the monolith and service containers. After confirming the successful deployment, they destroyed the duplicate messaging infrastructure and removed the <code>AccountLedger</code> and <code>Statements</code> tables from the monolith&#8217;s database schema.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rtjq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rtjq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 424w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 848w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 1272w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rtjq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png" width="1456" height="1016" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1016,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:284269,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rtjq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 424w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 848w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.png 1272w, https://substackcdn.com/image/fetch/$s_!rtjq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5912884-d054-447a-8099-f154eb6f7b2f_2439x1702.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">Figure 11. Remove the Modules from the Monolith</figcaption></figure></div><h3>Post-Migration</h3><p>The past six steps safely extracted the <em>Statement Generation</em> bounded context into two services that can be horizontally scaled to meet increased demand. They may now be updated and modified without impacting the original monolith as long as the interfaces do not change.</p><p>Ultimately, the <em>Billing System</em>&#8217;s context map looks like <em>Figure 12</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LpbZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LpbZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 424w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 848w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 1272w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LpbZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png" width="487" height="334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:334,&quot;width&quot;:487,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22573,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LpbZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 424w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 848w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.png 1272w, https://substackcdn.com/image/fetch/$s_!LpbZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6c811a-eb2d-44ba-8a9b-364715a67ad2_487x334.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">Figure 12. Final Billing System Context Map</figcaption></figure></div><h2>Conclusion</h2><p>As I mentioned, this six-step process is a variation of Richardson&#8217;s (n.d.) <em>Strangler Fig Pattern</em>. It is a low-risk, disciplined approach to decomposing a monolith into smaller services. Some key takeaways include:</p><ul><li><p>Ideally, outside the <em>Anti-corruption Layer</em>, the monolith remains unaware of any changes.</p></li><li><p>If your monolith originally received responses as entities from the extracted code and your services now return DTOs, you will need to convert those entities to adapters (i.e., wrappers) of the DTOs. You must then modify your <em>Anti-corruption Layer</em> to return the adapters to the rest of the monolith.</p></li><li><p>Although the size of your microservices may vary, the guiding principle is to ensure they neatly encapsulate their bounded contexts while remaining loosely coupled and highly cohesive.</p></li><li><p>There is no rule that a monolith must be fully decomposed. Extract microservices as necessary, but do not be dogmatic about it.</p></li></ul><div><hr></div><ul><li><p>Hohpe, G., &amp; Woolf, B. (2003). Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Addison-Wesley Longman Publishing Co., Inc. https://dl.acm.org/doi/10.5555/940308</p></li><li><p>Richardson, C. (2020). Decompose your Monolith: Six Principles for Refactoring a Monolith to Microservices. Microservice Architecture. https://microservices.io/post/refactoring/2020/07/28/six-principles-for-refactoring-to-microservices.html</p></li><li><p>Richardson, C. (n.d.). The Strangler Fig application pattern: incremental modernization to microservices. Microservice Architecture. Retrieved December 1, 2023, from https://microservices.io/post/refactoring/2023/06/21/strangler-fig-application-pattern-incremental-modernization-to-services.md.html</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Microservices Anti-patterns, Part 2]]></title><description><![CDATA[Avoiding them in New Projects]]></description><link>https://clintonbush.substack.com/p/microservices-anti-patterns-part-a54</link><guid isPermaLink="false">https://clintonbush.substack.com/p/microservices-anti-patterns-part-a54</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Tue, 23 Apr 2024 00:08:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6pe5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every software industry veteran knows that the cost of fixing bugs grows as they age. Microservices anti-patterns are no different, and it is best to avoid them altogether. I will discuss some best practices rooted in <em>Domain-Driven Design (DDD)</em> for architecting microservices. I will also discuss using <em>EventStorming</em>, a very effective technique for capturing requirements in a way conducive to <em>DDD</em>. Next, I will survey the <em>Microservice Adoption Anti-patterns</em> documented by Chris Richardson (n.d.) These are a different set of anti-patterns than the ones covered by this series of articles but are highly relevant when adopting microservice architecture. Finally, I will provide a few additional suggestions before concluding.</p><h2>Domain-Driven Design to the Rescue!</h2><h3>The Importance of Naming Things</h3><p>One of the most challenging tasks developers face is effectively naming things. By effective, I mean in a way that lasts the tests of time and remains meaningful, even to your successors who will someday maintain your code. This task is so infamous that it has been immortalized in the annals of the software industry by witty quotes, jokes, and comics.</p><p>One of the most famous quotes (which has, in turn, inspired many others) was spoken by Phil Karlton while leading a team of developers at Netscape:</p><blockquote><p><em>There are only two hard things in Computer Science: cache invalidation and naming things.</em></p></blockquote><p>If you are familiar with the complexities of cache invalidation, you will understand why comparing these two things is both paradoxical and somewhat shocking. In other cases, developers name things in ways that make sense to them but are incoherent to others (see <em>Figure 1</em>).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6pe5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6pe5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 424w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 848w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 1272w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6pe5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png" width="758" height="621" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:621,&quot;width&quot;:758,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:270399,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6pe5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 424w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 848w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.png 1272w, https://substackcdn.com/image/fetch/$s_!6pe5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fa13a6a-86f7-4413-85cb-a149845fc0de_758x621.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><figcaption class="image-caption">Figure 1. Naming &#8212; https://vincentdnl.com/drawings/naming</figcaption></figure></div><p><em>Domain-Driven Design</em> seeks to reduce the incongruence between a software system&#8217;s problem space (the customer&#8217;s <em>domain</em>) and its solution space (the system&#8217;s architecture and code). DDD starts with two essential tasks: <em>identifying things</em> and <em>naming those things</em>.</p><h3>Bridging the Gap</h3><p>DDD bridges the gap between the problem and solution spaces by systematically studying the problem space&#8217;s real-world systems and mapping those ideas into the solution domain. Eric Evans&#8217; <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em> (2004) is the &#8220;bible&#8221; of DDD. It lists many model elements, design patterns, strategies, and techniques of DDD; however, we will only discuss three that help solve our immediate problems with microservices: <em>ubiquitous language</em>, <em>domains</em>, and <em>bounded contexts</em>.</p><p>For the rest of this section, I will use a fictitious <em>Patient Records Portal</em> system as a case study. This system should serve patients and providers by providing access to their medical records.</p><h4>Ubiquitous Language</h4><p>The first step in <em>Domain-Driven Design</em> is adopting a <em>Ubiquitous Language</em>. The client and development team share this vocabulary (see <em>Figure 2</em>).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qybn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qybn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 424w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 848w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 1272w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qybn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png" width="1456" height="947" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:947,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24942,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Qybn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 424w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 848w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.png 1272w, https://substackcdn.com/image/fetch/$s_!Qybn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dad6fbc-06ce-49a9-bfca-1cfecde28e0c_1457x948.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">Figure 2. Ubiquitous Language</figcaption></figure></div><p>Creating and using a <em>ubiquitous language</em> offers many benefits, including:</p><ul><li><p>Unambiguous requirements</p></li><li><p>Self-documenting code that explains (through class, function, and variable names) what part of the client&#8217;s problem(s) it solves</p></li><li><p>Components of code (classes, methods, objects, packages, etc.) map to real-world concepts</p></li><li><p>Developers form a better intuition for the client&#8217;s problems and optimal solutions</p></li><li><p>The system architecture reflects the structures of the problem domain</p></li><li><p>Eliminates confusion in person-to-person communication</p></li><li><p>Enables loosely coupled and highly cohesive code</p></li></ul><p>In the context of the <em>Patient Records Portal</em>, the <em>ubiquitous language</em> should provide the development team with a vocabulary they both comprehend and use. It must clear up confusion and ensure that words hold the same meanings for the client(s) and the development team.</p><p>To demonstrate the value of a <em>ubiquitous language</em>, study the list of words below. Each word is either specific to the medical field or overloaded by meanings in the medical field and other fields:</p><ul><li><p>Assays</p></li><li><p>Provider</p></li><li><p>Classification</p></li><li><p>Topography</p></li></ul><blockquote><p>These are only four trivial examples, but imagine the confusion they would cause without a <em>ubiquitous language</em></p></blockquote><p>Consider <em>assays;</em> according to <em>The American Heritage&#174; Dictionary</em>, it could mean assessing a substance, the substance being assessed, or the assessment results. Imagine if the <em>Patient Records Portal</em> clients understood <em>assay</em> to mean &#8220;an assessment&#8221; while developers understood it to mean &#8220;the results of an assessment.&#8221; This mistake is not only easy to make, but it could go undetected for a long while.</p><p><em>Provider</em> is overloaded with different meanings from the medical, software, and other fields. In the medical field, it often refers to a physician. However, <em>provider</em> could also mean a nurse practitioner, physician&#8217;s assistant, dentist, or other professional. Even more confusing, physicians may or may not be providers, and providers may or may not be physicians. While medical professionals can probably deduce the intended meaning from the context in which the word was used, a software team is unlikely to do the same.</p><p><em>Topography</em> is usually associated with maps that include elevation markings, relief features, and shading. In the medical field, however, it means a specific location on the human body. <em>Classification</em> is another broadly used term. In the medical field, it refers to coding diagnoses, treatments, medications, and other things so they can be precisely recorded and billed. It may also refer to data classified as <em>protected health information (PHI)</em> by the <em>Health Insurance Portability and Accountability Act (HIPAA)</em>. In the government domain, it refers to the sensitivity of information and the potential harm its unintended release could cause.</p><p>We have already anticipated a lot of confusion resulting from just four words. What happens when we begin throwing software terms into the mix? For example:</p><ul><li><p>ProviderRegistry</p></li><li><p>AbstractAssayFactory</p></li><li><p>TopographyDiagnosisBuilder</p></li><li><p>ClassificationConversionService</p></li></ul><p>These are only four trivial examples, but imagine the confusion they would cause without a <em>ubiquitous language</em>.</p><h4>Domains</h4><p>The next step in applying DDD to a microservices project is identifying the subdomains the project will touch. The <em>domain</em> is what a business does and the world it does it in (Vernon, 2013, p. 43). An organization&#8217;s domain includes multiple subdomains that house different categories of activity and knowledge required for a business to function. There are three types of subdomains: core, supporting, and generic.</p><blockquote><p>&#128161; <em>Subdomains</em> model the <em>problem space</em> of a project &#8212; real-world business activities and ideas.</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_!idXC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!idXC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 424w, https://substackcdn.com/image/fetch/$s_!idXC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 848w, https://substackcdn.com/image/fetch/$s_!idXC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 1272w, https://substackcdn.com/image/fetch/$s_!idXC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!idXC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png" width="1000" height="1060" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1060,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:28589,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!idXC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 424w, https://substackcdn.com/image/fetch/$s_!idXC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 848w, https://substackcdn.com/image/fetch/$s_!idXC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.png 1272w, https://substackcdn.com/image/fetch/$s_!idXC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4399e1fc-e797-4ef8-9b9b-a0fe9c933d96_1000x1060.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">Figure 3. Context Map with Only Subdomains Shown</figcaption></figure></div><p><em>Figure 3</em> (above) depicts the <em>Patient Records Portal</em> project&#8217;s overall domain comprising six subdomains. Each subdomain can be either a <em>core</em>, <em>supporting</em>, or <em>generic</em> subdomain (Vernon, 2013, p. 51-52):</p><ul><li><p><em><strong>Supporting subdomains</strong></em> model those aspects of the project that are essential but not core. For example, imagery (e.g., X-rays, CT scans, sonograms) is a valuable part of patient records, but it is not the core of this project.</p></li><li><p>A <em><strong>generic subdomain</strong></em> is a part of the project that is not special, particular, or unique to the project but still essential to its success. <em>Figure 3</em> shows <em>Identity &amp; Access Management (IAM)</em> as a generic subdomain. Not only is making the application secure a good idea, but it is also legally required when dealing with health information. At the same time, IAM is not a concept unique or special to our project. Therefore, we include it in our domain but label it as <em>generic</em>.</p></li></ul><h4>Bounded Contexts</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7nfY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7nfY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 424w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 848w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 1272w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7nfY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png" width="1200" height="800" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80261149-694f-4a36-a3dd-46da500eca07_1200x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1965186,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7nfY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 424w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 848w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.png 1272w, https://substackcdn.com/image/fetch/$s_!7nfY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80261149-694f-4a36-a3dd-46da500eca07_1200x800.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">Figure 4. Bounded Contexts are like Cell Walls</figcaption></figure></div><p>Even with a <em>ubiquitous language</em> developed for a project, it is possible to encounter conflicting models and terms within that project. It is also possible for different terms to refer to very similar concepts. For example:</p><ul><li><p>The term <em>schedule</em> is used when classifying drugs (e.g., <em>schedule 2</em> drugs&#8212;like narcotics&#8212;have medical uses but are also highly prone to abuse). This conflicts with using <em>schedule</em> in the <em>Scheduling</em> subdomain, which refers to appointments.</p></li><li><p>The idea of &#8220;customer&#8221; may be labeled differently in each subdomain. This is especially likely if any subdomains are applications maintained by external entities (e.g., SaaS products). The <em>Pharmacological</em> subdomain may call them &#8220;customer,&#8221; the <em>Patient Records</em> subdomain probably calls them &#8220;patient,&#8221; the <em>Imaging</em> subdomain may call them &#8220;subject,&#8221; and the <em>Billing</em> subdomain may refer to them as an &#8220;account.&#8221; More importantly, that person is probably represented by a different data model with different fields in each subdomain.</p></li></ul><p>DDD calls these boundaries between models and meaning <em>bounded contexts</em>. As the cell walls shown in <em>Figure 4</em> prevent the spillage of organelles, so do <em>bounded contexts</em> prevent the spillage of one domain&#8217;s models and meanings into another&#8217;s. That spillage results in buggy software that is tightly coupled and incoherent.</p><blockquote><p>&#128161; <em><strong>Bounded Contexts</strong></em> model a project&#8217;s <em>solution space</em> &#8212; the software systems that solve a problem.</p></blockquote><p>Evans (2004, p. 336) suggests that these boundaries should be realized logically (e.g., separate database schemas and code repositories) and physically (e.g., team organization). This proposition flips <em>Conway&#8217;s Law</em> on its head by forcing team structure to match the system rather than the inverse.</p><p>Alberto Brandolini (2015, p. 116) describes the significance of <em>bounded contexts</em> like this:</p><blockquote><p>Now I consider &#8220;getting the boundaries right&#8221; the single design decision with the most significant impact over the entire life of a software project.</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_!-tCT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-tCT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 424w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 848w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 1272w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-tCT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png" width="1000" height="1060" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1060,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50928,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-tCT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 424w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 848w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.png 1272w, https://substackcdn.com/image/fetch/$s_!-tCT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb6de537-f34a-48f5-8b94-faf44eaabcca_1000x1060.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">Figure 5. Bounded Contexts for the Patient Records Portal</figcaption></figure></div><p><em>Figure 5</em> depicts one possibility for how <em>bounded contexts</em> might be drawn in the <em>Patient Records Portal</em> project. Notice that the <em>bounded contexts</em> often, but not always, align one-for-one with a system&#8217;s subdomains. There are two exceptions in <em>Figure 5</em>: the <em>Scheduling System</em> and the <em>Pharmacy System</em>.</p><p>The <em>Scheduling System</em> overlaps three subdomains apart from the <em>Scheduling</em> subdomain. The scheduling system might be used for patient appointments, imaging scans, and laboratory procedures. In that case, it must include concepts and models from those subdomains.</p><p>Similarly, the <em>Pharmacy System</em> may manage prescriptions, prescription fills, medication regimens, patients&#8217; adverse reactions to medications, and control over pharmacological substances. Some medication regimens require periodic blood tests to monitor things like liver health. Therefore, the <em>Pharmacy System</em> would include some ideas and models from the <em>Lab Orders</em> subdomain.</p><h3>From Context Maps to Microservices</h3><p>Now that the <em>Patient Records Portal</em> has been modeled as a context map, it is relatively simple to identify prospective microservices &#8212; look at the <em>bounded contexts</em>. A domain&#8217;s bounded contexts translate cleanly to microservices for two simple reasons:</p><ol><li><p>They are highly cohesive:</p><ul><li><p>Focused on solving a specific problem</p></li><li><p>Contain a single, specific model</p></li><li><p>Contain a single subset of the domain&#8217;s <em>ubiquitous language</em></p></li></ul></li><li><p>They are loosely coupled:</p><ul><li><p>Models are not shared with other <em>bounded contexts</em></p></li><li><p>Overlapping multiple subdomains means they will have their representations of models, not shared models</p></li><li><p>Boundaries require interfaces for <em>bounded contexts</em> to interact with one another</p></li></ul></li></ol><p>Interestingly, this solves three of the four anti-patterns discussed in <a href="">Microservices Anti-patterns, Part 1: What are they?</a>:</p><ol><li><p><em>Distributed Monolith:</em> A distributed monolith is a collection of microservices that know too much about each other and are overly dependent on each other. <em>Bounded contexts</em> do neither of these things.</p></li><li><p><em>Shared Persistence:</em> There can be no shared persistence without shared models.</p></li><li><p><em>Shared Libraries:</em> There is no reason to share libraries if every microservice is appropriately bound to be highly cohesive and loosely coupled.</p></li></ol><p><em>Figure 5</em> clearly shows the need for at least seven microservices:</p><ol><li><p>Patient Records Service</p></li><li><p>Lab Service</p></li><li><p>Scheduling Service</p></li><li><p>Imaging Service</p></li><li><p>Pharmacy Service</p></li><li><p>IAM Service</p></li><li><p>Billing Service</p></li></ol><p>Notice my use of the phrase &#8220;at least.&#8221; I find these seven microservices to be large. No rule prohibits a <em>bounded context</em> from containing more than one microservice. For example, the <em>Billing Service</em> might be split into:</p><ul><li><p>Account Statements Service</p></li><li><p>Outstanding Accounts Report Service</p></li><li><p>Statement Mailing Service</p></li><li><p>Transaction Summary Report Service</p></li><li><p>Past Due Report Service</p></li><li><p>Collections Service</p></li></ul><p>The key to successfully subdividing <em>bounded contexts</em> into multiple microservices is to treat each microservice as its own <em>bounded context</em>. They must not share models, persistence, kernels, or libraries. They should remain highly cohesive (focused on a single problem) and loosely coupled (unaware of the inner workings of other microservices).</p><h3>Storming to Discovery</h3><p>I have explained what <em>ubiquitous language</em>, <em>domains</em>, <em>subdomains</em>, and <em>bounded contexts</em> are. You know the <em>domain</em> and its <em>subdomains</em> form the problem space &#8212; the customer&#8217;s business functions. I also explained how a <em>subdomain</em> houses a specific category of activity and knowledge that falls within the overall <em>domain</em>. Therefore, it is possible to discern these details by studying the customer&#8217;s business or asking them directly.</p><p><em>Bounded contexts</em>, however, are not so easily identified. They model the <em>solution space</em> &#8212; the system that will solve the project&#8217;s problem. Discovering <em>bounded contexts</em> and overlaying them atop the business&#8217;s <em>subdomains</em> is the first step in designing a new distributed system composed of microservices. How, then, does one go about making this discovery? There is no shortage of opinions on requirements gathering and design practices, but the most effective one I have experienced (particularly for <em>Domain-Driven Design</em>) is <em><strong>EventStorming</strong></em>.</p><p>There is no singular definition for <em>EventStorming</em>. Even its progenitor, Alberto Brandolini, does not explicitly define the term in his book about EventStorming, <em>Introducing EventStorming: An Act of Deliberate Collective Learning</em>. However, by borrowing some concepts from his book and mixing them with Wikipedia&#8217;s explanation, I came up with this:</p><blockquote><p><em><strong>EventStorming</strong></em>: A workshop where stakeholders, domain experts, and a software team collaborate to model business processes, identify problems worth solving, share knowledge, and begin planning solutions. The workshop is lightweight, using only markers and sticky notes, and highly collaborative, requiring everyone&#8217;s active participation.</p></blockquote><p>I won&#8217;t explain how to perform <em>EventStorming</em>, as that would take another series of articles. I will, however, provide some tips on identifying <em>bounded contexts</em> once you have completed a storming session.</p><p>First, Brandolini (2015) suggests adding two relatively obscure (not commonly mentioned when discussing <em>EventStorming</em>) markers to the wall when storming: <em>pivotal events</em> and <em>swimlanes</em>.</p><p>He defines them as:</p><ul><li><p><em><strong>Pivotal Events:</strong></em> &#8220;specific events which are particularly relevant for the business and mark a transition between different business phases&#8221; (p. 126)</p></li><li><p><em><strong>Horizontal Swimlanes:</strong></em> &#8220;a common way to structure portions of the whole flow&#8221; to be used when &#8220;&#8230;the flow is not linear. There are branches, loops, and things that happen in parallel.&#8221; (p. 128)</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ww2v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ww2v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 424w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 848w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 1272w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ww2v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png" width="1456" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:341,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:203534,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ww2v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 424w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 848w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 1272w, https://substackcdn.com/image/fetch/$s_!Ww2v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707fff19-4477-4c15-bd0e-a4bbcc2c1232_2862x671.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Figure 6. Pivotal Events and Swimlanes (Brandolini, p. 129)</figcaption></figure></div><p><em>Figure 6</em>&nbsp;(above) is a simple example borrowed from Brandolini&#8217;s book that depicts both&nbsp;<em>pivotal events</em>&nbsp;and&nbsp;<em>swimlanes</em>.&nbsp;<em>Pivotal events</em>&nbsp;are highlighted by placing a yellow line (marker or tape) beneath the orange event sticky note.&nbsp;<em>Horizontal swimlanes</em>&nbsp;are drawn as a red line (marker or tape) that indicates the extent of a nested flow within the overall business flow.</p><p>Before moving on, it is crucial to have the participants of the EventStorming agree on the <em>pivotal events</em> and <em>swimlanes</em>. Once you have their approval, you can use both the <em>pivotal events</em> and <em>horizontal swimlanes</em> as guides for where to discover <em>bounded contexts</em> (see <em>Figure 7</em> below). Brandolini emphasizes that this is a heuristic, not a guarantee or rule.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x4N5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x4N5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 424w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 848w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 1272w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x4N5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png" width="1456" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:430640,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x4N5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 424w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 848w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.png 1272w, https://substackcdn.com/image/fetch/$s_!x4N5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38149112-61ea-4890-80ed-359b0eefb8cc_2759x958.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">Figure 7. Using Pivotal Events and Swimlanes to find Bounded Contexts (Brandolini, p. 136)</figcaption></figure></div><p>Before leaving this section, I&nbsp;<strong>implore</strong>&nbsp;you to read&nbsp;<em>Chapter 6: Discovering Bounded Contexts with EventStorming</em>&nbsp;in Brandolini&#8217;s book. My explanation above is an extreme distillation of what the book covers. Please purchase his book if you plan to use DDD or EventStorming and can spare $10. It is worth it.</p><h3>Microservices are a Magic Pixie Dust</h3><p>This pattern describes when an organization adopts microservices to solve problems that are not solvable with microservices. Microservices are a highly cohesive, loosely coupled architecture that offers easy deployability, testability, and scalability. They will not solve tangential and unrelated issues like code quality, poor development and deployment pipelines, or existing architectural issues (an overgrown monolith). Microservices may be a <em>part</em> of the actual solution. As Richardson notes, you must first understand the cause of your problems, the problems microservices can solve, and determine if the two overlap.</p><h3>Microservices are the Goal</h3><p>Having experienced this anti-pattern, it is especially poignant to me. Whether it is an executive who jumps on every buzzword bandwagon or an overzealous architect who wants to pad his r&#233;sum&#233; with a long list of new technologies, the result is the same: using microservices for the sake of using microservices. Many issues can come from this strategy, not the least of which is that microservices may be a poor choice for a given project.</p><h3>Scattershot Adoption</h3><p>This is another anti-pattern I have experienced firsthand. This describes the situation where multiple teams in an organization decide to start building microservices without coordinating their efforts. Several issues arise from this practice, including duplicated effort, myriad approaches to infrastructure, and blurred <em>bounded contexts</em>.</p><h3>Trying to Fly Before You Can Walk</h3><p>Richardson describes this as occurring when organizations skip past getting basic software development practices right and go straight to building microservices. It would be best to master the fundamentals before progressing to more complicated exercises. Those fundamentals include clean code, automated testing, CI/CD pipelines, and solid design/architecture skills. While he didn&#8217;t mention this, I would add a functioning orchestrated hosting environment and continuous container delivery platform. Amazon Web Services (AWS) offers excellent options. If an organization prefers a self-managed solution, Kubernetes combined with ArgoCD is a good choice.</p><h3>Focusing on Technology</h3><p>An organization that spends large sums of money on top-notch cloud and orchestration platforms before they understand their needs or the problems they are trying to solve has fallen victim to this anti-pattern. Instead, they should focus on the benefits of microservices, apply them as appropriate, invest in relevant technologies, and evolve their systems as needed.</p><h3>More the Merrier</h3><p>This happens when organizations take &#8220;micro&#8221; to the extreme and create many overly fine-grained microservices. This creates unnecessary complexity, resource spending, and, possibly, latency because of the additional network traffic. Additionally, unnaturally dividing a system into microservices is an easy way to blur <em>bounded contexts</em> and introduce anti-patterns like shared persistence, shared libraries, and distributed monoliths.</p><h3>Red Flag Law</h3><p>This anti-pattern describes organizations that keep the same operating practices and team structures used for monolithic development when building microservices. Richardson explains, &#8220;If you don&#8217;t change your organization&#8217;s software development process and structure, it&#8217;s likely that you will not benefit from the microservice architecture.&#8221;</p><h2>A Little Advice</h2><p>Using DDD and EventStorming to get <em>bounded contexts</em> right is an excellent first step to avoiding the Microservices Anti-patterns I discussed in the previous article. Avoiding Richardson&#8217;s Microservices Adoption Anti-patterns will also guide you to use microservices where they make sense and get the maximum benefit from them. Below, I list a few final suggestions to help you be successful.</p><h3>Be Flexibly DRY</h3><p>Developers tout the &#8220;Don&#8217;t Repeat Yourself&#8221; dogma, but it can cause problems for you in microservices. This is especially true at the edges of <em>bounded contexts</em>. Microservices that use the same data should not share the same models. Their models may be very similar, but they should still be tailored to each service&#8217;s needs. This is another way of saying don&#8217;t share kernels, don&#8217;t share persistence, and don&#8217;t share libraries.</p><h3>A Small Monolith may be a Better Option</h3><p>Microservices are not always the right solution. If you cannot cleanly separate the concerns of a monolith, creating microservices will likely cause you more difficulties than you already have.</p><h2>Parting Thoughts</h2><p>When deploying new microservices systems, be deliberate about enforcing the principles of high cohesion and low coupling. Ensure each microservice has a specific purpose and &#8220;stays in its lane.&#8221; Leverage <em>Domain-Driven Design</em> when discovering the system&#8217;s requirements and architecting a solution. Finally, if you are newly adopting microservices, take a conservative approach to that journey and avoid Richardson&#8217;s adoption anti-patterns.</p><p>In the following article in this series, I write about avoiding microservices anti-patterns when migrating a monolithic application to microservices architecture.</p><div><hr></div><ul><li><p>Brandolini, A. (2015). <em>Introducing EventStorming: An Act of Deliberate Collective Learning</em>. Leanpub. <a href="http://leanpub.com/introducing_eventstorming">http://leanpub.com/introducing</a><em><a href="http://leanpub.com/introducing_eventstorming">eventstorming</a></em></p></li><li><p>Evans, E. (2004). <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em>. Pearson Education, Inc.</p></li><li><p>Richardson, C. (n.d.). Microservices adoption anti-patterns. Microservice Architecture. Retrieved November 22, 2023, from <a href="https://microservices.io/microservices/antipatterns/-/the/series/2019/06/18/microservices-adoption-antipatterns.html">https://microservices.io/microservices/antipatterns/-/the/series/2019/06/18/microservices-adoption-antipatterns.html</a></p></li><li><p>Vernon, V. (2013). <em>Implementing Domain-Driven Design</em>. Pearson Education, Inc.</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Microservices Anti-patterns, Part 1]]></title><description><![CDATA[What are they?]]></description><link>https://clintonbush.substack.com/p/microservices-anti-patterns-part</link><guid isPermaLink="false">https://clintonbush.substack.com/p/microservices-anti-patterns-part</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Sat, 06 Apr 2024 23:02:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vype!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Microservices may be a silver bullet for enterprise systems; however, they become Pandora&#8217;s box when poorly architected. In his seminal 2015 book <em>Building Microservices: Designing Fine-grained Systems</em>, Sam Newman listed seven key benefits of adopting microservices:</p><ol><li><p>Technology Heterogeneity</p></li><li><p>Resilience</p></li><li><p>Scaling</p></li><li><p>Ease of Deployment</p></li><li><p>Organizational Alignment</p></li><li><p>Composability</p></li><li><p>Optimizing for Replaceability</p></li></ol><p>Of these seven traits, five depend on the services being loosely coupled. This principle is the most frequent victim of microservice anti-patterns. It is, therefore, unsurprising that organizations often fail to realize their goals when they migrate their systems from monoliths to microservices. Their microservices may be highly resilient and easily scalable, thanks to tools like Kubernetes, but maintaining those services quickly becomes unmanageable.</p><blockquote><p>Their microservices may be highly resilient and easily scalable, thanks to tools like Kubernetes, but maintaining those services quickly becomes unmanageable.</p></blockquote><p>The five anti-patterns we will discuss all create dependencies amongst microservices, making them tightly coupled, brittle, and difficult to maintain. They are:</p><ol><li><p>Distributed Monolith</p></li><li><p>Entangled Data, Data Taffy, or Shared Persistence</p></li><li><p>Piggy Bank</p></li><li><p>Dependency Disorder</p></li></ol><h2>Distributed Monolith</h2><p>While this anti-pattern is most commonly found in microservices that result from splitting a monolithic application, I have also seen it in greenfield projects. The <em>Distributed Monolith</em> is the result of a system&#8217;s fall to the other three anti-patterns.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vype!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vype!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 424w, https://substackcdn.com/image/fetch/$s_!vype!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 848w, https://substackcdn.com/image/fetch/$s_!vype!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 1272w, https://substackcdn.com/image/fetch/$s_!vype!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vype!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png" width="1100" height="810" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:810,&quot;width&quot;:1100,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32310,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vype!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 424w, https://substackcdn.com/image/fetch/$s_!vype!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 848w, https://substackcdn.com/image/fetch/$s_!vype!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.png 1272w, https://substackcdn.com/image/fetch/$s_!vype!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34138aac-c4ea-4501-98cc-118040ccb1e5_1100x810.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><figcaption class="image-caption">Figure 1. Stereotypical Distributed Monolith</figcaption></figure></div><p>Indicators of&nbsp;<em>Distributed Monoliths</em>&nbsp;(see&nbsp;<em>Figure 1</em>&nbsp;for an example) include:</p><ul><li><p>Deploying a new version of one microservice requires new versions of other microservices to be deployed</p></li><li><p>Multiple services share common resources (e.g., databases, message channels, etc.)</p></li><li><p>Standard libraries of code are shared between the microservices</p></li><li><p>Any of the microservices cannot perform their tasks without other microservices being present</p></li></ul><p>Let&#8217;s dissect <em>Figure 1</em> before moving on. First and most apparent, you will notice that three of the four services rely on the same database. This likely indicates that these three services must all be updated if their data model changes.</p><p>Next, notice that microservices 1 and 2 communicate with each other. The diagram doesn&#8217;t indicate what form that communication takes, but we can assume it is some form of API or RPC. If one of the services is responsible for triggering the other, they should probably be merged.</p><p>Microservices 3 and 4 are coupled via a message channel. This could be good or bad, depending on who owns and maintains those services. The microservices should be merged if they are from the same development team, as it appears that microservice 4 waits for a message from 3 and then writes something to the database. In that case, the message channel is an unnecessary complication.</p><p>If different teams own microservices 3 and 4, those teams should carefully assess their arrangement. Maybe both services should be owned by the same team. Maybe the organizational structure of the teams is wrong. Perhaps the teams fell victim to Richardson&#8217;s (n.d.) <em>Scattershot Adoption</em> anti-pattern, and they should collaborate on a better solution. It is also possible that using the message channel is the best solution.</p><h2>Entangled Data, Data Taffy, or Shared Persistence</h2><p><em>Shared Persistence</em> most often refers to multiple microservices using the same relational database. Taibi, Lenarduzzi, and Pahl (2020) found that the worst scenario is when the afflicted microservices share the same database entities (p. 11). While it is also bad when other types of databases are shared amongst services (e.g., key-value stores, document databases, etc.), the effects aren&#8217;t as profound due to the flexibility of the schemas and the horizontal scalability of NoSQL databases.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WkZ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WkZ2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 424w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 848w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 1272w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WkZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png" width="1456" height="588" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:588,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44218,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WkZ2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 424w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 848w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.png 1272w, https://substackcdn.com/image/fetch/$s_!WkZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4038f123-dcfa-41c3-8642-c44179d6ac31_1560x630.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">Figure 2. Microservices Suffering from Entangled Data</figcaption></figure></div><p>Indicators of&nbsp;<em>Shared Persistence</em>&nbsp;include:</p><ul><li><p>Altering a database table schema requires modifying the code of multiple microservices</p></li><li><p>Shared code libraries of object-relational mapping entities used by multiple microservices</p></li><li><p>Difficulty determining which microservice project should contain database migration scripts</p></li><li><p>Microservices are suffering due to database performance, row locks, and other forms of resource contention</p></li></ul><p><em>Figure 2</em> depicts a classic case of <em>Entangled Data</em>. A cursory glance reveals the following issues:</p><ul><li><p>Because <em>Java Microservice 1</em> and <em>Java Microservice 2</em> depend on the same <em>Java ORM Entities Library</em>, they are <em>tightly coupled</em> via that dependency. This dependency is, in turn, tightly coupled to the database schema.</p></li><li><p>All three microservices depend on the same database table. In turn, they are tightly coupled to each other via that table. Any changes made to that table will require:</p><ul><li><p>The <em>Java ORM Entities Library</em> be updated</p></li><li><p><em>Java Microservice 1</em> and <em>Java Microservice 2</em> must be updated to use the new version of the <em>Java ORM Entities Library</em>. They may require code changes to be compatible with the new version of the library.</p></li><li><p>The <em>Golang Microservice</em> must be updated so its representation of the <em>users table</em> matches the database schema.</p></li></ul></li><li><p>The graphic doesn&#8217;t tell us which, if any, of the microservices <em>write</em> to the database. However, if more than one writes to it, they may encounter resource contention issues.</p></li><li><p>If the owning team employs automated database migrations, how should they choose which service is responsible?</p></li></ul><h2>Piggy Bank</h2><p>The <em>Piggy Bank</em> anti-pattern describes what happens when teams take a naive approach to migrating a monolithic application to microservices. The team begins with an application aptly described as a &#8220;pig.&#8221; It is large, ugly, and odorous with code smells.</p><p>Monson (2019) writes, &#8220;When a team is first introduced to this&#8230;the first inclination is to smash the whole thing up into little pieces, like a piggy bank with a hammer. Because, of course, 1000 services will be way better than one bloated one, right?&#8221; However, without proper planning and design, these microservices will almost certainly suffer from <em>Shared Persistence</em>, <em>Entangled Data</em>, and <em>Shared Libraries</em>. Guess what you have, then? A <em>Distributed Monolith</em>.</p><blockquote><p>When a team is first introduced to this&#8230;the first inclination is to smash the whole thing up into little pieces, like a piggy bank with a hammer.</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_!Knjf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Knjf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 424w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 848w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 1272w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Knjf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png" width="1456" height="694" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:694,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82327,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Knjf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 424w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 848w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.png 1272w, https://substackcdn.com/image/fetch/$s_!Knjf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F026dd045-7251-41af-a088-2e406ebfc695_2370x1130.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">Figure 3. Stereotypical Piggy Bank</figcaption></figure></div><p>Look at&nbsp;<em>Figure 3</em>&nbsp;and imagine how easily a migration could transform the original&nbsp;<em>Patient Records Portal</em>&nbsp;monolith (on the far left) into the massive&nbsp;<em>Distributed Monolith</em>&nbsp;on the right. What was once a monolith is now a hodgepodge of microservices &#8212; some are anemic, some are bloated, many share databases, many depend on each other, and, presumably, many share code libraries. In short, the result of breaking that&nbsp;<em>Piggy Bank</em>&nbsp;is a mess.</p><h2>Dependency Disorder</h2><p>This anti-pattern comprises two issues under a single label: inter-service dependencies via shared code and inter-service dependencies via API calls. In short, the services suffer from an unhealthy relationship such that they cannot be independently modified and deployed in any order.</p><blockquote><p>The services suffer from an unhealthy relationship such that they cannot be independently modified and deployed in any order.</p></blockquote><p>While this list is assuredly not exhaustive, here are some ways <em>Dependency Disorder</em> can occur:</p><ol><li><p>As a side-effect of <em>Entangled Data</em>:</p></li></ol><p>In this situation, one microservice is treated as the &#8220;owner&#8221; of the data and contains a code module of ORM entities. Another microservice references that code module as a dependency so that it may use those ORM entities in its code base.</p><ol start="2"><li><p>As a side-effect of <em>Shared Persistence</em>:</p></li></ol><p>Like the first example, one microservice is treated as the &#8220;owner&#8221; of the data, but in this case, it is responsible for executing database migration scripts. Other microservices that use those same database tables are dependent on the &#8220;owner&#8221; and must be modified and simultaneously deployed with it to avoid breaking.</p><ol start="3"><li><p>As a failure to properly version the API:</p></li></ol><p>When microservices depend on each other, they implicitly enter into a contract guaranteeing that the <em>provider</em> will not change the API being used by the <em>consumer.</em> Breaching this contract would break the <em>consumer.</em> Despite this contract, the providing service must also be able to evolve by adding new features. The most common solution to this backward compatibility problem is to version the API. This modifies the contract such that the <em>provider</em> guarantees it will not alter a previous API version but remains free to add new versions.</p><ol start="4"><li><p>As a failure to separate concerns:</p></li></ol><p>This form of <em>Dependency Disorder</em> may arise as a result of the <em>Piggy Bank</em> anti-pattern. It can be distilled down to poor refactoring, creating a timing issue. For example, <em>service A</em> requires that <em>B</em> is available before <em>A</em> starts. If <em>B</em> is unavailable when <em>A</em> starts, then <em>A</em> fails to start. In the best case, your orchestrator respawns <em>A</em> until it successfully starts. In the worst case, <em>B</em> has a circular dependency on <em>A,</em> and a vicious cycle begins where the orchestrator continually restarts both <em>A</em> and <em>B.</em></p><p>A couple of solutions to this problem are immediately apparent. First, the two services could be refactored to eliminate this dependency or gracefully handle failure. Second, an API gateway could be deployed to mediate interactions with and between the services.</p><h2>Conclusion</h2><p>To end this article on a positive note, take solace in knowing that all of these issues can be avoided by following the principles of <em>Domain-Driven Design</em>. In this series&#8217; following articles, I will discuss how one might:</p><ol><li><p>Avoid these anti-patterns for new microservice projects</p></li><li><p>Avoid these anti-patterns when migrating from a monolith to microservices</p></li><li><p>Convert a <em>distributed monolith</em> into proper microservices</p></li></ol><div><hr></div><ul><li><p>Monson, D. (2019, March 25). <em>Microservices Anti-Patterns - DZone</em>. DZone. https://dzone.com/articles/microservices-anti-patterns</p></li><li><p>Newman, S. (2015). <em>Building Microservices: Designing Fine-grained Systems</em>. O&#8217;Reilly Media, Inc.</p></li><li><p>Richardson, C. (n.d.). <em>Microservices adoption antipatterns</em>. Microservice Architecture. Retrieved November 22, 2023, from https://microservices.io/microservices/antipatterns/-/the/series/2019/06/18/microservices-adoption-antipatterns.html</p></li><li><p>Taibi, D., Lenarduzzi, V., &amp; Pahl, C. (2020). Microservices Anti-patterns: A Taxonomy. In <em>Microservices</em> (pp. 111&#8211;128). Springer International Publishing. https://doi.org/10.1007/978-3-030-31646-4<em>5</em></p></li></ul>]]></content:encoded></item><item><title><![CDATA[DTOs vs. Entities]]></title><description><![CDATA[DTOs, Aggregates, Entities, and Value Objects: What are they, and how do DTOs and Entities fit into an API?]]></description><link>https://clintonbush.substack.com/p/dtos-vs-entities</link><guid isPermaLink="false">https://clintonbush.substack.com/p/dtos-vs-entities</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Mon, 01 Apr 2024 21:42:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have progressed past <em>hello world</em> applications, you have undoubtedly encountered at least one of these terms. Finding definitions for them is a simple task, but knowing when a DTO is needed instead of an entity or when an ORM entity should be represented as a value object requires a bit more depth. Object-relational mapping libraries further complicate this subject because developers often feel compelled, either out of some dogmatic adherence to a pattern they read about or due to a lack of understanding of their ORM, to create DTOs identical to their entities. In this post, I hope to clear up this architectural snake pit.</p><h2>First things First, some Definitions&#8230;</h2><p>Some definitions are in order before exploring the finer points of architectural decision-making. It is difficult to discuss terms before agreeing on their meaning&#8230;</p><h3>Data-Transfer Objects (DTOs)</h3><p>Martin Fowler (2002) defines a DTO as &#8220;an object that carries data between processes in order to reduce the number of method calls.&#8221; This definition deftly captures the crucial difference between DTOs and the other object types we will discuss:</p><blockquote><p>DTOs are defined by their intended use, while Aggregates and Entities are defined by their contents and structure.</p></blockquote><p>It follows that:</p><blockquote><p>Entities and Aggregates may also be DTOs if used as DTOs.</p></blockquote><p>Another common use of DTOs is simplifying the presentation of information by projecting only data fields required for a particular interface into a DTO. The MVVM (Model-View-ViewModel) pattern is one excellent example. In MVVM, the <em>ViewModel</em> holds only fields required for its corresponding view. The view may be a desktop application window, web application page, terminal screen, or some other output shown to the user. Views typically require only a subset of data fields from one or several models that map to data storage structures (e.g., database tables). It follows that it would be inefficient and cumbersome to map parts of a view onto multiple large and complex models when the required data would be neatly packed into a single DTO.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eENy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eENy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 424w, https://substackcdn.com/image/fetch/$s_!eENy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 848w, https://substackcdn.com/image/fetch/$s_!eENy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 1272w, https://substackcdn.com/image/fetch/$s_!eENy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eENy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png" width="1033" height="326" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:326,&quot;width&quot;:1033,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13222,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eENy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 424w, https://substackcdn.com/image/fetch/$s_!eENy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 848w, https://substackcdn.com/image/fetch/$s_!eENy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.png 1272w, https://substackcdn.com/image/fetch/$s_!eENy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb22f4c02-ca15-4187-a3ec-4abe6da4657f_1033x326.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">Figure 1. The Model-View-ViewModel Pattern</figcaption></figure></div><p>DTOs do not require an <em>identity</em> while entities <em>must</em> have an identity. In his pivotal work <em>Domain-Driven Design</em> (2003), Eric Evans provides an interesting explanation, &#8220;Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity&#8221; (p. 89).</p><p>Consider the United States Constitution: Any copy of the Constitution will do if you simply want to read it; however, you mean something quite different if you refer to <em>The Constitution</em> stored in the National Archives. In the former, you do not care what copy you get so long as it is accurate. In the latter, you mean a specific copy of the document. The latter has an identity, while the former does not.</p><p>As Evans describes below, the latter also <em>has history</em>:</p><blockquote><p>Some objects are not defined primarily by their attributes. They represent a thread of identity that runs through time and often across distinct representations. Sometimes such an object must be matched with another object even though attributes differ. An object must be distinguished from other objects even though they might have the same attributes. Mistaken identity can lead to data corruption. (p. 91)</p></blockquote><p>Notice that entities can maintain their identity across <em>distinct representations</em>. This is a nod to projections of entities, serializations of entities, DTOs that carry entities, or entities used as DTOs. No matter the form, the data represents the same entity.</p><p>As a final note, DTOs and entities are not mutually exclusive &#8212; an entity may simultaneously be a DTO if used as one.</p><h3>Value Objects</h3><p>Evans (p. 99) defines <em>Value Objects</em> as:</p><blockquote><p>When you care only about the attributes of an element of the model, classify it as a VALUE OBJECT. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the VALUE OBJECT as immutable. Don&#8217;t give it an identity and avoid the design complexities necessary to maintain ENTITIES.</p></blockquote><p>All common programming languages include many value objects in their native APIs. Java, for example, has myriad representations of date and time information. These types are immutable, making them easily transferrable, easily serializable, simple to copy, and safe to share. Dispensing with the notion of identity frees the developer of many concerns.</p><p>Some ORMs offer built-in features for treating their <em>ORM entities</em> as <em>value objects</em>. Java&#8217;s Hibernate ORM has an <code>@Immutable</code> annotation that prevents any modifications from being persisted to an entity in the database. A developer can create a read-only ORM entity by carefully using language features or other libraries.</p><p>A common pattern I use is applying Hibernate&#8217;s <code>@Immutable</code> annotation to the <code>@Entity</code> class and only providing getters. This effectively creates a read-only entity that Hibernate will not persist changes to, and that cannot be modified after creation. Vlad Mihalcea (2019) (the Hibernate guru, in my opinion) provides a great explanation of this approach. The image below, borrowed from his article, shows the UML for such a class.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IWZY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IWZY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 424w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 848w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 1272w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IWZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png" width="440" height="491" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:491,&quot;width&quot;:440,&quot;resizeWidth&quot;:440,&quot;bytes&quot;:20600,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IWZY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 424w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 848w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.png 1272w, https://substackcdn.com/image/fetch/$s_!IWZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a61b6a6-ee92-4f54-b9a8-aaf59320d683_440x491.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">Figure 2. Immutable JPA Entity</figcaption></figure></div><p>Java 17 brought another great solution with the Java Record type. Java Records are natively read-only. Using a Domain-Driven Design service that queries the ORM and maps ORM entities into Java Records will achieve the same result. Finally, Java Records are also an excellent solution for DTOs.</p><h3>Aggregates</h3><p>Turning again to Evans (p. 125), we find the concept of an <em>aggregate</em>. Aggregates and Entities are closely related concepts because aggregates must always contain an entity that acts as its <em>root</em>. Aggregates simplify the management of complex object graphs by (1) enforcing invariants amongst the relations and (2) ensuring the atomicity of the related objects in transactions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q5zH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q5zH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 424w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 848w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q5zH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png" width="992" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f552325-7424-491d-899a-3a6ef1299336_992x811.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:992,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39680,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Q5zH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 424w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 848w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5zH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f552325-7424-491d-899a-3a6ef1299336_992x811.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">Figure 3. Example of an Aggregate that Enforces Invariants (Evans, p. 128)</figcaption></figure></div><p>Developers familiar with modern enterprise applications have likely used C#&#8217;s Entity Framework or Java&#8217;s Jakarta Persistence API (JPA). These Object-Relational Mapping (ORM) frameworks center on their concept of entity classes that map directly onto a persistence source.</p><p>Developers might assume that registering a class as an entity in one of these frameworks <em>makes</em> it an entity in the DDD sense. This is not always the case. Consider a value lookup table of states or territories. To perform CRUD (create, read, update, delete) operations via an ORM framework, you would need to register that class as an entity. You must assign one or more of the class&#8217;s fields as the entity ID. Such an object does not fit the definition of a Domain-Driven Design entity. They are defined primarily by their attributes (values) rather than their identity; they do not represent a thread of identity that runs through time and across representations, and they will never need to be matched to another object when their attributes differ. In short, they are <em>Value Objects</em> treated as entities by the ORM.</p><p>As a concrete example, one would not care which copy of a <em>State</em> object representing <em>Colorado</em> was received as long as its attributes hold the correct values. Unfortunately, if the developer wishes to manage the list of available states in a database, the ORM library would require that states be treated as ORM entities. I am not denigrating ORMs, as every record in a database <em>must</em> be treated as an entity because records do <em>represent a thread of identity that runs through time</em> and records are <em>defined primarily by their identity</em> (not their attributes). This is necessary because ORMs must track changes to each record, synchronize the state between the database and its first-level cache, and perform other similar tasks.</p><p>The bottom line is there is a certain amount of <em>impedance mismatch</em> that ORMs cannot abstract away. They must track each record individually between the middle-tier application and the persistence layer &#8212; the specific record holding Colorado&#8217;s information. On the other hand, developers view those records in the context of their domain &#8212; Colorado is a state, and any copy of Colorado will do.</p><h2>How DTOs fit into the Mix</h2><h3>Common Arguments for DTOs</h3><p>Personal experience and an Internet search yielded several common arguments for using DTOs with APIs rather than exposing entities. Before examining three frequently encountered situations where developers must choose between DTOs and entities, a study of the arguments below will provide insight into the qualities of DTOs. Then, we can apply those insights to make better-informed decisions when one of those situations arises.</p><h4>Argument 1: DTOs hide implementation details of your persistence layer</h4><p>This is a compelling argument at first glance. However, most serdes (Serialization-Deserialization) utilities (e.g., Jackson) allow you to omit certain fields from being (de)serialized; thus, you achieve the goal without adding another class to maintain. More importantly, however, <em>do you need to hide implementation details</em>? DTOs are not free; they add complexity and more code to your software. If your team will be the only users of your API, does it matter if you expose implementation details? Is the juice worth the squeeze?</p><h4>Argument 2: Handling object references (associations)</h4><p>This argument refers to the relationships between data elements (e.g., foreign key references in a relational database). I have found these situations arise most often during the (de)serialization process and usually appear as stack overflow exceptions due to cyclic references between parent-child objects. Normally, they can be easily resolved by omitting one end of the relationship (e.g., telling your serdes utility not to serialize the reference from the child back to the parent).</p><h4>Argument 3: Use-case-specific API design</h4><p>This point describes designing APIs for third-party users. In such cases, the developer seeks to make the API as user-friendly as possible by tailoring each endpoint to a specific purpose. It follows that the request and response payloads should also be purpose-driven in their structures. There is really no debating this item, as DTOs are the clear winner here.</p><h4>Argument 4: API versioning</h4><p>This argument is really a thin veil over a deeper question of decoupling your API from your persistence layer. As with the first question (of hiding implementation details), you must ask yourself if the additional complexity and maintenance are worth the looser coupling. Even if you plan to offer your API for third-party use, I suggest you hold off on unnecessary DTOs if your only motivation is API versioning. You can always introduce DTOs as a backward compatibility solution when deploying a new version.</p><h4>Argument 5: Security</h4><p>This argument normally stems from a lack of understanding of one&#8217;s ORM and is expressed in terms of <em>injection</em> or <em>mass assignment</em> vulnerabilities. The essence of either concern is that attackers might modify fields that the developer intended to restrict.</p><p>First, remember that ORM entities are not inherently more dangerous than DTOs once <em>detached</em> from the ORM session. Second, remember that they are necessarily detached by serialization when traveling between the front and back ends. Therefore, a field&#8217;s value can only be modified if the developer reattaches and merges an ORM entity received by the API.</p><p>That being said, if you find yourself fetching the current state of an entity from the data store and copying specific fields from a received ORM entity into it, you may want to consider introducing a DTO. If, instead, you are simply storing the entire modified entity, a DTO would be no more or less safe than what you are already doing.</p><h3>Use Cases Where You Must Choose Entities or DTOs</h3><p>There is no shortage of opinions about whether one should expose ORM entities to an API. As I sat brainstorming for this post, these are the three most common use cases where I encountered that question:</p><ol><li><p>When an entire object graph is required</p></li><li><p>When you want to allow partial modifications by API calls</p></li><li><p>When several data elements must be modified atomically</p></li></ol><p>I realize this list is not exhaustive, but it offers a solid basis for examining the important aspects of making this decision.</p><h4>Use Case 1: The Object Graph</h4><p>Regarding object-relational mapping, an <em>object graph</em> refers to the graph formed by object references within an entity. DDD would call this graph an <em>aggregate</em>.</p><p>If your requirements call for returning an <em>entire</em> object graph from an API endpoint, using a DTO is unlikely to be worth the additional effort and complexity it introduces. I recommend using the ORM entity until you are compelled by <em>Arguments 3 or 4</em> above. Similarly, if you need to allow API users to update an entire entity graph, I don&#8217;t believe DTOs would be worth the cost.</p><p>If you only require a subset of the fields contained by the object graph, keep reading&#8230;</p><h4>Use Case 2: Partial Modifications by API Calls</h4><p>Permitting partial updates to the object graph does not always require the introduction of DTOs. I have frequently encountered situations where I need to allow users to modify all but one or two fields of an entity (e.g., allowing users to update their profiles while preventing them from changing their roles and permissions). In trivial systems, it hardly makes sense to create an entirely separate object to simply omit one or two fields.</p><p>Instead, I code my API methods to receive the request payload as the ORM entity class. <em>Remember the class is received as a detached entity and makes no changes to the persistence layer.</em> Next, I fetch the current state of the entity from the persistence layer &#8212; this ORM entity is <em>attached</em>. Finally, I copy properties from the <em>detached</em> instance to the <em>attached</em> instance while excluding those fields I want to remain unmodified.</p><p>I have found a couple of good approaches to copying fields. The first uses either <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html">Spring Framework&#8217;s </a><code>BeanUtils</code> class or <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils&#8217; </a><code>BeanUtilsBean</code> class.</p><p>Spring&#8217;s option lends itself better to these needs. It has the signature:</p><pre><code><code>void copyProperties(Object source, Object target, String... ignoreProperties)</code></code></pre><p>An example of this approach would look like:</p><pre><code><code>@PutRequest
ResponseEntity&lt;void&gt; updateProfile(@RequestBody User user) {
    final User attachedUser = userRepo.findById(user.getId());
    BeanUtils.copyProperties(
        user,
        attachedUser,
        "roles", "permissions"
    );
    userRepo.save(user);
    return ResponseEntity.ok().build();
}</code></code></pre><p>A second approach is using a mapping library. The most notable example in Java is <em><a href="http://modelmapper.org/">ModelMapper</a></em>. ModelMapper enables you to handle very complex situations and is probably overkill for copying fields between two objects of the same class. I will cover it in more detail in the next part of this series, where I show you how to introduce DTOs in a manageable way.</p><p>For the sake of completeness, however, here is an example of using ModelMapper to copy from a <em>detached</em> entity to an <em>attached</em> one while omitting certain fields:</p><pre><code><code>static final ModelMapper mapper = new ModelMapper();
static final TypeMap&lt;User, User&gt; safeUserMap = mapper.createTypeMap(User.class, User.class, "safeUserMapper");

static {
    safeUserMap.addMappings(mapper -&gt; {
        mapper.skip(User::setRoles);
        mapper.skip(User::setPermissions);
    });
}

@PutRequest
ResponseEntity&lt;void&gt; updateProfile(@RequestBody User user) {
    final User attachedUser = userRepo.findById(user.getId());
    mapper.map(user, attachedUser, "safeUserMapper");
    userRepo.save(user);
    return ResponseEntity.ok().build();
}</code></code></pre><p>I would be remiss if I failed to mention Spring&#8217;s <code>@Projection</code> feature. It is extremely handy when you only need a subset of a single entity&#8217;s data fields. Using Spring Data, you can create an interface that contains only the accessors (i.e., getters) of an entity that you need. If you code methods of a Spring Repository to return the projection type, Spring will query for only those fields and map them into an implementation of that interface (i.e., a DTO). If you also wish to receive a projection, you would need to build a DTO class that implements the interface (as Jackson cannot deserialize to an interface without an implementation).</p><p>Here&#8217;s a quick example of using the <code>@Projection</code> feature:</p><pre><code><code>@Entity
class User {
    @Id
    Long userId;
    Collection&lt;String&gt; roles;
    String firstName;
    String lastName;
    String password;
}

@Projection
interface SafeUser {
    Long getUserId();
    String getFirstName();
    String getLastName();
}

@Repository
interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;SafeUser&gt; findByUserId(Long userId);
}

@RestController
class UserController {
    @Autowired UserRepository userRepo;
    
    @PutRequest("/user/{id}")
    SafeUser fetchUser(@PathVariable Long id) {
        return userRepo.findByUserId(id).orElseThrow(() =&gt;
            new ResponseStatusException(HttpStatus.NOT_FOUND));
    }
}</code></code></pre><h4>Use Case 3: Atomic Modifications by API Calls</h4><p>Special care must be taken when an API endpoint receives a collection of fields that must be applied to multiple entities <em>atomically</em>.</p><blockquote><p>Atomic modifications must be executed as a single unit that either completely succeeds or is rolled back.</p></blockquote><p>I cannot imagine a <em>use case 3</em> situation where you would not want to use a DTO. Any alternative would require multiple API calls to send the various entities involved (which would make atomicity all but impossible), or it would require creating a synthetic entity that acts as a wrapper object for the others. Both options are unappealing.</p><p>Instead, you should create a DTO that carries the involved fields. You should also create a <em>Data Mapper</em> (Fowler, p. 165) that moves data between your ORM layer and the DTO. Finally, you should persist the updated ORM entities atomically within a single transaction.</p><p>Consider a point-of-sale system for a vehicle service center. When a customer purchases a service package, several updates must be made atomically to the center&#8217;s database:</p><ul><li><p>Money must be transacted</p></li><li><p>Lubricant and other supply inventory must be updated</p></li><li><p>The vehicle&#8217;s service record must be updated</p></li><li><p>History of the driver&#8217;s visit must be recorded</p></li></ul><p>The DTOs for this use case might look like this:</p><pre><code><code>class LineItemDto {
    private LineItemType type; // service or material
    private Optional&lt;BigDecimal&gt; quantity; // amount or hours
    private Long lineItemId;
}

class ServiceTransactionDto {
    private Collection&lt;PurchaseLineItem&gt; lineItems;
    private String vehicleVin;
    private Long customerId;
}</code></code></pre><p>These two simple classes hide a lot of complexity within the underlying persistence layer. Thankfully, the API endpoint need only know about the <code>ServiceTransactionDto</code> and the <em>Data Mapper</em> that will handle creating service records for the vehicle, billing the client, updating inventory, and more. Using the Spring Framework, the API endpoint might look like this:</p><pre><code><code>@Transactional
@PutRequest
void updateProfile(@RequestBody ServiceTransactionDto serviceTx) {
    serviceTxMapper.update(serviceTx);
}</code></code></pre><p>Behind the scenes, the data mapper will use the DTO&#8217;s data to apply updates to many other entities. While the <em>Data Mapper</em> pattern is out of this article&#8217;s scope, I encourage you to read that section of Fowler&#8217;s book.</p><h2>Conclusion</h2><p>Key takeaways from this article are:</p><ul><li><p>ORM entities are not necessarily DDD entities</p></li><li><p>DTOs do not always make sense &#8212; when given the choice, do a cost-benefit analysis before introducing DTOs</p></li><li><p>There are several common arguments for using DTOs, but only a couple really hold water</p></li><li><p>I&#8217;ve found three use cases where I frequently have to choose between exposing entities or creating DTOs &#8212; I present several options for dealing with these</p></li></ul><p>Check back for part two of this series for tips on effectively managing DTOs.</p><div><hr></div><ul><li><p>Evans, E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Pearson Education, Inc.</p></li><li><p>Fowler, M. (2002). Patterns of Enterprise Application Architecture (1st ed.). Addison-Wesley Professional.</p></li><li><p>Mihalcea, V. (2019, August 20). <em>How to map an immutable entity with JPA and Hibernate - Vlad Mihalcea</em>. Vlad Mihalcea. https://vladmihalcea.com/immutable-entity-jpa-hibernate/</p></li></ul>]]></content:encoded></item><item><title><![CDATA[How to GitOps]]></title><description><![CDATA[An Introduction to GitOps via Kubernetes and ArgoCD]]></description><link>https://clintonbush.substack.com/p/how-to-gitops</link><guid isPermaLink="false">https://clintonbush.substack.com/p/how-to-gitops</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Fri, 29 Mar 2024 00:52:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!WOLy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What is GitOps?</h2><p>What DevOps did for software development, GitOps has done for infrastructure and deployment. Virtualization and containerization were the advent of a revolution that saw organizations get the most out of their capital investments, cut costs everywhere possible, and speed up much of their operations.&nbsp;</p><p>Surprisingly, IT infrastructure still lacked the structure, discipline, and agility that have been commonplace in development for over a decade. The introduction of declarative provisioning (Infrastructure as Code) technologies like Terraform, AWS CloudFormation, Docker Stack, and Kubernetes Objects brought infrastructure a step closer to rectifying these issues because their code could be stored and managed just like other source code. At first glance, Git is a surprising tool for managing infrastructure, but it makes sense when one views it as a&nbsp;<em>configuration management</em>&nbsp;system, not just a version control system. This arrangement was still lacking in essential ways &#8212; notably, automation (of applying changes) and reconciliation (correcting drift caused by changes made to the environment outside of IaC).&nbsp;</p><p>In 2017, Weaveworks CEO Alexis Richardson coined the term&nbsp;<em>GitOps</em>(Weaveworks, 2021). It was the culmination of his organization&#8217;s slow progression from practicing a form of GitOps-lite to today&#8217;s accepted standard. Around that time, Weaveworks released its&nbsp;<em>Weave Flux</em>&nbsp;tool that provided the two final pieces &#8212; automation and reconciliation. Unsurprisingly, Richardson himself defined the standard for GitOps:</p><ol><li><p>The entire system is described declaratively</p></li><li><p>The canonical desired system state is versioned in Git</p></li><li><p>Approved changes that can be automatically applied to the system</p></li><li><p>Software agents to ensure correctness and alert on divergence</p></li></ol><p>There still needs to be a formal or official definition for GitOps. The four principles above are the closest thing available to such a definition. It is possible, however, to extrapolate a definition based on the GitOps principles and other authoritative sources. As a springboard for the remainder of this article, let us agree to define&nbsp;<em>GitOps</em>&nbsp;as:</p><blockquote><p>The use of Git to enforce technical reviews, version control, and canonical recording of changes to Infrastructure as Code combined with the use of other tools to automate the deployment of changes to IaC, eliminating divergence of the system&#8217;s actual state from the state in Git.</p></blockquote><h2>How GitOps Works</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WOLy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WOLy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 424w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 848w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 1272w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WOLy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png" width="1456" height="617" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:617,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135479,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WOLy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 424w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 848w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.png 1272w, https://substackcdn.com/image/fetch/$s_!WOLy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4701124e-43ca-42ac-8c95-507652c6d79d_2545x1078.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><figcaption class="image-caption">Figure 1. The GitOps Flow</figcaption></figure></div><ol><li><p><strong>Developers</strong>&nbsp;(the stick figure person)</p></li><li><p><strong>Reconciler</strong>&nbsp;(the GitOps management platform)</p></li><li><p><strong>Convergence Mechanism</strong>&nbsp;(the system applying IaC to an environment &#8212; Kubernetes, CloudFormation, Terraform, etc.)</p></li></ol><p>These three actors work together in an elegant dance we call&nbsp;<em>GitOps</em>. Richardson (2018) choreographs that dance as four distinct steps:</p><ol><li><p>A&nbsp;<strong>developer</strong>&nbsp;describes the desired state of the system using a declarative syntax (e.g., Kubernetes objects or Terraform) and commits that code to a Git repository. Ideally, another developer must approve a pull request before the changes are merged into the&nbsp;<code>master</code> or <code>main</code>&nbsp;branch.</p></li><li><p>The&nbsp;<strong>reconciler</strong>&nbsp;detects that the desired state (what is reflected by the Git repository) and the actual state are not the same. It activates the new state by applying it to the convergence mechanism.</p></li><li><p>The&nbsp;<strong>convergence mechanism</strong>&nbsp;adjusts the actual state to match the desired state. Thus, Git commits &#8220;cause verifiable and idempotent updates in the cluster&#8221; (idempotent&nbsp;means an action may be repeated any number of times but always results in the same outcome).</p></li><li><p>Convergence is achieved once there are no more &#8220;&#8216;diff&#8217; alerts during a defined time interval.&#8221; Convergence is the state where the desired state and actual state match.</p></li></ol><h2>What do you get from GitOps?</h2><p>GitOps borrows many of its benefits from its most fundamental component, Infrastructure as Code. These benefits include:</p><ul><li><p>Consistency: all infrastructure (including both servers and containers) is defined in the same way</p></li><li><p>Security: all configurations are codified in a single place rather than manually applied in an infinite number of ways</p></li><li><p>Access control: all system changes must pass through a single spot and (ideally) go through a peer review; further, changes are applied by an automated software agent</p></li><li><p>Auditing: all changes are retained in version history</p></li><li><p>Authority: there is a single source of truth for the system&#8217;s desired state</p></li></ul><p>GitOps also adds the benefits of continuous delivery, including:</p><ul><li><p>Repeatability: changes are deployed the same way, every time</p></li><li><p>Speed: deployments are faster</p></li><li><p>Reliability: automation eliminates human error arising from manual deployments</p></li><li><p>Visibility: most of the popular tools include dashboards of their own and integrate with other monitoring systems</p></li></ul><h2>Survey of GitOps Management Platforms</h2><p>The leaders of the GitOps market generally fall into one of two categories: those managing&nbsp;<em>application</em>&nbsp;deployment and those managing&nbsp;<em>infrastructure</em>&nbsp;provisioning. It is important to note how the term &#8220;infrastructure&#8221; is somewhat overloaded. While it generally refers to resources provisioned on your cloud platform (e.g., AWS, GCP, Azure), when used in &#8220;Infrastructure as Code,&#8221; it also refers to resources provisioned by a container orchestrator as part of an application deployment.</p><blockquote><p>It is important to note how the term &#8220;infrastructure&#8221; is somewhat overloaded. While it generally refers to resources provisioned on your cloud platform (e.g., AWS, GCP, Azure), when used in &#8220;Infrastructure as Code,&#8221; it also refers to resources provisioned by a container orchestrator as part of an application deployment.</p></blockquote><h3>GitOps Managers for Application Deployment</h3><p><strong><a href="https://fluxcd.io/">Flux</a></strong>&nbsp;is a free and open-source Kubernetes application that reconciles the state of a Kubernetes environment to match IaC code stored in a Git repository. It is a tool managed by the Cloud Native Computing Foundation (CNCF) and was created by Weave Works. It does not include a UI and is more often used as the core of more comprehensive GitOps tools.</p><p><strong><a href="https://www.weave.works/product/gitops-enterprise/">Weave GitOps Enterprise</a></strong>&nbsp;is a comprehensive GitOps management platform built by Weave Works. It is a paid option that uses Flux as its reconciler. It comes with a mature and user-friendly web-based GUI. This product also supports Terraform GitOps by including Weave Works&#8217; Terraform Controller as an additional reconciler.&nbsp;</p><p><strong><a href="https://argoproj.github.io/cd/">ArgoCD</a></strong>&nbsp;is one of the most popular free and open source GitOps platforms. ArgoCD is a full-featured option that includes a very mature and advanced GUI. One of its most exciting features is its display of Kubernetes deployments as graphs that update in real-time. It treats Kustomize as a first-class concern and automatically recognizes Kustomize projects.</p><p><strong><a href="https://jenkins-x.io/">Jenkins X</a></strong>&nbsp;takes an even more comprehensive approach by automating both CI and CD. It unifies the integration of applications and deployment of their artifacts with the deployment of infrastructure changes. Jenkins X seeks to act as a &#8220;GitOps appliance&#8221; that just works. One particularly useful feature is its tight integration with cloud computing environments. Under the hood, it leverages Terraform to use cloud-native services, regardless of the provider. An important thing to note is that Jenkins X is exclusively controlled via CLI and config files in Git. It offers two GUIs, but both are read-only.</p><p><strong><a href="https://docs.gitlab.com/ee/user/infrastructure/">GitLab</a></strong>&nbsp;is the Swiss Army knife of CI/CD platforms. It does everything, including serving as your Git repository, continuous integration, continuous deployment, security scanning, and more. It uses Flux as its reconciler to synchronize Kubernetes with IaC files. GitLab also integrates tightly with Terraform environments, providing and managing your cloud infrastructure.</p><h3>GitOps Managers for Infrastructure Provisioning</h3><p><strong><a href="https://www.runatlantis.io/">Atlantis</a></strong>&nbsp;is a barebones, battle-tested tool for automating Terraform via GitOps. Its primary user interface is a sequence of Git comments on pull requests. Atlantis lacks support for drift detection. If you&nbsp;<em>only </em>want to automate Terraform through GitOps and self-hosting is required, this is the tool for you.</p><p><strong><a href="https://www.terraform.io/">Terraform Cloud</a></strong>&nbsp;is a complete solution for using the Terraform tool for GitOps. It includes collaborative features for teams, native GitOps support, drift detection, and more. Terraform Cloud is primarily a SaaS offering, but their Enterprise pricing plan does include a self-hosted option.</p><p><strong><a href="https://spacelift.io/">Spacelift</a></strong>&nbsp;is a direct competitor to Terraform Cloud. Spacelift seeks to be a unified GitOps tool by supporting all the major Infrastructure as Code offerings. These include OpenTofu, Terraform, Terragrunt, CloudFormation, Pulumi, Kubernetes, and Ansible. It goes a step further by also supporting Policy as Code by leveraging Open Policy Agent and its Rego language. Like Terraform Cloud, Spacelift is, first and foremost, a SaaS offering but also has a self-hosted option attached to its Enterprise pricing plan. Spacelift differentiates itself with its clean and intuitive user interface. It also has a unique feature that allows testing IaC modules before deploying them.</p><p><strong><a href="https://www.env0.com/">env0</a></strong>&nbsp;is another offering that is very similar to Terraform Cloud and Spacelift.&nbsp;</p><p>It supports OpenTofu, Terraform, Terragrunt, CloudFormation, Pulumi, Kubernetes, and Helm. It differentiates itself with a robust templating feature. DevOps teams can create useful templates and allow developers downstream to deploy these templates. Essentially, downstream teams can choose from a catalog of templates and deploy ready-to-use environments for their projects.</p><h2>Setting up GitOps Locally</h2><p>So far, this article has built a foundation of understanding for GitOps, how it works, its benefits, and the leaders in the GitOps marketplace. It is time to put this all into practice by building, configuring, and testing a GitOps stack. The tools chosen for this task include&nbsp;<em>GitHub</em>,&nbsp;<em>Docker Hub</em>,&nbsp;<em>Kubernetes</em>,&nbsp;<em>Kustomize</em>, and&nbsp;<em>ArgoCD</em>. All example CLI commands are based on macOS.</p><h3>The Flow (or&nbsp;<em>the Vision</em>)</h3><p>Before rolling up our sleeves, we need a vision for the outcome. The best way to define that vision is by describing how the GitOps process should work upon completion.</p><p>Here is an outline of that flow:</p><ol><li><p>Developers commit Kubernetes/Kustomize IaC changes to a GitHub repository</p></li><li><p>When changes are merged to the&nbsp;<code>main</code>&nbsp;or&nbsp;<code>master</code>&nbsp;branch, the&nbsp;<em>reconciler</em>&nbsp;(ArgoCD) applies the changes</p></li><li><p>When a new version of the application is pushed to Docker Hub with the&nbsp;<code>sha-#####</code>&nbsp;tag, the&nbsp;<code>reconciler</code>&nbsp;detects this and pulls that image for the&nbsp;<code>dev</code>&nbsp;namespace</p></li><li><p>When a new version of an image is pushed to Docker Hub and tagged as&nbsp;<code>v#.#.#-RC</code>&nbsp;(release candidate), it is pulled for the&nbsp;<code>test</code>&nbsp;namespace</p></li><li><p>When a new version of an image is pushed to Docker Hub and tagged as&nbsp;<code>v#.#.#</code>, it is pulled for the&nbsp;<code>prod</code>&nbsp;namespace</p></li></ol><p>One additional requirement is load-balancing across replica sets.</p><h3>Installing and Configuring Kubernetes (k8s)</h3><h4>Install the Tools</h4><p>The easiest way to set k8s up for a local environment is via Docker Desktop.&nbsp;</p><ol><li><p>On a Mac, you can install Docker Desktop via&nbsp;<code>brew</code>&nbsp;by running:</p></li></ol><pre><code><code>brew install --cask docker</code></code></pre><ol><li><p>Open Docker Desktop, navigate to&nbsp;<strong>Settings</strong> and select&nbsp;<strong>Kubernetes</strong>.</p></li><li><p>Check the&nbsp;<strong>Enable Kubernetes</strong>&nbsp;option and click&nbsp;<strong>Apply &amp; restart</strong>.</p></li></ol><p>You will also want to install a few Kubernetes utilities:</p><pre><code><code>brew install kubernetes-cli kubectx k9s</code></code></pre><p>These add the&nbsp;<code>kubectl</code>,&nbsp;<code>kubectx</code>, and&nbsp;<code>k9s</code>&nbsp;commands to your computer, respectively.&nbsp;<code>kubectl</code>&nbsp;is the primary CLI tool for managing k8s.&nbsp;<code>kubectx</code>&nbsp;allows you to view k8s contexts (clusters) and select the one you wish to interact with. You probably won't need this unless you already have multiple clusters configured. Finally,&nbsp;<code>k9s</code>&nbsp;is a TUI (textual user interface) for managing k8s. It is particularly useful when editing config files, scaling replica sets, and other common tasks.</p><h4>Configure k8s Namespaces</h4><p>Namespaces allow separating k8s objects with identical names. For our purposes, they will act as various deployment environments. We will need four namespaces:&nbsp;<code>argocd</code>&nbsp;to store ArgoCD&#8217;s applications, and&nbsp;<code>dev</code>,&nbsp;<code>test</code>, and&nbsp;<code>prod</code>, for our application deployments.</p><p>In a terminal, execute these commands:</p><pre><code><code>kubectl create ns argocd
kubectl create ns dev
kubectl create ns test
kubectl create ns prod</code></code></pre><h4>MetalLB&#8230;a Load Balancer</h4><p>While k8s Replica Sets allow horizontal scaling of applications, they are useless if you cannot distribute traffic across all the active instances. Normally, k8s clusters will leverage load balancers supplied by their cloud providers. When running k8s locally, however, a k8s-native load balancer like&nbsp;<a href="https://metallb.org/">MetalLB</a>&nbsp;is required.</p><p>To add it to the local k8s cluster, execute this command in the terminal:</p><pre><code><code>kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-frr.yaml</code></code></pre><p>Before it can be used, MetalLB must be told what IP addresses it can choose from when selecting an IP to announce for a k8s service. CIDR blocks are used to specify these range(s). In the case of this example, we need the addresses used by the load balancer to loop back to the host. Thus, we&#8217;ll allocate a small section of the loop back CIDR block:&nbsp;<code>127.0.0.240/28</code>. This is a bit of a &#8220;trick&#8221; because even though MetalLB might assign a service the IP&nbsp;<code>127.0.0.241</code>&nbsp;and expose it on port&nbsp;<code>8882</code>, you will still access it at&nbsp;<code>127.0.0.1:8882</code>&nbsp;in your browser.&nbsp;</p><p>Now, configure MetalLB to use this IP CIDR block by creating an IP Address Pool object. Then, tell MetalLB to announce these new IP addresses to the local network via ARP.&nbsp;</p><p>Copy the code below into a text file named&nbsp;<code>lb-ip-config.yaml</code>:</p><pre><code><code>apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: localhost-address-pool
  namespace: metallb-system
spec:
  addresses:
    - 127.0.0.240/28
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: ipadvertisement
  namespace: metallb-system</code></code></pre><p>Apply this file to your k8s cluster:&nbsp;<code>kubectl apply -f lb-ip-config.yaml</code></p><h4>What did we just do?</h4><p>We need a load balancer to allocate an IP address for k8s services and distribute traffic across instances of that service&#8217;s app. Above, we configured MetalLB as our load balancer and told it to select from the IP address range represented by the CIDR block&nbsp;<code>127.0.0.240/28</code>.&nbsp;</p><p>Later, we will deploy a horizontally scalable app &#8212; meaning multiple copies of the app run simultaneously. In k8s-speak, this is known as a replica set. Although we will be running three copies of the app, we want it to appear as a single app to our users. We also want to&nbsp;<em>load balance </em>traffic across the replica set. To do this, we will create a Kubernetes&nbsp;<code>Service</code>&nbsp;of type&nbsp;<code>LoadBalancer</code>. MetalLB will be used as the load balancer.&nbsp;</p><p>In production, a cloud provider&#8217;s load balancer would be used rather than MetalLB. For example, Amazon Web Services&#8217; Elastic Load Balancer (ELB) might be configured as a load balancer and would expose our services.</p><h3>Installing and Configuring ArgoCD</h3><p>Before testing the load balancer, we need an app to deploy. Before we can deploy an app, we need a&nbsp;<em>Continuous Deployment</em>&nbsp;framework to automate those deployments. Thus, we need to set up our GitOps tool:&nbsp;<em><a href="https://argoproj.github.io/cd/">ArgoCD</a></em>.</p><h4>Installing ArgoCD</h4><p>We created the&nbsp;<code>argocd</code>&nbsp;namespace earlier, so all we need to do here is apply the k8s deployment:</p><pre><code><code>kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml</code></code></pre><p>Once that&#8217;s complete, we must expose our new ArgoCD server. Because we&#8217;ve already configured MetalLB, let us leverage that by configuring the ArgoCD&nbsp;<code>Service</code>&nbsp;as type&nbsp;<code>LoadBalancer</code>:</p><pre><code><code>kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'</code></code></pre><p>Confirm everything is working correctly by navigating to&nbsp;<code>https://localhost</code>.</p><p>Because ArgoCD uses a self-created certificate, you must tell your browser to navigate to the site despite its misgivings.</p><h4>Installing ArgoCD Image Updater</h4><p>By itself, ArgoCD solves one of two automation problems &#8212; it ensures deployments match, in reality, what is described by their IaC (Infrastructure as Code). However, there is no out-of-the-box mechanism for updating the Docker image used by a deployment as new versions of that image are released. This means that ArgoCD cannot deploy application updates automatically.&nbsp;</p><p>There are two obvious ways of approaching this problem. First, you could specify an exact image tag to use in your k8s code (e.g., my app:v1.1.3). As you create new versions of your app, you could change that tag and merge those changes into the repository where your k8s code files are kept. I don&#8217;t particularly like this option because it conflates responsibilities. The k8s code is responsible for defining the structure of a deployment. Using it to define the version of the app being deployed is an anti-pattern.&nbsp;</p><p>This leads to the second approach: introduce a second reconciler that tracks Docker image versions and ensures ArgoCD always uses the latest version. This is what the&nbsp;<a href="https://argocd-image-updater.readthedocs.io/">ArgoCD Image Updater</a>&nbsp;does.&nbsp;</p><p>The simplest way to install Image Updater is via Kubernetes manifest. You just need to execute this code in your terminal:</p><pre><code><code>kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml</code></code></pre><p>Note that I encountered an issue with the Image Updater refusing to accept Docker Hub&#8217;s x509 certificate. To resolve it, I patched the config. I recommend you do the same for these purposes (in a real-world deployment, find a way to resolve the issue that doesn&#8217;t impact your security posture).</p><pre><code><code> kubectl patch -n argocd configmap/argocd-image-updater-config --patch 'data:
  log.level: debug
  registries.conf: |
    registries:
    - name: Docker Hub
      insecure: yes
      api_url: https://registry-1.docker.io
      prefix: docker.io
      defaultns: library'</code></code></pre><h4>Registering a GitHub Repository</h4><p>The final task before we build our app is creating a GitHub repository and registering it with ArgoCD.&nbsp;</p><h5><strong>Create a GitHub Repository</strong></h5><p>Create a GitHub repository named&nbsp;<code>patient-portal-argocd</code>&nbsp;(this is to be consistent with examples from previous blog posts).&nbsp;<strong>Ensure you initialize it with a README file.</strong>&nbsp;If the new repository does not have content or a branch, ArgoCD will fail to connect to it.</p><p>Next, create a new SSH key pair. In your terminal, navigate to&nbsp;<code>~/.ssh</code>. Execute&nbsp;<code>ssh-keygen</code>. At the first prompt, name the key pair&nbsp;<code>argocd</code>. Press ENTER to accept the defaults for all subsequent prompts.</p><p>In your terminal, execute&nbsp;<code>cat ~/.ssh/argocd.pub</code>. Copy the output to your clipboard.</p><p>In GitHub, navigate to your account settings and select &#8220;SSH and GPG keys&#8221; in the navigation bar. Add a new SSH key. Name it &#8220;ArgoCD&#8221; and paste the contents of your clipboard into the&nbsp;<code>Key</code>&nbsp;textbox.</p><h5><strong>Register the GitHub Repository with ArgoCD</strong></h5><p>Before we proceed, we need to get your ArgoCD admin password. In your terminal, execute&nbsp;<code>k9s</code>&nbsp;to open the k9s Kubernetes tool.</p><p>Type&nbsp;<code>:secrets</code>. Highlight&nbsp;<code>argocd-initial-admin-secret</code>&nbsp;and press the&nbsp;<code>x</code>&nbsp;key. Copy the password shown.</p><p>Open your browser and navigate to:&nbsp;https://localhost. Log into ArgoCD using the username&nbsp;<code>admin</code>&nbsp;and the password you copied.</p><p>Click&nbsp;<strong>Settings</strong>&nbsp;on the left side of the screen. Choose&nbsp;<strong>Repositories</strong>&nbsp;and then&nbsp;<strong>+ Connect Repo</strong>.&nbsp;</p><p>Name the repository&nbsp;<code>GitHub patient-portal-argocd</code>. Select&nbsp;<code>default</code>&nbsp;for&nbsp;<strong>Project</strong>. Copy and paste the GitHub repository SSH URL to the&nbsp;<strong>Repository URL</strong>&nbsp;textbox in ArgoCD. For example, it should look similar to:&nbsp;<code>git@github.com:____/patient-portal-argocd.git&nbsp;</code>.</p><p>In your terminal, execute:&nbsp;<code>cat ~/.ssh/argocd</code>. Copy the output and paste it into the&nbsp;<strong>SSH private key data</strong>&nbsp;textbox in ArgoCD. Click the&nbsp;<strong>CONNECT</strong>&nbsp;button.</p><h4>What did we just do?</h4><p>You accomplished three major tasks:</p><ol><li><p>We installed ArgoCD on your Kubernetes cluster and configured it to be served up by the MetalLB load balancer</p></li><li><p>We installed the ArgoCD Image Updater that will ensure any Kubernetes deployments always have the latest version of Docker images</p></li><li><p>We created a new GitHub repo to store our IaC and configured ArgoCD to add that repository to ArgoCD</p></li></ol><p>Later, we will actually create some IaC files, push them to the&nbsp;<code>patient-portal-argocd</code>&nbsp;repository, and configure ArgoCD to deploy that project.</p><h2>Creating a Simple Vite/VueJS Project</h2><p>We need an app that can demonstrate a successful configuration of all parts of our project. At a minimum, it should do the following:</p><ol><li><p>Display the current Docker image version (i.e., tag name)</p></li><li><p>Display the pod&#8217;s hostname</p></li><li><p>Be easily testable and buildable by GitHub actions</p></li><li><p>Be packageable as a single Docker image</p></li></ol><p>Vite is a tool that makes it extremely easy to scaffold, build, and package NodeJS apps based on common frameworks like VueJS, React, and Svelte.&nbsp;</p><h3>Building the App</h3><h4>Scaffold the Project</h4><p>Navigate to a directory where you keep your coding projects. Execute the code below:</p><pre><code><code>npm create vite-extra@latest patient-portal -- --template ssr-vue</code></code></pre><p>When prompted, simply accept the defaults (respond with&nbsp;<code>Y</code>es)</p><h4>Create a Git Repository for the App</h4><p>Create a new GitHub repository named&nbsp;<code>patient-portal</code>.&nbsp;<strong>Do not initialize it with anything.</strong></p><p>In your terminal, navigate to the new project folder (created in the step above &#8212;&nbsp;<strong>Scaffold the Project</strong>) and execute the following commands.&nbsp;<strong>Ensure you replace the blank with your repo&#8217;s SSH URL!</strong></p><pre><code><code>git init
git add .
git commit -am "Initial commit"
git branch -M master
bit remote add origin _______
git push -u origin master</code></code></pre><h4>Display Git and Host Information</h4><p>We will use the&nbsp;<code>dotenv</code>&nbsp;utility to provide our app with version information. Vite already integrates this, but we must create a&nbsp;<code>.env.local</code> file and tell Vite where to find it.</p><p>Create a directory called&nbsp;<code>env</code>&nbsp;at&nbsp;<code>patient-portal/src/env</code>. In that directory, create a file named&nbsp;<code>.env.local</code>&nbsp;and paste these contents into it:</p><pre><code><code>VITE_GIT_REF=v1.0.0</code></code></pre><p>Next, edit&nbsp;<code>patient-portal/src/vite.config.js</code>&nbsp;so it matches this code:</p><pre><code><code>import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  envDir: 'src/env'
})</code></code></pre><p>We will inject the hostname into VueJS&#8217; server-side rendering (SSR) context. To do this, edit&nbsp;<code>patient-portal/src/entry-server.js</code>&nbsp;so it matches this code:</p><pre><code><code>import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'
import * as os from "os";

export async function render() {
  const { app } = createApp()

  const ctx = {
    hostname: os.hostname()
  }
  const html = await renderToString(app, ctx)

  return { html }
}</code></code></pre><p>Finally, let&#8217;s rework the default&nbsp;<code>HelloWorld</code>&nbsp;Vue component to display the two bits of information we want. Edit&nbsp;<code>patient-portal/src/components/HelloWorld.vue</code>&nbsp;so it matches this code:</p><pre><code><code>&lt;script setup lang="ts"&gt;
import {ref, useSSRContext} from 'vue'

defineProps({
  msg: String
})

const gitTagCommit = ref(import.meta.env.VITE_GIT_REF);
let ctx : Record&lt;string, any&gt; | undefined;

if(import.meta.env.SSR) {
  ctx = useSSRContext();
}
&lt;/script&gt;

&lt;template&gt;
  &lt;h1&gt;{{ msg }}&lt;/h1&gt;

  &lt;div class="card"&gt;
    &lt;span&gt;&lt;strong&gt;Git Tag/Commit&lt;/strong&gt;&lt;/span&gt;
    &lt;span class="data"&gt;{{ gitTagCommit }}&lt;/span&gt;
    &lt;span&gt;&lt;strong&gt;Host&lt;/strong&gt;&lt;/span&gt;
    &lt;span class="data"&gt;{{ ctx?.hostname ?? '' }}&lt;/span&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;style scoped&gt;
.card {
  display: grid;
  grid-template-columns: 10rem auto;
  row-gap: 0.5rem;

  &amp; span {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
}

.data {
  background-color: #1a1a1a;
  border-radius: 8px;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
}
&lt;/style&gt;</code></code></pre><p>Your&nbsp;<code>patient-portal</code>&nbsp;directory structure should look like this:</p><pre><code><code>.
&#9500;&#9472;&#9472; README.md
&#9500;&#9472;&#9472; index.html
&#9500;&#9472;&#9472; package.json
&#9500;&#9472;&#9472; public
&#9474;&nbsp;&nbsp; &#9492;&#9472;&#9472; vite.svg
&#9500;&#9472;&#9472; server.js
&#9500;&#9472;&#9472; src
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; App.vue
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; assets
&#9474;&nbsp;&nbsp; &#9474;&nbsp;&nbsp; &#9492;&#9472;&#9472; vue.svg
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; components
&#9474;&nbsp;&nbsp; &#9474;&nbsp;&nbsp; &#9492;&#9472;&#9472; HelloWorld.vue
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; entry-client.js
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; entry-server.js
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; env
&#9474;&nbsp;&nbsp; &#9474;&nbsp;&nbsp; &#9492;&#9472;&#9472; .env.local
&#9474;&nbsp;&nbsp; &#9500;&#9472;&#9472; main.js
&#9474;&nbsp;&nbsp; &#9492;&#9472;&#9472; style.css
&#9492;&#9472;&#9472; vite.config.js</code></code></pre><h4>Updating the Git Commit Hash and Tags Automatically</h4><p>We want to ensure the Git ref information is updated each time the app is built for our dev, test, or prod environments. Tags will be used to trigger deployments to test and prod; therefore, we want to show a commit for dev and tag names for test and prod. The simplest way to do this is to use a build script that updates the&nbsp;<code>.env.local</code>&nbsp;file before building. Of special note, the&nbsp;<code>.env.local</code>&nbsp;file is ignored by Git, so it will be freshly created by GitHub Actions every time.</p><p>Navigate to your&nbsp;<code>patient-portal</code>&nbsp;project&#8217;s root directory and run the commands below:</p><pre><code><code>cat &gt; build.sh &lt;&lt; EOF
write_properties () {
  echo "Building for \$1"
  
  mkdir -p ./src/env
  
  if [ \$1 = 'dev' ]; then
    echo "VITE_GIT_REF=\$(git rev-parse --short HEAD)"
    echo "VITE_GIT_REF=\$(git rev-parse --short HEAD)" &gt; ./src/env/.env.local
  elif [ \$1 = 'test' ] || [ \$1 = 'prod' ]; then
    echo "VITE_GIT_REF=\$(git describe --tags)"
    echo "VITE_GIT_REF=\$(git describe --tags)" &gt; ./src/env/.env.local
  else
    echo "Argument 1 must be dev, test, or prod. \$1 is not recognized."
    return 1
  fi
  npm run build
}

write_properties \$1
EOF

chmod +x build.sh</code></code></pre><p>Finally, ensure you commit and push all your changes:</p><pre><code><code>git add .
git commit -am "Modified project to show version data and added automated build script."
git push</code></code></pre><h4>Test the App</h4><p>Navigate to the application&#8217;s root directory in your terminal and execute the following commands:</p><pre><code><code>npm install
./build.sh dev
npm run dev</code></code></pre><p>If all goes well, you should be able to navigate to the URL specified in the command output.&nbsp;<strong>Your URL may not be the same as the one in the image below.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kZnG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kZnG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 424w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 848w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 1272w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kZnG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png" width="376" height="85" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:85,&quot;width&quot;:376,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:16766,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kZnG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 424w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 848w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 1272w, https://substackcdn.com/image/fetch/$s_!kZnG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91856055-6e72-4a7a-87d1-51dddd9c3f4e_376x85.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Figure 2. Vite Dev Server Startup</figcaption></figure></div><p>The web page should look similar to this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uS03!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uS03!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 424w, https://substackcdn.com/image/fetch/$s_!uS03!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 848w, https://substackcdn.com/image/fetch/$s_!uS03!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 1272w, https://substackcdn.com/image/fetch/$s_!uS03!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uS03!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png" width="699" height="441" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:441,&quot;width&quot;:699,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30659,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uS03!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 424w, https://substackcdn.com/image/fetch/$s_!uS03!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 848w, https://substackcdn.com/image/fetch/$s_!uS03!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.png 1272w, https://substackcdn.com/image/fetch/$s_!uS03!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b14713a-ccea-4e18-9aa6-712bc5a6ff62_699x441.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">Figure 3. Vite App Home Page</figcaption></figure></div><h3>Packaging the App in a Container</h3><p>We have a functioning app, but we need to package it in a Docker container so we can deploy it to a Kubernetes cluster.</p><p>In the root of your project, create a new file named&nbsp;<code>Dockerfile</code>. Use the following code for its contents:</p><pre><code><code>FROM node

RUN mkdir /opt/app
COPY package.json /opt/app
COPY dist/  /opt/app/dist
COPY server.js /opt/app

WORKDIR /opt/app

RUN npm install

ENV NODE_ENV=production
ENV PORT=8080

ENTRYPOINT ["npm", "run", "serve"]</code></code></pre><h3>Create a Docker Hub Image Registry</h3><p>If you do not already have a Docker hub account, create one. Then, create a new registry named&nbsp;<code>patient-portal</code>.</p><p>Navigate to&nbsp;<a href="https://hub.docker.com/settings/security">https://hub.docker.com/settings/security</a>&nbsp;and create a new&nbsp;<em>Access Token</em>.&nbsp;<strong>Ensure you copy it somewhere for later use.</strong></p><h3>Configuring the App&#8217;s GitHub Actions</h3><p>Now, it is time to configure continuous integration for the application. It should follow a flow similar to the one outlined for our ArgoCD IaC repository at the beginning of this article:</p><ol><li><p>When changes are merged to the&nbsp;<code>main</code>&nbsp;or&nbsp;<code>master</code>&nbsp;branch, a new Docker image should be published with the&nbsp;<code>latest</code>&nbsp;tag</p></li><li><p>When a prerelease is created, a new Docker image should be published that has the&nbsp;<code>v#.#.#-RC</code>&nbsp;suffix</p></li><li><p>When a release is created, a new Docker image should be published that has the&nbsp;<code>v#.#.#</code>&nbsp;suffix</p></li></ol><p>In the root directory of your project, create a new directory and subdirectory with this structure:&nbsp;<code>.github/workflows/</code></p><p>Create a file there named&nbsp;<code>github-actions.yml</code>&nbsp;with these contents:</p><pre><code><code>name: Node.js CI

on:
  workflow_dispatch:
  release:
    types: [ published, prereleased, released ]
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]

env:
  TARGET_TEST: ${{ github.event.release != null &amp;&amp; contains(fromJson('["created", "prereleased", "released"]'), github.event.action) &amp;&amp; github.event.release.prerelease }}
  TARGET_PROD: ${{ github.event.release != null &amp;&amp; contains(fromJson('["created", "prereleased", "released"]'), github.event.action) &amp;&amp; !github.event.release.prerelease }}

jobs:

  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: |
          if [[ $TARGET_PROD = true ]]; then 
            ./build.sh prod; 
          elif [[ $TARGET_TEST = true ]]; then 
            ./build.sh test; 
          else 
            ./build.sh dev; 
          fi
      - uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist

  publish:
    needs: build
    if: ${{ github.ref_name == github.event.repository.default_branch || contains(fromJson('["created", "prereleased", "released"]'), github.event.action) }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/download-artifact@v3
        with:
          name: dist
          path: dist
      - run: npm ci
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            docker.io/_______/patient-portal
          tags: |
            type=sha,enable=${{ github.ref_name == github.event.repository.default_branch &amp;&amp; github.event.release == null }}
            type=semver,pattern={{raw}},suffix=-RC,enable=${{ github.event.release != null &amp;&amp; github.event.release.prerelease == true }}
            type=semver,pattern={{raw}},enable=${{ github.event.release != null &amp;&amp; github.event.release.prerelease == false }}
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}</code></code></pre><p>Notice that this file relies on two&nbsp;<em>secrets</em>&nbsp;that provide your Docker hub username and access token.&nbsp;</p><p><strong>Ensure you modify this value</strong>,&nbsp;<code>docker.io/_______/patient-portal</code>,&nbsp;<strong>in the YAML file to replace the blank with your Docker hub username.</strong></p><p>Navigate to your&nbsp;<code>patient-portal</code>&nbsp;GitHub repository in your browser. Go to the&nbsp;<strong>Settings</strong>&nbsp;tab. Expand&nbsp;<strong>Secrets and variables</strong>&nbsp;in the left navigation bar. Choose&nbsp;<strong>Actions</strong>.</p><p>Create two new&nbsp;<em>Repository secrets</em>:</p><ul><li><p><code>DOCKERHUB_TOKEN</code>&nbsp;= the access token you created in the previous section</p></li><li><p><code>DOCKERHUB_USERNAME</code>&nbsp;= your Docker hub username</p></li></ul><h3>Test Your App&#8217;s CI</h3><h4>Build a Dev Image (with tag&nbsp;<code>latest</code>)</h4><p>Finally, we get to test the continuous integration of the app. To do this, you only need to commit and push everything to GitHub:</p><pre><code><code>git add .
git commit -am "Adding GitHub Actions CI"
git push</code></code></pre><p>Navigate to the&nbsp;<strong>Actions</strong>&nbsp;tab of your GitHub repository, and you should see a build running. When it is complete, you can navigate to your Docker Hub and verify a new image was added with the&nbsp;<code>latest</code>&nbsp;tag and a tag similar to&nbsp;<code>sha-7494edf</code>.</p><h4>Build a Test Image (with tag&nbsp;<code>v1.0.0-RC</code>)</h4><p>Navigate to your repository&#8217;s&nbsp;<em>Releases</em>&nbsp;by clicking on&nbsp;<strong>Create a new release</strong>&nbsp;in the right-hand column:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ILNg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ILNg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 424w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 848w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 1272w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ILNg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png" width="612" height="482" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:482,&quot;width&quot;:612,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:97895,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ILNg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 424w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 848w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.png 1272w, https://substackcdn.com/image/fetch/$s_!ILNg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ac9a66a-18c8-4c49-9aed-5dbc0a309593_612x482.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">Figure 3. GitHub Releases</figcaption></figure></div><p>Click&nbsp;<strong>Choose a tag</strong>, enter&nbsp;<code>v1.0.0</code>, and click&nbsp;<strong>Create a new tag</strong>.</p><p>Enter &#8220;v1.0.0&#8221; for the&nbsp;<strong>Release title</strong>.</p><p>Select the&nbsp;<strong>Set as a pre-release</strong>&nbsp;checkbox.</p><p>Click&nbsp;<strong>Publish release</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HFP_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HFP_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 424w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 848w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 1272w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HFP_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png" width="778" height="856" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:856,&quot;width&quot;:778,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84300,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HFP_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 424w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 848w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.png 1272w, https://substackcdn.com/image/fetch/$s_!HFP_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd830baa6-204c-47f3-bd12-ce4e2c89359d_778x856.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">Figure 4. GitHub Publish Release</figcaption></figure></div><p>When the pre-release publishes a Docker image, it will suffix the Git tag with&nbsp;<code>-RC</code>&nbsp;and use the result as the image tag. This is achieved by line 73 of the GitHub Actions YAML file.</p><h4>Build a Prod Image (with tag&nbsp;<code>v1.0.0</code>)</h4><p>Once we have a pre-release, publishing a release is as simple as&nbsp;<em>unchecking</em>&nbsp;the&nbsp;<strong>Set as a pre-release</strong>&nbsp;checkbox.</p><p>While still on your repository&#8217;s&nbsp;<strong>Releases</strong>&nbsp;screen, click the edit button (a pencil) for pre-release v1.0.0.&nbsp;</p><p>Uncheck&nbsp;<strong>Set as a pre-release</strong>.&nbsp;</p><p>Then, click&nbsp;<strong>Update release</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FNPl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FNPl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 424w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 848w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 1272w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FNPl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png" width="775" height="860" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:860,&quot;width&quot;:775,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76957,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FNPl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 424w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 848w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.png 1272w, https://substackcdn.com/image/fetch/$s_!FNPl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019d9ebf-882c-42fe-b833-e00ed9572ff4_775x860.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">Figure 5. GitHub Update Release</figcaption></figure></div><p>When the release publishes a Docker image, the Git tag will be used as the image tag. This is achieved by line 74 of the GitHub Actions YAML file.</p><h3>What did we just do?</h3><p>We scaffolded a new VueJS/Vite project and modified the project to show the current Git tag/hash and the hostname serving the app. This allows us to confirm that the correct Docker image is being used in each of our dev, test, and prod environments. It also allows us to test the load balancer to ensure it distributes traffic across the replica set.</p><p>We also packaged the app into a Docker container by creating a&nbsp;<code>Dockerfile</code>, configured continuous integration via GitHub Actions, and pushed the app to GitHub. Then, we confirmed the app was built correctly and that the&nbsp;<code>latest</code>&nbsp;tag of the Docker image was published to Docker hub.</p><p>Finally, we confirmed that a GitHub pre-release publishes a Docker image with the tag format of&nbsp;<code>v#.#.#-RC</code>&nbsp;and we confirmed that a GitHub release publishes a Docker image with the tag format&nbsp;<code>v#.#.#</code>.</p><h2>Deploying to Kubernetes</h2><p>It is&nbsp;<em>finally</em>&nbsp;time to see GitOps in action! We now have a Kubernetes cluster, ArgoCD, and Image Updater installed, as well as an app to deploy. Now, we need to write the IaC code that describes the app&#8217;s deployment, deploy that code to ArgoCD, and watch the magic happen!</p><h3>Writing the IaC</h3><p>Recall that we already have a GitHub repository named&nbsp;<code>patient-portal-argocd</code>, to store our IaC. Let&#8217;s clone that project to our local machine where we will add IaC to it. Navigate to the directory where you keep your GitHub projects and execute the code below.&nbsp;<strong>Ensure you replace the blank with your GitHub username.</strong></p><pre><code><code>git clone git@github.com:______/patient-portal-argocd.git </code></code></pre><h4>Base Code</h4><p>We are using&nbsp;<code>kustomize</code>&nbsp;to simplify our Kubernetes code. It allows patches to be overlaid on base code. The three files below are base code, and we will overlay a few changes for each of our dev, test, and prod environments.</p><p>Inside the&nbsp;<code>patient-portal-argocd</code>&nbsp;project, create a directory named&nbsp;<code>base</code>. Add the following three files to the base.</p><p><strong>deployment.yaml (Notice the comment in the file!)</strong></p><pre><code><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: patientportal
spec:
  replicas: 3
  selector:
    matchLabels:
      app_name: patientportal
  template:
    metadata:
      labels:
        app_name: patientportal
    spec:
      containers:
        - name: patientportal
          # Replace the blank with your Docker Hub username
          image: docker.io/____/patient-portal:latest
          imagePullPolicy: IfNotPresent</code></code></pre><p><strong>service.yaml</strong></p><pre><code><code>apiVersion: v1
kind: Service
metadata:
  name: patientportal
spec:
  type: LoadBalancer
  selector:
    app_name: patientportal</code></code></pre><p><strong>kustomization.yaml</strong></p><pre><code><code>apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml</code></code></pre><h4>Dev Overlays</h4><p>In the&nbsp;<code>patient-portal-argocd</code>&nbsp;project, create a new directory named&nbsp;<code>overlays</code>. Inside that directory, create another directory called&nbsp;<code>dev</code>.</p><p>Create the two following files in&nbsp;<code>patient-portal-argocd/overlays/dev</code>.</p><p><strong>service.yaml</strong></p><pre><code><code>apiVersion: v1
kind: Service
metadata:
  name: patientportal
spec:
  ports:
  # Serve the dev environment at http://localhost:8787
  - port: 8787
    targetPort: 8080</code></code></pre><p><strong>kustomization.yaml</strong></p><pre><code><code>apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: dev
resources:
  - ../../base
patches:
  - path: service.yaml</code></code></pre><h4>Test Overlays</h4><p>In&nbsp;<code>patient-portal-argocd/overlays</code>, create a new directory named&nbsp;<code>test</code>. Inside that directory, create the three following files.</p><p><strong>service.yaml</strong></p><pre><code><code>apiVersion: v1
kind: Service
metadata:
  name: patientportal
spec:
  ports:
  # Serve the dev environment at http://localhost:8888
  - port: 8888
    targetPort: 8080</code></code></pre><p><strong>deployment.yaml (Notice the comment in the file!)</strong></p><p>Later, we will configure ArgoCD Image Updater to modify the image version (below) with the latest test image. Remember, test images will have tags like&nbsp;<code>v#.#.#-RC</code>.</p><pre><code><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: patientportal
spec:
  template:
    spec:
      containers:
        - name: patientportal
          # Replace the blank with your Docker Hub username
          image: docker.io/____/patientportal:v1.0.0-RC
          imagePullPolicy: IfNotPresent</code></code></pre><p><strong>kustomization.yaml</strong></p><pre><code><code>apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: test
resources:
  - ../../base
patches:
  - path: service.yaml
  - path: deployment.yaml</code></code></pre><h4>Prod Overlays</h4><p>In&nbsp;<code>patient-portal-argocd/overlays</code>, create a new directory named&nbsp;<code>prod</code>. Inside that directory, create the three following files.</p><p><strong>service.yaml</strong></p><pre><code><code>apiVersion: v1
kind: Service
metadata:
  name: patientportal
spec:
  ports:
  # Serve the dev environment at http://localhost:8989
  - port: 8989
    targetPort: 8080</code></code></pre><p><strong>deployment.yaml (Notice the comment in the file!)</strong></p><p>Later, we will configure ArgoCD Image Updater to modify the image version (below) with the latest prod image. Remember, prod images will have tags like&nbsp;<code>v#.#.#</code>.&nbsp;</p><pre><code><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: patientportal
spec:
  template:
    spec:
      containers:
        - name: patientportal
          # Replace the blank with your Docker Hub username
          image: docker.io/____/patientportal:v1.0.0
          imagePullPolicy: IfNotPresent</code></code></pre><p><strong>kustomization.yaml</strong></p><pre><code><code>apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: prod
resources:
  - ../../base
patches:
  - path: service.yaml
  - path: deployment.yaml</code></code></pre><h4>Commit and Push Your IaC</h4><p>Before we can fire up our app on ArgoCD, we need to make the IaC available to it by pushing everything to our&nbsp;<code>partient-portal-argocd</code>repository. In your terminal, navigate to your repository&#8217;s directory and execute:</p><pre><code><code>git add .
git commit -am "Added IaC for dev, test, and prod environments"
git push</code></code></pre><h3>Create Dev, Test, and Prod Applications on ArgoCD</h3><p>This step will make our application live in three environments. You can use the ArgoCD UI to accomplish this, but executing the scripts below is less error-prone.</p><p><strong>Ensure you replace the blanks in each code block with your Docker Hub and GitHub usernames, respectively.</strong></p><h4>Deploy to DEV</h4><p>Deploy the app to the&nbsp;<strong>dev</strong>&nbsp;environment by executing this script in your terminal (<strong>after inserting your Docker Hub and GitHub usernames</strong>):</p><pre><code><code>kubectl apply -f - &lt;&lt;EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: patient-portal-dev
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/image-list: devpatientportalalias=docker.io/____/patient-portal
    argocd-image-updater.argoproj.io/devpatientportalalias.update-strategy: latest
    argocd-image-updater.argoproj.io/devpatientportalalias.allow-tags: regexp:^sha-.*$
spec:
  destination:
    name: ''
    namespace: dev
    server: 'https://kubernetes.default.svc'
  source:
    path: overlays/dev
    repoURL: 'git@github.com:____/patient-portal-argocd.git'
    targetRevision: HEAD
  sources: []
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF</code></code></pre><h4>Deploy to TEST</h4><p>Deploy the app to the&nbsp;<strong>test</strong>&nbsp;environment by executing this script in your terminal (<strong>after inserting your Docker Hub and GitHub usernames</strong>):</p><pre><code><code>kubectl apply -f - &lt;&lt;EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: patient-portal-test
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/image-list: testpatientportalalias=docker.io/____/patient-portal
    argocd-image-updater.argoproj.io/testpatientportalalias.update-strategy: latest
    argocd-image-updater.argoproj.io/testpatientportalalias.allow-tags: regexp:^v\d.\d.\d-RC$
spec:
  destination:
    name: ''
    namespace: test
    server: 'https://kubernetes.default.svc'
  source:
    path: overlays/test
    repoURL: 'git@github.com:____/patient-portal-argocd.git'
    targetRevision: HEAD
  sources: []
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF</code></code></pre><h4>Deploy to PROD</h4><p>Deploy the app to the&nbsp;<strong>prod</strong>&nbsp;environment by executing this script in your terminal (<strong>after inserting your Docker Hub and GitHub usernames</strong>):</p><pre><code><code>kubectl apply -f - &lt;&lt;EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: patient-portal-prod
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/image-list: testpatientportalalias=docker.io/____/patient-portal
    argocd-image-updater.argoproj.io/testpatientportalalias.update-strategy: latest
    argocd-image-updater.argoproj.io/testpatientportalalias.allow-tags: regexp:^v\d.\d.\d$
spec:
  destination:
    name: ''
    namespace: prod
    server: 'https://kubernetes.default.svc'
  source:
    path: overlays/prod
    repoURL: 'git@github.com:____/patient-portal-argocd.git'
    targetRevision: HEAD
  sources: []
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF</code></code></pre><h3>What did we just do?</h3><p>This &#8220;Deploying to Kubernetes&#8221; section has a <em>lot of code</em>. However, most of it is very repetitive. In a nutshell, here is what we accomplished:</p><ul><li><p>Described our Kubernetes application deployment using Kustomizations &#8212; these included base code and a custom overlay for each environment (dev, test, and prod)</p></li><li><p>Created ArgoCD applications based on our Kustomizations by applying ArgoCD Application YAML files to our Kubernetes cluster &#8212; we applied one for each environment (dev, test, and prod)</p></li></ul><h2>Testing</h2><p>At this point, if we merge something to the&nbsp;<code>master</code>&nbsp;branch, a new&nbsp;<code>sha-#######&nbsp;</code>image should be pushed to Docker Hub, and ArgoCD Image Updater should update our&nbsp;<code>dev</code>&nbsp;environment.</p><p>If we create a new pre-release in GitHub, a new&nbsp;<code>v#.#.#-RC</code>&nbsp;image should be pushed to Docker Hub, and the&nbsp;<code>test</code>&nbsp;environment should be updated.</p><p>Likewise, a new release in GitHub should trigger the push of a new&nbsp;<code>v#.#.#</code>&nbsp;image to Docker Hub and the updating of the&nbsp;<code>prod </code>environment.</p><p>Finally, if we change any of our Kustomization IaC in the&nbsp;<code>patient-portal-argocd</code>&nbsp;and merge those changes to master, ArgoCD should apply the changes to Kubernetes. For example, assume we set scale our&nbsp;<code>prod</code>&nbsp;environment up to 6 pods rather than 3.</p><p>Having described how things&nbsp;<em>should</em>&nbsp;work, let&#8217;s verify.</p><h3>Verify DEV Deployments</h3><p>Let&#8217;s make a simple change to our app so the home page reads &#8220;Patient Portal&#8221; instead of &#8220;Vite + Vue.&#8221;</p><p>In your IDE or text editor, open the&nbsp;<code>patient-portal/src/App.vue</code>&nbsp;file and change the&nbsp;<code>msg</code>&nbsp;attribute on line 16. Your file should look like this when you&#8217;re done:</p><pre><code><code>&lt;script setup&gt;
// This starter template is using Vue 3 &lt;script setup&gt; SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;a href="https://vitejs.dev" target="_blank"&gt;
      &lt;img src="/vite.svg" class="logo" alt="Vite logo" /&gt;
    &lt;/a&gt;
    &lt;a href="https://vuejs.org/" target="_blank"&gt;
      &lt;img src="./assets/vue.svg" class="logo vue" alt="Vue logo" /&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;HelloWorld msg="Patient Portal" /&gt;
&lt;/template&gt;

&lt;style scoped&gt;
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
&lt;/style&gt;</code></code></pre><p>Commit and push your changes to GitHub:</p><pre><code><code>git commit -am "Fix message on home screen"
git push</code></code></pre><p>Monitor GitHub actions. There is about a 2-minute interval from the time the finish to GitHub Image Updater&#8217;s detection of the changes.</p><p>While you wait for things to be completed, open the ArgoCD UI, navigate to the&nbsp;<code>patient-portal-dev</code>&nbsp;app, and watch it. When Image Updater deploys the new version of the&nbsp;<code>dev</code>&nbsp;app, you will see the graph begin moving and changing.</p><p>Finally, if all went well, you should see your changes in the app when you navigate to it at&nbsp;<code>http://localhost:8787</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_!Zc6v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Zc6v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 424w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 848w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 1272w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Zc6v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png" width="753" height="503" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:503,&quot;width&quot;:753,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35034,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Zc6v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 424w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 848w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.png 1272w, https://substackcdn.com/image/fetch/$s_!Zc6v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e18605c-978a-4a1a-a3c1-e24fd5f69795_753x503.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">Figure 6. Verifying the DEV Deployment</figcaption></figure></div><h3>Verify TEST Deployments</h3><p>Next, let us confirm that&nbsp;<em>GitHub pre-releases trigger a test environment deploy</em>ment. Navigate to your GitHub repository, click &nbsp;Releases&nbsp;in the right-hand navigation bar, and click the&nbsp;<strong>Draft a new release</strong>&nbsp;button.</p><p>Click&nbsp;<strong>Choose a tag</strong>, type&nbsp;<code>v1.0.1</code>, and click&nbsp;<strong>Create a new tag: v1.0.1</strong>. Type&nbsp;<code>v1.0.1</code>&nbsp;in the&nbsp;<strong>Release title</strong>&nbsp;textbox. Select&nbsp;<strong>Set as pre-release</strong>. Click&nbsp;<strong>Publish release</strong>.</p><p>Now, monitor the GitHub actions that kicked off due to the new tag and new pre-release. Once those are complete, you should again monitor your <code>patient-portal-test </code>application in ArgoCD. Once the &#8220;v1.0.1-RC&#8221; image is noticed by the Image Updater, the ArgoCD diagram will move and change as the new image is rolled out.</p><p>Finally, you should see your updated app deployed to&nbsp;<code>http://localhost:8888</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_!krW7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!krW7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 424w, https://substackcdn.com/image/fetch/$s_!krW7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 848w, https://substackcdn.com/image/fetch/$s_!krW7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 1272w, https://substackcdn.com/image/fetch/$s_!krW7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!krW7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png" width="583" height="466" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:466,&quot;width&quot;:583,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31542,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!krW7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 424w, https://substackcdn.com/image/fetch/$s_!krW7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 848w, https://substackcdn.com/image/fetch/$s_!krW7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.png 1272w, https://substackcdn.com/image/fetch/$s_!krW7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6350d635-9d45-4baa-a0fd-8c958e65f652_583x466.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">Figure 7. Verifying the TEST Deployment</figcaption></figure></div><h3>Verify PROD Deployments</h3><p>Verifying a deployment to the&nbsp;<code>patient-portal-prod</code>&nbsp;app is the easiest test of all. Navigate to your GitHub repository, again select&nbsp;<strong>Releases</strong>&nbsp;in the right-hand navigation column, and click the edit (pencil) icon for&nbsp;<code>v1.0.1</code>.</p><p>Unselect (remove the check)&nbsp;<strong>Set as pre-release</strong>. You can optionally select&nbsp;<strong>Set as the latest release</strong>, but it is not required. Click&nbsp;<strong>Update release</strong>.</p><p>Monitor the ensuing GitHub Action execution, which will deploy an image tagged as&nbsp;<code>v1.0.1</code>&nbsp;to your Docker Hub registry. Once the action completes, go watch your ArgoCD&nbsp;<code>patient-portal-prod</code>&nbsp;app; you will notice the graph move and change once Image Updater rolls out the new image version.</p><p>Finally, you should see the updated app deployed to <code>http://localhost:8989</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_!EKPy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EKPy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 424w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 848w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 1272w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EKPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png" width="699" height="503" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:503,&quot;width&quot;:699,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:33548,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EKPy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 424w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 848w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.png 1272w, https://substackcdn.com/image/fetch/$s_!EKPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dadbfa3-7388-42d3-a5e9-d5ca2dbec21f_699x503.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">Figure 8. Verifying the PROD Deployment</figcaption></figure></div><h3>Verify IaC Changes are Automatically Applied</h3><p>The final test is to confirm that any changes made to the IaC stored in the&nbsp;<code>patient-portal-argocd</code>&nbsp;repository are automatically applied.</p><p>I suggest scaling up the&nbsp;<code>prod</code>&nbsp;environment to 6 replicas (instead of 3). To do this, open the file&nbsp;<code>overlays/prod/deployment.yaml</code>&nbsp;and add&nbsp;<code>replicas: 6</code>&nbsp;below line 5:</p><pre><code><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: patientportal
spec:
  replicas: 6
  template:
    spec:
      containers:
        - name: patientportal
          # Replace the blank with your Docker Hub username
          image: docker.io/____/patient-portal:v1.0.0
          imagePullPolicy: IfNotPresent</code></code></pre><p>Save the changes. Then commit and push your updates:</p><pre><code><code>git add .
git commit -am "Scaling up prod"
git push</code></code></pre><p>Open the&nbsp;<code>patient-portal-argocd</code>&nbsp;app in ArgoCD and watch it. In a few minutes, you should notice it adding pods to the deployment until it reaches a total of 6. To hurry things along, you can manually &#8220;Sync&#8221; and &#8220;Refresh&#8221; your application in the ArgoCD UI.</p><p>In the end, your deployment should look similar to this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qsyA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qsyA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 424w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 848w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 1272w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qsyA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png" width="1456" height="737" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:737,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:237247,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qsyA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 424w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 848w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.png 1272w, https://substackcdn.com/image/fetch/$s_!qsyA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ad76e1-b6ef-4e50-b355-f0d33baaff81_1756x889.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">Figure 9. Scaled up PROD Deployment</figcaption></figure></div><h3>Verify Load Balancing Works</h3><p>The final test is very simple; we want to perform a fast series of &#8220;hard refreshes&#8221; in our browser and confirm the hostname changes.</p><p>Navigate to the app in the&nbsp;<code>prod</code>&nbsp;environment. Once the page loads, perform a fast series of &#8220;hard refreshes.&#8221; In Chrome-based browsers, you do this with&nbsp;<code>CTRL+SHIFT+R</code>. The test passes if you notice the hostname changing periodically.</p><h2>Conclusion</h2><p>GitOps is the standard for enterprise operations today. It brings myriad benefits to an organization, including a single source of truth for deployments, versioned control over infrastructure, automation, ease of rolling back changes, consistency, repeatability, and visibility.&nbsp;</p><p>This article has walked you through installing, configuring, and using Kubernetes and ArgoCD to create a GitOps environment. While it used a local Docker installation, it is trivial to create a similar system in a cloud environment like AWS or Azure.</p><h2>Resources</h2><p>Source code referenced throughout this article can be found at:</p><ul><li><p><a href="https://github.com/cbush06/patient-portal-argocd">https://github.com/cbush06/patient-portal-argocd</a></p></li><li><p><a href="https://github.com/cbush06/patient-portal">https://github.com/cbush06/patient-portal</a></p></li></ul><div><hr></div><ul><li><p>Richardson, A. (2018, August 21). What Is GitOps. Weaveworks Blog. <a href="https://www.weave.works/blog/what-is-gitops-really">https://www.weave.works/blog/what-is-gitops-really</a></p></li><li><p>Weaveworks. (2021, July 13). The History of GitOps. Weaveworks Blog. <a href="https://www.weave.works/blog/the-history-of-gitops">https://www.weave.works/blog/the-history-of-gitops</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Drawing to Discovery]]></title><description><![CDATA[Domain-driven Design and Ubiquitous Language]]></description><link>https://clintonbush.substack.com/p/drawing-to-discovery</link><guid isPermaLink="false">https://clintonbush.substack.com/p/drawing-to-discovery</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Thu, 28 Mar 2024 02:21:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Wi1v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg" 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_!Wi1v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wi1v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wi1v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg" width="834" height="557" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:557,&quot;width&quot;:834,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;UML Diagram : Definition, Types, Tools &amp; Selection Guide | TEC&quot;,&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="UML Diagram : Definition, Types, Tools &amp; Selection Guide | TEC" title="UML Diagram : Definition, Types, Tools &amp; Selection Guide | TEC" srcset="https://substackcdn.com/image/fetch/$s_!Wi1v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Wi1v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e545dfc-3420-4792-a33f-db221651fe00_834x557.jpeg 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><p>Even the most junior of professional software developers will have been subjected to the hair-pulling, teeth-grinding, fist-clenching experience of eliciting requirements from the client.</p><p>You know the drill. Your client needs a new feature developed to support some business process, say budgeting new machinery for a construction company. Seems simple enough until they explain all the intricacies and business rules involved. Then they need you to interface with other systems. They also want portions of the navigation to match an old system to cut the learning curve. Back and forth you go, you ask questions and get answers that lead to more questions.</p><p>You continue blindly stumbling your way through this forest for several more days, taking reams of notes in lieu of a breadcrumb trail and trying to glean enough information to start a rudimentary backlog of user stories. The whole time, you're confusing all the interwoven business processes, the client is switching from describing one issue to another without warning, and you can barely keep up with the deluge of information, much less begin codifying requirements into comprehensible sentences to take back to your team. To make matters worse, your client is growing frustrated with your questions and giving you looks that say, "Are you even listening to me?" To be fair, you feel like you're just rephrasing the same questions over and over because every answer you get only adds to your confusion.</p><p>Ultimately, your requirements session leaves you with more questions, more confusion, more frustration, and no starting point for development.&nbsp;</p><h2>So, What Went Wrong?</h2><p>Requirements development is more than a shallow conversation where the client enumerates their needs; requirements development is a major step in&nbsp;<em>systems analysis and design.</em>&nbsp;Effective requirements development needs four things:</p><ol><li><p>People discussing complex ideas must constantly&nbsp;<strong>test their understanding</strong>&nbsp;of the ideas</p></li><li><p>When studying any nontrivial problem,&nbsp;<strong>a visual representation</strong>&nbsp;helps you clarify and better understand the problem</p></li><li><p>When designing systems, people need&nbsp;<strong>a way to test and experiment on their designs</strong></p></li><li><p>Everyone must&nbsp;<strong>speak the same language</strong></p></li></ol><h3>A Failure to Communicate</h3><p>Quoting the 1967 film&nbsp;<em>Cool Hand Luke</em>, "What we have here is a failure to communicate." Specifically, a failure to communicate effectively. When people are discussing complex and interrelated ideas (i.e., systems), the person on the receiving end must be able to test their understanding as the conversation progresses. It follows that you need to be able to test your understanding of the needs your client describes. Similarly, your client must be able to test their understanding of your proposed solutions. Without testing, neither you nor your client can be sure that you fully and correctly understand what the other is communicating.</p><h3>Picturing the Problem</h3><p>It is extremely difficult, if not impossible, to understand a reasonably complex problem without visual aid. Kozma (2003) neatly frames the dilemma:</p><blockquote><p>In science, graphics are also used to display data, organize complex information, and promote a shared understanding of scientific phenomena. These graphics are often used to present multiple relationships and processes that are difficult to describe. (Cook, 2006, p. 2)</p></blockquote><p>In short, you must bring visual representations of the client's needs to the table if you are to understand those needs and fully grasp the problem. Not only will a visual help you conceptualize the requirements and see the complex relationships between various parts, it will also serve as a common reference for you and your client. It is an aide you can reference to explain your questions and an aide the client can reference as they give an answer.</p><h3>Testing Your Ideas</h3><p>After collaborating around a model of your client's needs to develop a set of requirements and testing your understanding of those requirements, you need to design a solution. All but the most trivial software systems are far too complex for a person to accurately and fully represent in their memory. Just as you need a model to fully understand a client's needs, you need a model to help shape your solution. As you begin designing your solution, you naturally start with the most obvious parts of the problem. It's the less obvious concerns (e.g., edge cases, failure scenarios, relationships, and interfaces) that escape your attention. To identify these, you need a way to test your solution for gaps. You need a model.</p><p>Bobek and Tversky (2016) explain the benefits of using a visual model:</p><blockquote><p>There are several notable differences between visual and verbal explanations; visual explanations map thought more directly than words and provide checks for completeness and coherence as well as a platform for inference, notably from structure to process. (p. 1)</p></blockquote><h3>Ubiquitous Language</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0QbF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0QbF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 424w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 848w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 1272w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0QbF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png" width="1456" height="888" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:888,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:289518,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0QbF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 424w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 848w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.png 1272w, https://substackcdn.com/image/fetch/$s_!0QbF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d87d462-f3b0-4bed-9c55-98dad10cf22d_3455x2108.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><em>Domain-driven design</em> is a highly effective methodology for designing and building quality software that is not only consistent with the client's domain but also correctly solves that problem. One of its fundamental practices is using a&nbsp;<em>Ubiquitous Language</em>&nbsp;throughout the entire software development lifecycle. This means the developers, clients, and other stakeholders all use a common vocabulary. This lexicon should be applied to code, documents, e-mails, meetings, and other activities.</p><p>Using a visual model in both requirements development and solution design both encourages and smoothes the process of building that Ubiquitous Language. Elements of visualizations naturally constitute parts of the Domain Model and give all stakeholders a single frame of reference. Using those visualizations, naming things becomes a team activity.</p><p>Finally, visual models make the requirements development and design processes inclusive even before the Ubiquitous Language has been developed. Without a visualization, the domain experts and technical experts do a great deal of talking past each other. The domain experts use their industry lexicon that developers don't understand and vice versa. Visual models allow everyone to participate despite these differences in experience and knowledge.</p><h2>Tools of the Trade</h2><p>Requirements development tools can be roughly divided into two categories:</p><ol><li><p>Those that document current business processes and/or the status quo. Examples include:</p><ol><li><p>Business Process Models</p></li><li><p>UML (Unified Modeling Language) Activity and Statechart Diagrams</p></li><li><p>Event storming</p></li><li><p>Data Flow Diagrams</p></li></ol></li><li><p>Those who design and model the proposed solution. Examples include:</p><ol><li><p>UML Activity, Statechart, Sequence, Collaboration, Use Case, and Class Diagrams</p></li><li><p>Wireframes</p></li><li><p>Mock-ups</p></li><li><p>Event storming</p></li><li><p>Data Flow Diagrams</p></li></ol></li></ol><h3>Documenting Business Processes</h3><p>If you are building a system that automates a complex business process, your first step in requirements development should be documenting that process. As you can see above, there are several tools you can use to perform this process. Earlier in the article, I covered some criteria you may want to consider when choosing:</p><ul><li><p>Inclusive: Can your clients easily join in the process?</p></li><li><p>Intuitive: Is it simple and quick to master? Does it express thoughts well?</p></li><li><p>Collaborative: Can everyone join in?</p></li></ul><p>I believe the UML Activity diagram best captures business processes to be implemented in a software system. However, I believe these criteria rule out UML in one way or another. Most obviously, UML comes with a learning curve that the non-developer will have to tackle and, therefore, will be excluded from the exercise.</p><p>Fortunately, there is a nearly equivalent alternative that anyone can rapidly learn: Event Storming. Event Storming can be used to document, design, and explore business processes. It also lends itself nicely to capturing the various interactions between components within a system and between that system and external systems.</p><p>Event Storming is extremely simple. It lacks confusing symbols, has minimal rules, and requires only a few pads of different-colored sticky notes. The facilitator of an Event Storming session can explain the activity in very little time, one to two minutes at most.&nbsp;</p><p>Finally, even though Event Storming was created for large walls in conference rooms, there are now many online tools that allow teams to collaborate in real time on a diagram.</p><p>For more details on Event Storming and plenty of material from its creator, Alberto Brandolini, visit&nbsp;<a href="https://www.eventstorming.com/">eventstorming.com</a>.</p><h3>Documenting the Interface and Flow</h3><p>User-centric applications will have an interface that is tightly coupled to its data flow. This makes sense because the user is either the primary source of the data, the primary destination of the data, or the primary trigger of manipulations of that data.</p><p>Developing requirements and designs for such an application will have two goals:</p><ol><li><p>Document the flow of data and business processes</p></li><li><p>Design an interface that injects the user into that flow and those processes</p></li></ol><p>Depending on the size and complexity of the data flow and business processes, it may be worth taking the time to capture them explicitly. If this is the case, I suggest Event Storming for all the reasons covered in the section above.</p><p>On the other hand, I have worked on systems (e.g., a simple class scheduling system for training events, a data repository for equipment manuals, etc.) where the business flow and business processes are relatively simple. In these cases, the processes are so tightly coupled with the interface that you can capture all the relevant details while designing the user interface.&nbsp;</p><p>The dogmatic engineer might disparage this shortcut, but hear me out. Most of my clients are not engineers or even technologists. In the example above of the class scheduling system, they are Soldiers in jobs that have nothing to do with systems engineering or software development. Working through more traditional requirements development events with them would garner little interest and involvement; it would likely lead to frustration and resentment.</p><p>Instead, I used Adobe XD to prototype mock-ups for all the obvious flows through the system. I then held a one-hour session where I walked a small group of stakeholders through each flow while asking probing questions and soliciting feedback. The visuals of the prototype captured their imagination and attention far better than other forms of requirements gathering. The group could identify various edge cases, scenarios, and flows I had missed. They pointed out pain points where the interface could better support their jobs and imagined ways elements they saw in the wireframes could be extended to add even more value.&nbsp;</p><p>In short, if you are starting a project with a user interface tightly coupled to the data flow and processes it enables, consider using visual tools like wireframes and prototypes to capture the users&#8217; requirements and start designing solutions. With a visual model, you can test your understanding and design for "completeness and coherence."</p><h2>Agile Requirements</h2><p>In closing, one of the tenets of Agile methodologies is performing the Software Development Lifecycle phases in parallel. I propose you extend this philosophy to requirements development and solution design by doing both tasks simultaneously. Using an intuitive visual model as the medium for this task allows you to test both your understanding of the client's needs and your plans for a solution. Furthermore, it makes the process accessible to stakeholders who are unfamiliar with more technical tools like UML diagrams.</p>]]></content:encoded></item><item><title><![CDATA[Cowards Cannot Lead Software Teams]]></title><description><![CDATA[Why Courageous Trust is a Must for Leaders of Software Development]]></description><link>https://clintonbush.substack.com/p/cowards-cannot-lead-software-teams</link><guid isPermaLink="false">https://clintonbush.substack.com/p/cowards-cannot-lead-software-teams</guid><dc:creator><![CDATA[Clinton Bush]]></dc:creator><pubDate>Thu, 28 Mar 2024 00:04:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DMnA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg" 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_!DMnA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DMnA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DMnA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg" width="970" height="646" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:646,&quot;width&quot;:970,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57198,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&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_!DMnA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DMnA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0668bbe7-36a1-4d10-8708-bc47350a1f9c_970x646.jpeg 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><p></p><h2>The Contract that Died</h2><p>I once worked on a government contract that died. It did not die for the usual reasons like its services no longer being required, a lack of quality in work, or the final product having been delivered. It died because the leaders lacked courage and refused to trust their team. They refused to trust the team because they did not understand what the team did; and, though they could have trusted us, through a leap of faith in spite of their incompetence, they lacked the courage to do so.&nbsp;</p><p>Over the course of several years, the government imposed a few constraints here, made some unwise choices there, and eventually strangled our best efforts to deliver the services they had paid for. As the lead developer on the team, I protested loudly, frequently, and vehemently for a long time. Sadly, my protests fell on unknowing and untrusting ears. They fell on ears that neither understood the implications of the government's decisions nor trusted me enough to fight back regardless of their handicapped knowledge.</p><p>At the end of the contract, my job felt more like playing &#8220;Mother May I&#8221; with Bill Lumbergh (of&nbsp;<em>Office Space</em>) than developing software. I had to ask permission for everything, was watched like a hawk, was completely ignored until something broke, and was always the last to know anything of importance. If you thought to yourself, "That sounds like a run-of-the-mill micromanager to me," you would be right. However, I believe micromanagement is only a symptom of underlying problems that come from a lack of competence, a lack of trust, or both.</p><p>Put another way, I believe there are three flavors of micromanagers:</p><ul><li><p>Competent but untrusting</p></li><li><p>Incompetent but trusting</p></li><li><p>Incompetent and untrusting</p></li></ul><p>Before I continue, know that being a micromanager does not always mean that person is "bad." Sometimes, they are "bad" people in the self-centered, ego-centric sort of way and only&nbsp;<em>care</em>&nbsp;about themselves. However, I believe these are the exceptions and, in most cases, micromanagers are good people who&nbsp;<em>care</em>&nbsp;about getting the job done right.</p><h2>The Contract's Death Rattle</h2><p>The death of that government contract was no surprise to the development team. Its raspy, half-choking death rattle began long before a merciful demise. Life support was administered through temporary, one-year extensions rather than the normal 3-5 year renewal; and, CPR came in the form of offloading portions of our members' time to other contracts as funds began drying up. No, there was absolutely no surprise when the end came, despite management's best efforts to keep the wraps on their failed resucitations. As a morbid sort of post-mortem, I want to walk you through the metastization of this bad management cancer.</p><p>When I first joined the contract, it possessed an environment in which anyone would enjoy working. There was freedom; creativity and experimentation were encouraged; and, there was just enough process to keep chaos at bay. However, the good times were short-lived. Corporate started tasking our project manager with duties outside our contract. These additional responsibilities and a few other factors led our PM to reframe their role to that of a&nbsp;<em>program</em>&nbsp;manager and abdicate their day-to-day responsibilities to several new hires who were to learn project management on-the-job.</p><p>These junior PMs came from diverse backgrounds including business process analysis, event planning, and technical writing. There was no shortage of intelligence and can-doism but there was a complete lack of experience in the field to which they had been assigned -- software development.</p><p>This incompetence manifested in the third form of micromanagement above: incompetant and untrusting. The project managers did not understand the work they were managing and, whether out of an abundance of concern for saving face or a basic lack of faith, did not trust the team to get the job done.</p><p>The day-to-day symptoms of this management style included:</p><ul><li><p>A convoluted, incomprehensible business process whose creator could not even brief correctly</p></li><li><p>Demands for increasingly verbose and daily comments on every in-progress ticket</p></li><li><p>Information hording</p></li><li><p>Exponential growth of SOPs (standard operating procedures) and documented business processes</p></li><li><p>Management through faceless and impersonal Jira tickets rather than face-to-face leadership</p></li><li><p>Inordinate focus on irrelevant details of how things were done rather than the the end product</p></li><li><p>Seeking to demonstrate personal value through:</p></li><li><p>Finding mistakes in other's work</p></li><li><p>Finding roadblocks rather than removing them</p></li><li><p>Excessive quantities of useless documentation</p></li><li><p>Increasing isolation between management and developers</p></li></ul><p>Every one of these items is a result of either</p><ul><li><p>incompetence;</p></li><li><p>distrust; or,</p></li><li><p>fear.</p></li></ul><p>If any of my story resonates with your experiences, stick with me while I dig into the reasons project managers fall victim these shortcomings and how these issues impact projects and developers.</p><p>The contract finally ended when the government financed another project that subsumed the responsibilities of the applications we maintained. Personally, I blame this outcome on: (1) a lack of our responsiveness to the customer's needs; (2) a failure of our management to actively advocate for our ability to meet the customer's needs; and, (3) a failure of both our management and their government counterparts to solicit more work for our contract.</p><p>I believe the first and second problems could have been avoided by courageous and trusting leaders.</p><h2>Leaders Without&nbsp;<em>Trust</em>&nbsp;Are Not Leaders</h2><p>Most thought-work (e.g., software development, mathematicians, physicists, accountants, lawyers, etc.) operates according to the principles of research and development rather than manufacturing or assembly lines. Thought-work jobs start with a problem to be solved rather than a blueprint to be followed. These jobs require investigation, stating of hypotheses, experimentation, testing, and implementation. All of these stages are subject to failure and refinement. They require independent thinkers who are free to operate in an environment where the concerns of business are abstracted away so the thought-worker can focus on thinking (Spolsky, 2006). Micromanagers break down these abstractions because they constantly inject themselves into the thought-workers' world demanding the worker perform tasks unrelated to solving the problem at hand, to either edify the manager or enable their continued incompetence and distrust.</p><p>In short, micromanagers destroy their thought-workers by&nbsp;<em>distrusting</em>them and, as a result,&nbsp;<em>erasing the development abstraction</em>. With the development abstraction gone, workers are led to believe that their priorities and passions are misplaced, that metrics about what they do are more important than what they produce, that mountains of unused documentation appear more productive than a functioning product, that value is measured by adherence to dogmatic processes rather than customer satisfaction, and that they are so untrusted their every activity must be reported. They are told things like "We need all of your tasks documented in SOPs," "You must record everything you do in comments on your tickets," or "You must submit a daily e-mail recording all of your activities."</p><p>In the end, the workers realize management cares more about framing business in a form they are comfortable with and less about actually delivering value. The concerns of the managers are forced into the world of their developers and the developers' progress grinds to a halt.</p><h2>Trust Without&nbsp;<em>Courage</em>&nbsp;is Useless</h2><p>For a moment, let us assume the project managers discussed above&nbsp;<em>do trust</em>&nbsp;their team but still&nbsp;<em>lack courage</em>. The result is passively impotent leaders. In this case, they are not actively obstructing the team as in the previous section but they are still failing to advocate for the team. The outcome is the same -- the loss of the&nbsp;<em>development abstration</em>.</p><p>Good leaders who trust their teams must be courageous enough to&nbsp;<em>follow through</em>. This means they do things like:</p><ul><li><p>Advocate for their team to higher management</p></li><li><p>Shield the team from politics</p></li><li><p>Fight for resources</p></li><li><p>Negotiate requirements -- both internally (e.g., product owners and marketing) and externally (e.g., customers or partner organizations)</p></li><li><p>Defend the schedule</p></li><li><p>Support the team's assessments and recommendations</p></li><li><p>Eliminate barriers and impediments</p></li></ul><p>My time as a Federal civil servant showed me exactly what it is like to work for people who trust you but lack the courage to follow through. As a developer, I received a laptop with barely enough resources to run Microsoft Outlook. It was impossible to run Eclipse and other developer tools. I requested developer machines for the team multiple times but my leader did not want to fight for the money to purchase the tools we needed.&nbsp;</p><p>During this same period of my career, I was working on building a system used to solicit contract offers for services. If you are familiar with the Department of Defense (DoD), you know that, with very few exceptions, all of their IT systems must use Smart Card (a.k.a., Common Access Card (CAC)) authentication. This is a physical token that holds the owner's private keys and Personal Identity Verification (PIV) X.509 certificates. The issue for my project was the people and companies bidding on contracts would not receive these CAC cards until winning the contract. In other words, our target users would not be able to access the system. Obviously, username/password authentication was the best alternative but our cybersecurity manager adamantly stated this was not authorized. I did my homework and tracked down all the relavent DoD regulations, orders, and publications proving we could use username/password authentication as long as we requested and received a waiver. The cybersecurity manager refused to even look at my research and, rather than fighing on my behalf, my manager chose to bend the knee.</p><p>Following these events, my motivation and pride in my product took a serious dip. If my manager did not care enough to fight for me, then why should I care? If my manager did not care enough about building a usable product, was my job even important?</p><p>As with the stories above, project managers often suffer from a lack of courage to stand up for their team. Many others suffer from the fear of looking weak to their teams. This was a condition I encountered far more often in my military career than in my civilian one.</p><p>You often find these PMs attempting to hide behind their own subordinates by encouraging them to do the courageous things (e.g., report ethics violations, make risky investments, think outside the box) that they do not do themselves. Detert (2022) makes the case, &#8220;Yet that&#8217;s what leaders who &#8216;encourage courage&#8217; are essentially doing. They&#8217;re implicitly saying that because the aren&#8217;t courageous enough to change the conditions in their organization to make it safer for people to be honest, try new things, or take other prudent risks, everyone else should be courageous enough to do them anyway.&#8221;</p><p>Other times, these PMs will do everything they can to avoid admitting they do not know something. I have witnessed PMs fabricate lies, incoherently babble until they lose their audience, and drop a ten minute string of buzzwords, all to avoid admitting&nbsp;<em>they don't know something</em>. This condition relates to the next topic, incompetence, in that it is often incompetent managers who refuse to admit their lack of knowledge for fear of exposing that incompetence. Detert (2022) simultaneously condemns the willfully incompetent and encourages the knowledgeable who are afraid of losing face: &#8220;Once people know you&#8217;re competent, it makes you look stronger (not weak) when you admit &#8216;I don&#8217;t know&#8217; or say &#8216;Please help with this.&#8217;&#8221;</p><h2><em>Incompetence</em>&nbsp;is no Excuse</h2><p>I have found two common forms of incompetence plaguing project managers:</p><ol><li><p>A lack of knowledge -- they do not understand what their team does</p></li><li><p>A lack of expertise -- they do not understand their role as project manager</p></li></ol><h3>1. Lack of Knowledge -- PMs Who Don't Know Their Team's Jobs</h3><p>It takes a tremendous amount of courage to place your faith and trust in other people. Leaders are evaluated by the performance of their team. This means that they must trust their teams with their own careers.&nbsp;</p><p>A personal lack of expertise in the field a leader's team operates in only compounds this issue. In that case, the leader must trust their team to make a project successful while they do not fully understand that the team should be doing to realize that success.</p><p>Project managers should minimally understand the level of effort and complexity of their team members' various tasks. Llopis (2013) writes, "Though leaders cannot be expected to have all of the answers -- they should not play at arms length either. The 21st century leader must be more high-touch in order to effectively evaluate the business and coach-up their employees."</p><p>I believe leaders must understand what they are asking of their team members when they assign different tasks. Likewise, they must avoid over-delegating as a result of their own shortcomings. If you are a project manager, you should have enough competence that you can make educated inferences about the project schedule and its dependencies based on the requirements and designs your team is working from. In&nbsp;<em>The Contract that Died</em>&nbsp;(above), my project managers did little more than demand the team provide arbitrary dates and then plug those dates into Microsoft Project. Some people may find this acceptable, however, I do not. A competent project manager should know enough to ask probing questions, realize high risk areas of the project, and recognize overly pessimistic or overly optimistic estimates given by the team.</p><p>So far, I have only discussed the project manager's role in building a schedule and estimating levels of effort. What about the rest of the Software Development Lifecycle (SLDC)? Can the PM build a quality schedule with no understanding of testing tools, strategies, methodologies, and types? Would the PM realize the team overlooked load testing, failed to test all of the possible user platforms/browsers, or completely forgot to perform functional testing on external system intefaces/APIs? Some readers may believe I'm expecting too much from a project manager. I believe that the PM should not understand the implementations of interfaces or the methods of load testing, but if the PM is to lead a team of developers they need more than a high-level, academic understanding of the SDLC. They should have enough experience behind them to have developed an intuition about the activities involved in successful software projects.</p><p>A PM is certainly not a developer. Furthermore, I have found, with few exceptions, developers make poor project managers. The fact remains that PMs should understand how developers operate within the SDLC, how their decisions impact developers, and what their role is inside the SDLC.</p><h3>2. Lack of Expertise -- PMs Who Don't Know Their Jobs</h3><p>Project managers who do not know their roles will do everything they can to appear valuable. This frequently manifests as them inventing jobs for themselves, injecting themselves where they don't belong, placing an inordinate emphasis on nonissues, or exaggerating problems while offering no solutions. I believe this problem occurs more frequently with project managers who were highly technial workers in the past (e.g., former developers, testers, engineers) and tried (unsuccessfully) to transition to management.</p><p>Some examples I have witnessed include project managers who:</p><ul><li><p>take on the role of quality assurance (the job of testers);</p></li><li><p>make it their life goal to act as timecard police; and,</p></li><li><p>act as project schedule enforcers who want to beat deadlines (consequences be damned).</p></li></ul><p>These project managers will either track every movement of their team members as a salve for anxiety stemming their sense of being lost or they will find other roles they are comfortable with and try to supervise those. Regardless of how their ineptitude shows itself, the end result is always a project manager who obstructs rather than facilitates their team's success.</p><p>Good project managers, those that are true leaders, work to enable their team, remove impediments, shield the team from politics, provide resources, and inspire and motivate. They work to both defend their team and advocate on their behalf. They hold their team responsible while also being its greatest cheerleader. Their is a difference between managers and leaders. Good project managers are also leaders.</p><h2>Conclusion</h2><p>Project managers do not have to be technical experts. They do not have to possess superhuman levels of courage and demonstrate praise-worthy heroics on a daily basis. They do not even have to be fully proficient at their own jobs.&nbsp;</p><p>However, project managers must be courageous enough to take calculated risks, to go to bat for the teams, to admit when they lack knowledge, and to lead from the front.&nbsp;</p><p>Project managers must also trust their teams. They must trust them eough to act based on team's input, to stay out of the way while the team works, and to vouch for the team to their external stakeholders.</p><p>It is these courageous and trusting people whom we more frequently call&nbsp;<em>leaders</em>.</p>]]></content:encoded></item></channel></rss>