1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1"> 6<link rel="profile" href="http://gmpg.org/xfn/11"> 7 8<title>Ray Sinnema – Ray on Software Architecture</title>
9<script type="text/javascript"> 10 WebFontConfig = {"google":{"families":["Libre+Baskerville:b:latin,latin-ext","Playfair+Display:r,i,b,bi:latin,latin-ext"]},"api_url":"https:\/\/fonts-api.wp.com\/css"}; 11 (function() { 12 var wf = document.createElement('script'); 13 wf.src = '/wp-content/plugins/custom-fonts/js/webfont.js'; 14 wf.type = 'text/javascript'; 15 wf.async = 'true'; 16 var s = document.getElementsByTagName('script')[0]; 17 s.parentNode.insertBefore(wf, s); 18 })(); 19</script>
19<style id="jetpack-custom-fonts-css">.wf-active code, .wf-active kbd, .wf-active pre, .wf-active samp{font-family:"Playfair Display",serif}.wf-active body{font-family:"Playfair Display",serif}.wf-active button, .wf-active input, .wf-active select{font-family:"Playfair Display",serif}.wf-active textarea{font-family:"Playfair Display",serif}.wf-active blockquote{font-family:"Playfair Display",serif}.wf-active code, .wf-active kbd, .wf-active pre, .wf-active tt, .wf-active var{font-family:"Playfair Display",serif}.wf-active .button, .wf-active .more-link, .wf-active button:not(.menu-toggle), .wf-active input[type="button"], .wf-active input[type="reset"], .wf-active input[type="submit"], .wf-active .posts-navigation .nav-links a, .wf-active #content #infinite-handle span button{font-family:"Playfair Display",serif}.wf-active input[type="email"], .wf-active input[type="password"], .wf-active input[type="search"], .wf-active input[type="text"], .wf-active input[type="url"], .wf-active textarea{font-family:"Playfair Display",serif}.wf-active .post-navigation{font-family:"Playfair Display",serif}.wf-active .main-navigation{font-family:"Playfair Display",serif}.wf-active .entry-content, .wf-active .entry-summary, .wf-active .page-content{font-family:"Playfair Display",serif}.wf-active .entry-content .subtitle{font-family:"Playfair Display",serif}.wf-active #comments{font-family:"Playfair Display",serif}.wf-active .comment-form label{font-family:"Playfair Display",serif}.wf-active .comment-form span.required{font-family:"Playfair Display",serif}.wf-active .widget_recent_entries span.post-date{font-family:"Playfair Display",serif}.wf-active .site-description{font-family:"Playfair Display",serif}.wf-active .site-posted-on time{font-family:"Playfair Display",serif}.wf-active .page-header:not(.page-header-light) .taxonomy-description{font-family:"Playfair Display",serif}.wf-active .light-text{font-family:"Playfair Display",serif}.wf-active .site-info{font-family:"Playfair Display",serif}.wf-active .sticky-label{font-family:"Playfair Display",serif}.wf-active .post-details, .wf-active .post-details a{font-family:"Playfair Display",serif}.wf-active .page-links{font-family:"Playfair Display",serif}.wf-active .post-edit-link{font-family:"Playfair Display",serif}.wf-active .post-author-card .author-description{font-family:"Playfair Display",serif}.wf-active #tinymce h1, .wf-active #tinymce h2, .wf-active #tinymce h3, .wf-active #tinymce h4, .wf-active #tinymce h5, .wf-active #tinymce h6, .wf-active .comment-content h1, .wf-active .comment-content h2, .wf-active .comment-content h3, .wf-active .comment-content h4, .wf-active .comment-content h5, .wf-active .comment-content h6, .wf-active .entry-content h1, .wf-active .entry-content h2, .wf-active .entry-content h3, .wf-active .entry-content h4, .wf-active .entry-content h5, .wf-active .entry-content h6, .wf-active .entry-summary h1, .wf-active .entry-summary h2, .wf-active .entry-summary h3, .wf-active .entry-summary h4, .wf-active .entry-summary h5, .wf-active .entry-summary h6, .wf-active .widget_text h1, .wf-active .widget_text h2, .wf-active .widget_text h3, .wf-active .widget_text h4, .wf-active .widget_text h5, .wf-active .widget_text h6{font-family:"Libre Baskerville",serif;font-style:normal;font-weight:700}.wf-active h1{font-size:61.1px;font-style:normal;font-weight:700}.wf-active h2{font-size:40.3px;font-style:normal;font-weight:700}.wf-active h3{font-size:31.2px;font-style:normal;font-weight:700}.wf-active h4{font-size:26px;font-style:normal;font-weight:700}.wf-active h5{font-size:19.5px;font-style:normal;font-weight:700}.wf-active h6{font-size:18.2px;font-style:normal;font-weight:700}.wf-active blockquote h1, .wf-active blockquote h2, .wf-active blockquote h3, .wf-active blockquote h4{font-family:"Libre Baskerville",serif;font-weight:700;font-style:normal}.wf-active div#jp-relatedposts h3.jp-relatedposts-headline em{font-family:"Libre Baskerville",serif;font-style:normal;font-weight:700}.wf-active .comment-reply-title, .wf-active .comments-title{font-family:"Libre Baskerville",serif;font-size:33.8px;font-weight:700;font-style:normal}.wf-active .image-post-title{font-family:"Libre Baskerville",serif;font-size:20.8px;font-weight:700;font-style:normal}.wf-active .page-header:not(.page-header-light) h1{font-size:53.3px;font-style:normal;font-weight:700}.wf-active .entry-title{font-family:"Libre Baskerville",serif;font-size:2.86em;font-style:normal;font-weight:700}.wf-active #post-cover-image .cover-meta .single-post-title{font-family:"Libre Baskerville",serif;font-size:54.6px;font-style:normal;font-weight:700}.wf-active #hero-header .site-title{font-family:"Libre Baskerville",serif;font-size:53.3px;font-style:normal;font-weight:700}.wf-active .site-header .site-title{font-size:24.7px;font-style:normal;font-weight:700}.wf-active .site-header .site-description{font-size:16.9px;font-style:normal;font-weight:700}</style>
20<meta name='robots' content='max-image-preview:large' /> 21<meta name="google-site-verification" content="AExUw3WSxvIDUnG5A4Y8bqn6W1RahDebRjUE6zstHNc" /> 22 23<!-- Async WordPress.com Remote Login -->
24<script id="wpcom_remote_login_js"> 25var wpcom_remote_login_extra_auth = ''; 26function wpcom_remote_login_remove_dom_node_id( element_id ) { 27 var dom_node = document.getElementById( element_id ); 28 if ( dom_node ) { dom_node.parentNode.removeChild( dom_node ); } 29} 30function wpcom_remote_login_remove_dom_node_classes( class_name ) { 31 var dom_nodes = document.querySelectorAll( '.' + class_name ); 32 for ( var i = 0; i < dom_nodes.length; i++ ) { 33 dom_nodes[ i ].parentNode.removeChild( dom_nodes[ i ] ); 34 } 35} 36function wpcom_remote_login_final_cleanup() { 37 wpcom_remote_login_remove_dom_node_classes( "wpcom_remote_login_msg" ); 38 wpcom_remote_login_remove_dom_node_id( "wpcom_remote_login_key" ); 39 wpcom_remote_login_remove_dom_node_id( "wpcom_remote_login_validate" ); 40 wpcom_remote_login_remove_dom_node_id( "wpcom_remote_login_js" ); 41 wpcom_remote_login_remove_dom_node_id( "wpcom_request_access_iframe" ); 42 wpcom_remote_login_remove_dom_node_id( "wpcom_request_access_styles" ); 43} 44 45// Watch for messages back from the remote login 46window.addEventListener( "message", function( e ) { 47 if ( e.origin === "https://r-login.wordpress.com" ) { 48 var data = {}; 49 try { 50 data = JSON.parse( e.data ); 51 } catch( e ) { 52 wpcom_remote_login_final_cleanup(); 53 return; 54 } 55 56 if ( data.msg === 'LOGIN' ) { 57 // Clean up the login check iframe 58 wpcom_remote_login_remove_dom_node_id( "wpcom_remote_login_key" ); 59 60 var id_regex = new RegExp( /^[0-9]+$/ ); 61 var token_regex = new RegExp( /^.*|.*|.*$/ ); 62 if ( 63 token_regex.test( data.token ) 64 && id_regex.test( data.wpcomid ) 65 ) { 66 // We have everything we need to ask for a login 67 var script = document.createElement( "script" ); 68 script.setAttribute( "id", "wpcom_remote_login_validate" ); 69 script.src = '/remote-login.php?wpcom_remote_login=validate' 70 + '&wpcomid=' + data.wpcomid 71 + '&token=' + encodeURIComponent( data.token ) 72 + '&host=' + window.location.protocol 73 + '//' + window.location.hostname 74 + '&postid=8428' 75 + '&is_singular='; 76 document.body.appendChild( script ); 77 } 78 79 return; 80 } 81 82 // Safari ITP, not logged in, so redirect 83 if ( data.msg === 'LOGIN-REDIRECT' ) { 84 window.location = 'https://wordpress.com/log-in?redirect_to=' + window.location.href; 85 return; 86 } 87 88 // Safari ITP, storage access failed, remove the request 89 if ( data.msg === 'LOGIN-REMOVE' ) { 90 var css_zap = 'html { -webkit-transition: margin-top 1s; transition: margin-top 1s; } /* 9001 */ html { margin-top: 0 !important; } * html body { margin-top: 0 !important; } @media screen and ( max-width: 782px ) { html { margin-top: 0 !important; } * html body { margin-top: 0 !important; } }'; 91 var style_zap = document.createElement( 'style' ); 92 style_zap.type = 'text/css'; 93 style_zap.appendChild( document.createTextNode( css_zap ) ); 94 document.body.appendChild( style_zap ); 95 96 var e = document.getElementById( 'wpcom_request_access_iframe' ); 97 e.parentNode.removeChild( e ); 98 99 document.cookie = 'wordpress_com_login_access=denied; path=/; max-age=31536000'; 100 101 return; 102 } 103 104 // Safari ITP 105 if ( data.msg === 'REQUEST_ACCESS' ) { 106 console.log( 'request access: safari' ); 107 108 // Check ITP iframe enable/disable knob 109 if ( wpcom_remote_login_extra_auth !== 'safari_itp_iframe' ) { 110 return; 111 } 112 113 // If we are in a "private window" there is no ITP. 114 var private_window = false; 115 try { 116 var opendb = window.openDatabase( null, null, null, null ); 117 } catch( e ) { 118 private_window = true; 119 } 120 121 if ( private_window ) { 122 console.log( 'private window' ); 123 return; 124 } 125 126 var iframe = document.createElement( 'iframe' ); 127 iframe.id = 'wpcom_request_access_iframe'; 128 iframe.setAttribute( 'scrolling', 'no' ); 129 iframe.setAttribute( 'sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin allow-top-navigation-by-user-activation' ); 130 iframe.src = 'https://r-login.wordpress.com/remote-login.php?wpcom_remote_login=request_access&origin=' + encodeURIComponent( data.origin ) + '&wpcomid=' + encodeURIComponent( data.wpcomid ); 131 132 var css = 'html { -webkit-transition: margin-top 1s; transition: margin-top 1s; } /* 9001 */ html { margin-top: 46px !important; } * html body { margin-top: 46px !important; } @media screen and ( max-width: 660px ) { html { margin-top: 71px !important; } * html body { margin-top: 71px !important; } #wpcom_request_access_iframe { display: block; height: 71px !important; } } #wpcom_request_access_iframe { border: 0px; height: 46px; position: fixed; top: 0; left: 0; width: 100%; min-width: 100%; z-index: 99999; background: #23282d; } '; 133 134 var style = document.createElement( 'style' ); 135 style.type = 'text/css'; 136 style.id = 'wpcom_request_access_styles'; 137 style.appendChild( document.createTextNode( css ) ); 138 document.body.appendChild( style ); 139 140 document.body.appendChild( iframe ); 141 } 142 143 if ( data.msg === 'DONE' ) { 144 wpcom_remote_login_final_cleanup(); 145 } 146 } 147}, false ); 148 149// Inject the remote login iframe after the page has had a chance to load 150// more critical resources 151window.addEventListener( "DOMContentLoaded", function( e ) { 152 var iframe = document.createElement( "iframe" );
153 iframe.style.display = "none"; 154 iframe.setAttribute( "scrolling", "no" ); 155 iframe.setAttribute( "id", "wpcom_remote_login_key" ); 156 iframe.src = "https://r-login.wordpress.com/remote-login.php" 157 + "?wpcom_remote_login=key" 158 + "&origin=aHR0cHM6Ly9yYXlzaW5uZW1hLmJsb2c%3D" 159 + "&wpcomid=3802920" 160 + "&time=" + Math.floor( Date.now() / 1000 ); 161 document.body.appendChild( iframe ); 162}, false ); 163</script>
163 164<link rel='dns-prefetch' href='//s2.wp.com' /> 165<link rel='dns-prefetch' href='//s0.wp.com' /> 166<link rel="alternate" type="application/rss+xml" title="Ray on Software Architecture » Feed" href="https://raysinnema.blog/feed/" /> 167<link rel="alternate" type="application/rss+xml" title="Ray on Software Architecture » Comments Feed" href="https://raysinnema.blog/comments/feed/" /> 168<link rel="alternate" type="application/rss+xml" title="Ray on Software Architecture » Posts by Ray Sinnema Feed" href="https://raysinnema.blog/author/sinnema313/feed/" /> 169
169<script type="text/javascript"> 170 /* <![CDATA[ */ 171 function addLoadEvent(func) { 172 var oldonload = window.onload; 173 if (typeof window.onload != 'function') { 174 window.onload = func; 175 } else { 176 window.onload = function () { 177 oldonload(); 178 func(); 179 } 180 } 181 } 182 /* ]]> */ 183 </script>
183 184 <style id="wp-img-auto-sizes-contain-inline-css"> 185img:is([sizes=auto i],[sizes^="auto," i]){contain-intrinsic-size:3000px 1500px} 186/*# sourceURL=wp-img-auto-sizes-contain-inline-css */ 187</style> 188<link crossorigin='anonymous' rel='stylesheet' id='all-css-2-1' href='/wp-content/mu-plugins/jetpack-plugin/sun/modules/infinite-scroll/infinity.css?m=1777406400i&cssminify=yes' type='text/css' media='all' /> 189<style id="wp-emoji-styles-inline-css"> 190 191 img.wp-smiley, img.emoji { 192 display: inline !important; 193 border: none !important; 194 box-shadow: none !important; 195 height: 1em !important; 196 width: 1em !important; 197 margin: 0 0.07em !important; 198 vertical-align: -0.1em !important; 199 background: none !important; 200 padding: 0 !important; 201 } 202/*# sourceURL=wp-emoji-styles-inline-css */ 203</style> 204<style id="wp-block-library-inline-css"> 205:root{--wp-block-synced-color:#7a00df;--wp-block-synced-color--rgb:122,0,223;--wp-bound-block-color:var(--wp-block-synced-color);--wp-editor-canvas-background:#ddd;--wp-admin-theme-color:#007cba;--wp-admin-theme-color--rgb:0,124,186;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-10--rgb:0,107,160.5;--wp-admin-theme-color-darker-20:#005a87;--wp-admin-theme-color-darker-20--rgb:0,90,135;--wp-admin-border-width-focus:2px}@media (min-resolution:192dpi){:root{--wp-admin-border-width-focus:1.5px}}.wp-element-button{cursor:pointer}:root .has-very-light-gray-background-color{background-color:#eee}:root .has-very-dark-gray-background-color{background-color:#313131}:root .has-very-light-gray-color{color:#eee}:root .has-very-dark-gray-color{color:#313131}:root .has-vivid-green-cyan-to-vivid-cyan-blue-gradient-background{background:linear-gradient(135deg,#00d084,#0693e3)}:root .has-purple-crush-gradient-background{background:linear-gradient(135deg,#34e2e4,#4721fb 50%,#ab1dfe)}:root .has-hazy-dawn-gradient-background{background:linear-gradient(135deg,#faaca8,#dad0ec)}:root .has-subdued-olive-gradient-background{background:linear-gradient(135deg,#fafae1,#67a671)}:root .has-atomic-cream-gradient-background{background:linear-gradient(135deg,#fdd79a,#004a59)}:root .has-nightshade-gradient-background{background:linear-gradient(135deg,#330968,#31cdcf)}:root .has-midnight-gradient-background{background:linear-gradient(135deg,#020381,#2874fc)}:root{--wp--preset--font-size--normal:16px;
205--wp--preset--font-size--huge:42px}.has-regular-font-size{font-size:1em}.has-larger-font-size{font-size:2.625em}.has-normal-font-size{font-size:var(--wp--preset--font-size--normal)}.has-huge-font-size{font-size:var(--wp--preset--font-size--huge)}:root .has-text-align-center{text-align:center}:root .has-text-align-left{text-align:left}:root .has-text-align-right{text-align:right}.has-fit-text{white-space:nowrap!important}#end-resizable-editor-section{display:none}.aligncenter{clear:both}.items-justified-left{justify-content:flex-start}.items-justified-center{justify-content:center}.items-justified-right{justify-content:flex-end}.items-justified-space-between{justify-content:space-between}.screen-reader-text{word-wrap:normal!important;border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-break:normal!important}.screen-reader-text:focus{background-color:#ddd;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}html :where(.has-border-color){border-style:solid}html :where([style^=border-color],[style*=";border-color"],[style*="; border-color"]){border-style:solid}html :where([style^=border-top-color],[style*=";border-top-color"],[style*="; border-top-color"]){border-top-style:solid}html :where([style^=border-right-color],[style*=";border-right-color"],[style*="; border-right-color"]){border-right-style:solid}html :where([style^=border-bottom-color],[style*=";border-bottom-color"],[style*="; border-bottom-color"]){border-bottom-style:solid}html :where([style^=border-left-color],[style*=";border-left-color"],[style*="; border-left-color"]){border-left-style:solid}html :where([style^=border-width],[style*=";border-width"],[style*="; border-width"]){border-style:solid}html :where([style^=border-top-width],[style*=";border-top-width"],[style*="; border-top-width"]){border-top-style:solid}html :where([style^=border-right-width],[style*=";border-right-width"],[style*="; border-right-width"]){border-right-style:solid}html :where([style^=border-bottom-width],[style*=";border-bottom-width"],[style*="; border-bottom-width"]){border-bottom-style:solid}html :where([style^=border-left-width],[style*=";border-left-width"],[style*="; border-left-width"]){border-left-style:solid}html :where(img[class*=wp-image-]){height:auto;max-width:100%}:where(figure){margin:0 0 1em}html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:var(--wp-admin--admin-bar--height,0px)}@media screen and (max-width:600px){html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:0px}} 206 207/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/common.min.css */ 208</style> 209<style id="wp-block-heading-inline-css"> 210h1:where(.wp-block-heading).has-background,h2:where(.wp-block-heading).has-background,h3:where(.wp-block-heading).has-background,h4:where(.wp-block-heading).has-background,h5:where(.wp-block-heading).has-background,h6:where(.wp-block-heading).has-background{padding:var(--wp--style--block-background-padding,1.25em 2.375em)}h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){rotate:180deg} 211/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/heading/style.min.css */ 212</style> 213<style id="wp-block-image-inline-css"> 214.wp-block-image>a,.wp-block-image>figure>a{display:inline-block}.wp-block-image img{box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom}@media not (prefers-reduced-motion){.wp-block-image img.hide{visibility:hidden}.wp-block-image img.show{animation:show-content-image .4s}}.wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{border-radius:inherit}.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-image.aligncenter{text-align:center}.wp-block-image.alignfull>a,.wp-block-image.alignwide>a{width:100%}.wp-block-image.alignfull img,.wp-block-image.alignwide img{height:auto;width:100%}.wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{display:table}.wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{caption-side:bottom;display:table-caption}.wp-block-image .alignleft{float:left;margin:.5em 1em .5em 0}.wp-block-image .alignright{float:right;margin:.5em 0 .5em 1em}.wp-block-image .aligncenter{margin-left:auto;margin-right:auto}.wp-block-image :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-image.is-style-circle-mask img{border-radius:9999px}@supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){.wp-block-image.is-style-circle-mask img{border-radius:0;-webkit-mask-image:url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>');mask-image:url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>');mask-mode:alpha;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}}:root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){border-radius:9999px}.wp-block-image figure{margin:0}.wp-lightbox-container{display:flex;flex-direction:column;position:relative}.wp-lightbox-container img{cursor:zoom-in}.wp-lightbox-container img:hover+button{opacity:1}.wp-lightbox-container button{align-items:center;backdrop-filter:blur(16px) saturate(180%);background-color:rgba(90,90,90,.25);border:none;border-radius:4px;cursor:zoom-in;display:flex;height:20px;justify-content:center;opacity:0;padding:0;position:absolute;right:16px;text-align:center;top:16px;width:20px;z-index:100}@media not (prefers-reduced-motion){.wp-lightbox-container button{transition:opacity .2s ease}}.wp-lightbox-container button:focus-visible{outline:3px auto rgba(90,90,90,.25);outline:3px auto -webkit-focus-ring-color;outline-offset:3px}.wp-lightbox-container button:hover{cursor:pointer;opacity:1}.wp-lightbox-container button:focus{opacity:1}.wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){background-color:rgba(90,90,90,.25);border:none}.wp-lightbox-overlay{box-sizing:border-box;
214cursor:zoom-out;height:100vh;left:0;overflow:hidden;position:fixed;top:0;visibility:hidden;width:100%;z-index:100000}.wp-lightbox-overlay .wp-lightbox-close-button{align-items:center;cursor:pointer;display:flex;font-family:inherit;gap:8px;justify-content:center;line-height:1;min-height:40px;min-width:40px;padding:0 4px;position:absolute;right:calc(env(safe-area-inset-right) + 16px);top:calc(env(safe-area-inset-top) + 16px);z-index:5000000}.wp-lightbox-overlay .wp-lightbox-close-button:focus,.wp-lightbox-overlay .wp-lightbox-close-button:hover,.wp-lightbox-overlay .wp-lightbox-close-button:not(:hover):not(:active):not(.has-background){background:none;border:none}.wp-lightbox-overlay .wp-lightbox-close-button:has(.wp-lightbox-close-text:not([hidden])) .wp-lightbox-close-icon svg{height:1em;width:1em}.wp-lightbox-overlay .wp-lightbox-close-icon svg{display:block}.wp-lightbox-overlay .wp-lightbox-navigation-button-next,.wp-lightbox-overlay .wp-lightbox-navigation-button-prev{align-items:center;bottom:16px;cursor:pointer;display:flex;font-family:inherit;gap:4px;justify-content:center;line-height:1;min-height:40px;min-width:40px;padding:0 8px;position:absolute;z-index:2000002}.wp-lightbox-overlay .wp-lightbox-navigation-button-next[hidden],.wp-lightbox-overlay .wp-lightbox-navigation-button-prev[hidden]{display:none}@media (min-width:960px){.wp-lightbox-overlay .wp-lightbox-navigation-button-next,.wp-lightbox-overlay .wp-lightbox-navigation-button-prev{bottom:50%;transform:translateY(-50%)}}.wp-lightbox-overlay .wp-lightbox-navigation-button-next:focus,.wp-lightbox-overlay .wp-lightbox-navigation-button-next:hover,.wp-lightbox-overlay .wp-lightbox-navigation-button-next:not(:hover):not(:active):not(.has-background),.wp-lightbox-overlay .wp-lightbox-navigation-button-prev:focus,.wp-lightbox-overlay .wp-lightbox-navigation-button-prev:hover,.wp-lightbox-overlay .wp-lightbox-navigation-button-prev:not(:hover):not(:active):not(.has-background){background:none;border:none;padding:0 8px}.wp-lightbox-overlay .wp-lightbox-navigation-button-next:has(.wp-lightbox-navigation-text:not([hidden])) .wp-lightbox-navigation-icon svg,.wp-lightbox-overlay .wp-lightbox-navigation-button-prev:has(.wp-lightbox-navigation-text:not([hidden])) .wp-lightbox-navigation-icon svg{display:block;height:1.5em;width:1.5em}.wp-lightbox-overlay .wp-lightbox-navigation-button-prev{left:calc(env(safe-area-inset-left) + 16px)}.wp-lightbox-overlay .wp-lightbox-navigation-button-next{right:calc(env(safe-area-inset-right) + 16px)}.wp-lightbox-overlay .wp-lightbox-navigation-icon svg{vertical-align:middle}.wp-lightbox-overlay .lightbox-image-container{height:var(--wp--lightbox-container-height);left:50%;overflow:hidden;position:absolute;top:50%;transform:translate(-50%,-50%);transform-origin:top left;width:var(--wp--lightbox-container-width);z-index:2000001}.wp-lightbox-overlay .wp-block-image{align-items:center;box-sizing:border-box;display:flex;height:100%;justify-content:center;margin:0;position:relative;transform-origin:0 0;width:100%;z-index:3000000}.wp-lightbox-overlay .wp-block-image img{height:var(--wp--lightbox-image-height);min-height:var(--wp--lightbox-image-height);min-width:var(--wp--lightbox-image-width);width:var(--wp--lightbox-image-width)}.wp-lightbox-overlay .wp-block-image figcaption{display:none}.wp-lightbox-overlay button{background:none;border:none}.wp-lightbox-overlay .scrim{background-color:#fff;height:100%;opacity:.9;position:absolute;width:100%;z-index:2000000}.wp-lightbox-overlay.active{visibility:visible}@media not (prefers-reduced-motion){.wp-lightbox-overlay.active{animation:turn-on-visibility .25s both}.wp-lightbox-overlay.active img{animation:turn-on-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active){animation:turn-off-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active) img{animation:turn-off-visibility .25s both}.wp-lightbox-overlay.zoom.active{animation:none;opacity:1;visibility:visible}.wp-lightbox-overlay.zoom.active .lightbox-image-container{animation:lightbox-zoom-in .4s}.wp-lightbox-overlay.zoom.active .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.active .scrim{animation:turn-on-visibility .4s forwards}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active){animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{animation:lightbox-zoom-out .4s}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{animation:turn-off-visibility .4s forwards}}
214@keyframes show-content-image{0%{visibility:hidden}99%{visibility:hidden}to{visibility:visible}}@keyframes turn-on-visibility{0%{opacity:0}to{opacity:1}}@keyframes turn-off-visibility{0%{opacity:1;visibility:visible}99%{opacity:0;visibility:visible}to{opacity:0;visibility:hidden}}@keyframes lightbox-zoom-in{0%{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale))}to{transform:translate(-50%,-50%) scale(1)}}@keyframes lightbox-zoom-out{0%{transform:translate(-50%,-50%) scale(1);visibility:visible}99%{visibility:visible}to{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));visibility:hidden}} 215/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/image/style.min.css */ 216</style> 217<style id="wp-block-image-theme-inline-css"> 218:root :where(.wp-block-image figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme :root :where(.wp-block-image figcaption){color:hsla(0,0%,100%,.65)}.wp-block-image{margin:0 0 1em} 219/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/image/theme.min.css */ 220</style> 221<style id="wp-block-list-inline-css"> 222ol,ul{box-sizing:border-box}:root :where(.wp-block-list.has-background){padding:var(--wp--style--block-background-padding,1.25em 2.375em)} 223/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/list/style.min.css */ 224</style> 225<style id="wp-block-paragraph-inline-css"> 226.is-small-text{font-size:.875em}.is-regular-text{font-size:1em}.is-large-text{font-size:2.25em}.is-larger-text{font-size:3em}.has-drop-cap:not(:focus):first-letter{float:left;font-size:8.4em;font-style:normal;font-weight:100;line-height:.68;margin:.05em .1em 0 0;text-transform:uppercase}body.rtl .has-drop-cap:not(:focus):first-letter{float:none;margin-left:.1em}p.has-drop-cap.has-background{overflow:hidden}:root :where(p.has-background){padding:var(--wp--style--block-background-padding,1.25em 2.375em)}:where(p.has-text-color:not(.has-link-color)) a{color:inherit}p.has-text-align-left[style*="writing-mode:vertical-lr"],p.has-text-align-right[style*="writing-mode:vertical-rl"]{rotate:180deg} 227/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/paragraph/style.min.css */ 228</style> 229<style id="wp-block-library-inline-css-extra"> 230.has-text-align-justify { 231 text-align:justify; 232} 233.has-text-align-justify{text-align:justify;} 234/*# sourceURL=wp-block-library-inline-css */ 235</style> 236 237<style id="classic-theme-styles-inline-css"> 238.wp-block-button__link{background-color:#32373c;border-radius:9999px;box-shadow:none;color:#fff;font-size:1.125em;padding:calc(.667em + 2px) calc(1.333em + 2px);text-decoration:none}.wp-block-file__button{background:#32373c;color:#fff}.wp-block-accordion-heading{margin:0}.wp-block-accordion-heading__toggle{background-color:inherit!important;color:inherit!important}.wp-block-accordion-heading__toggle:not(:focus-visible){outline:none}.wp-block-accordion-heading__toggle:focus,.wp-block-accordion-heading__toggle:hover{background-color:inherit!important;border:none;box-shadow:none;color:inherit;padding:var(--wp--preset--spacing--20,1em) 0;text-decoration:none}.wp-block-accordion-heading__toggle:focus-visible{outline:auto;outline-offset:0}.wp-block-tab:not(.has-text-color){color:inherit!important}.wp-block-tab:not(.has-background){background-color:inherit!important}.wp-block-tab:focus,.wp-block-tab:hover{text-decoration:none}.wp-block-tab:focus-visible{outline:auto;outline-offset:0} 239/*# sourceURL=/wp-content/plugins/gutenberg-core/v24.0.0/build/styles/block-library/classic.min.css */ 240</style> 241 242<style id="coblocks-inline-format-inline-css"> 243.uppercase{text-transform:uppercase} 244/*# sourceURL=coblocks-inline-format-inline-css */ 245</style> 246<link crossorigin='anonymous' rel='stylesheet' id='all-css-12-1' href='/_static/??-eJx9jUsOwjAMRC+EY6UgCAvEWZrEKoG4jWq3hduTLvisurFG1nszuBQIQ6/UK5Y8dakXDIPPQ3gINsY6Y0ESl0ww0mwOGJPolwDRVyYTRHb4V8QT/L
246pGqn8ura4EU0wtZeKKbWlLih1p1eWTQem5rZQ6A96XkUSgXk4Tg97qlqzelS/26KzbN835dH8DjlpbIA==&cssminify=yes' type='text/css' media='all' /> 247<style id="global-styles-inline-css"> 248:root{--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #fff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--color--medium-blue: #0087be;--wp--preset--color--bright-blue: #00aadc;--wp--preset--color--dark-gray: #4d4d4b;--wp--preset--color--light-gray: #b3b3b1;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgb(6,147,227) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgb(252,185,0) 0%,rgb(255,105,0) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgb(255,105,0) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);
248--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--font-size--small: 13px;--wp--preset--font-size--medium: 20px;--wp--preset--font-size--large: 36px;--wp--preset--font-size--x-large: 42px;--wp--preset--font-family--albert-sans: 'Albert Sans', sans-serif;--wp--preset--font-family--alegreya: Alegreya, serif;--wp--preset--font-family--arvo: Arvo, serif;--wp--preset--font-family--bodoni-moda: 'Bodoni Moda', serif;--wp--preset--font-family--bricolage-grotesque: 'Bricolage Grotesque', sans-serif;--wp--preset--font-family--cabin: Cabin, sans-serif;--wp--preset--font-family--chivo: Chivo, sans-serif;--wp--preset--font-family--commissioner: Commissioner, sans-serif;--wp--preset--font-family--cormorant: Cormorant, serif;--wp--preset--font-family--courier-prime: 'Courier Prime', monospace;--wp--preset--font-family--crimson-pro: 'Crimson Pro', serif;--wp--preset--font-family--dm-mono: 'DM Mono', monospace;--wp--preset--font-family--dm-sans: 'DM Sans', sans-serif;--wp--preset--font-family--dm-serif-display: 'DM Serif Display', serif;--wp--preset--font-family--domine: Domine, serif;--wp--preset--font-family--eb-garamond: 'EB Garamond', serif;--wp--preset--font-family--epilogue: Epilogue, sans-serif;--wp--preset--font-family--fahkwang: Fahkwang, sans-serif;--wp--preset--font-family--figtree: Figtree, sans-serif;--wp--preset--font-family--fira-sans: 'Fira Sans', sans-serif;--wp--preset--font-family--fjalla-one: 'Fjalla One', sans-serif;--wp--preset--font-family--fraunces: Fraunces, serif;--wp--preset--font-family--gabarito: Gabarito, system-ui;--wp--preset--font-family--ibm-plex-mono: 'IBM Plex Mono', monospace;--wp--preset--font-family--ibm-plex-sans: 'IBM Plex Sans', sans-serif;--wp--preset--font-family--ibarra-real-nova: 'Ibarra Real Nova', serif;--wp--preset--font-family--instrument-serif: 'Instrument Serif', serif;--wp--preset--font-family--inter: Inter, sans-serif;--wp--preset--font-family--josefin-sans: 'Josefin Sans', sans-serif;--wp--preset--font-family--jost: Jost, sans-serif;--wp--preset--font-family--libre-baskerville: 'Libre Baskerville', serif;--wp--preset--font-family--libre-franklin: 'Libre Franklin', sans-serif;--wp--preset--font-family--literata: Literata, serif;--wp--preset--font-family--lora: Lora, serif;--wp--preset--font-family--merriweather: Merriweather, serif;--wp--preset--font-family--montserrat: Montserrat, sans-serif;--wp--preset--font-family--newsreader: Newsreader, serif;--wp--preset--font-family--noto-sans-mono: 'Noto Sans Mono', sans-serif;--wp--preset--font-family--nunito: Nunito, sans-serif;--wp--preset--font-family--open-sans: 'Open Sans', sans-serif;--wp--preset--font-family--overpass: Overpass, sans-serif;--wp--preset--font-family--pt-serif: 'PT Serif', serif;--wp--preset--font-family--petrona: Petrona, serif;--wp--preset--font-family--piazzolla: Piazzolla, serif;--wp--preset--font-family--playfair-display: 'Playfair Display', serif;--wp--preset--font-family--plus-jakarta-sans: 'Plus Jakarta Sans', sans-serif;--wp--preset--font-family--poppins: Poppins, sans-serif;--wp--preset--font-family--raleway: Raleway, sans-serif;--wp--preset--font-family--roboto: Roboto, sans-serif;--wp--preset--font-family--roboto-slab: 'Roboto Slab', serif;--wp--preset--font-family--rubik: Rubik, sans-serif;--wp--preset--font-family--rufina: Rufina, serif;--wp--preset--font-family--sora: Sora, sans-serif;--wp--preset--font-family--source-sans-3: 'Source Sans 3', sans-serif;--wp--preset--font-family--source-serif-4: 'Source Serif 4', serif;--wp--preset--font-family--space-mono: 'Space Mono', monospace;--wp--preset--font-family--syne: Syne, sans-serif;--wp--preset--font-family--texturina: Texturina, serif;--wp--preset--font-family--urbanist: Urbanist, sans-serif;--wp--preset--font-family--work-sans: 'Work Sans', sans-serif;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem;--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgb(255, 255, 255), 6px 6px rgb(0, 0, 0);--wp--preset--shadow--crisp: 6px 6px 0px rgb(0, 0, 0);}.wp-block-button{--wp--preset--dimension--25: 25%;--wp--preset--dimension--50: 50%;--wp--preset--dimension--75: 75%;--wp--preset--dimension--100: 100%;}:where(body) { margin: 0; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 0px;}:root :where(.wp-element-button, .wp-block-button__link){background-color: #32373c;border-width: 0;color: #fff;font-family: inherit;font-size: inherit;font-style: inherit;font-weight: inherit;letter-spacing: inherit;line-height: inherit;padding-top: calc(0.667em + 2px);padding-right: calc(1.333em + 2px);padding-bottom: calc(0.667em + 2px);padding-left: calc(1.333em + 2px);text-decoration: none;text-transform: inherit;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-medium-blue-color{color: var(--wp--preset--color--medium-blue) !important;}.has-bright-blue-color{color: var(--wp--preset--color--bright-blue) !important;}.has-dark-gray-color{color: var(--wp--preset--color--dark-gray) !important;}.has-light-gray-color{color: var(--wp--preset--color--light-gray) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-medium-blue-background-color{background-color: var(--wp--preset--color--medium-blue) !important;}.has-bright-blue-background-color{background-color: var(--wp--preset--color--bright-blue) !important;}.has-dark-gray-background-color{background-color: var(--wp--preset--color--dark-gray) !important;}.has-light-gray-background-color{background-color: var(--wp--preset--color--light-gray) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-medium-blue-border-color{border-color: var(--wp--preset--color--medium-blue) !important;}.has-bright-blue-border-color{border-color: var(--wp--preset--color--bright-blue) !important;}.has-dark-gray-border-color{border-color: var(--wp--preset--color--dark-gray) !important;}.has-light-gray-border-color{border-color: var(--wp--preset--color--light-gray) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}
248.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;}.has-albert-sans-font-family{font-family: var(--wp--preset--font-family--albert-sans) !important;}.has-alegreya-font-family{font-family: var(--wp--preset--font-family--alegreya) !important;}.has-arvo-font-family{font-family: var(--wp--preset--font-family--arvo) !important;}.has-bodoni-moda-font-family{font-family: var(--wp--preset--font-family--bodoni-moda) !important;}.has-bricolage-grotesque-font-family{font-family: var(--wp--preset--font-family--bricolage-grotesque) !important;}.has-cabin-font-family{font-family: var(--wp--preset--font-family--cabin) !important;}.has-chivo-font-family{font-family: var(--wp--preset--font-family--chivo) !important;}.has-commissioner-font-family{font-family: var(--wp--preset--font-family--commissioner) !important;}.has-cormorant-font-family{font-family: var(--wp--preset--font-family--cormorant) !important;}.has-courier-prime-font-family{font-family: var(--wp--preset--font-family--courier-prime) !important;}.has-crimson-pro-font-family{font-family: var(--wp--preset--font-family--crimson-pro) !important;}.has-dm-mono-font-family{font-family: var(--wp--preset--font-family--dm-mono) !important;}.has-dm-sans-font-family{font-family: var(--wp--preset--font-family--dm-sans) !important;}.has-dm-serif-display-font-family{font-family: var(--wp--preset--font-family--dm-serif-display) !important;}.has-domine-font-family{font-family: var(--wp--preset--font-family--domine) !important;}.has-eb-garamond-font-family{font-family: var(--wp--preset--font-family--eb-garamond) !important;}.has-epilogue-font-family{font-family: var(--wp--preset--font-family--epilogue) !important;}.has-fahkwang-font-family{font-family: var(--wp--preset--font-family--fahkwang) !important;}.has-figtree-font-family{font-family: var(--wp--preset--font-family--figtree) !important;}.has-fira-sans-font-family{font-family: var(--wp--preset--font-family--fira-sans) !important;}.has-fjalla-one-font-family{font-family: var(--wp--preset--font-family--fjalla-one) !important;}.has-fraunces-font-family{font-family: var(--wp--preset--font-family--fraunces) !important;}.has-gabarito-font-family{font-family: var(--wp--preset--font-family--gabarito) !important;}.has-ibm-plex-mono-font-family{font-family: var(--wp--preset--font-family--ibm-plex-mono) !important;}.has-ibm-plex-sans-font-family{font-family: var(--wp--preset--font-family--ibm-plex-sans) !important;}.has-ibarra-real-nova-font-family{font-family: var(--wp--preset--font-family--ibarra-real-nova) !important;}.has-instrument-serif-font-family{font-family: var(--wp--preset--font-family--instrument-serif) !important;}.has-inter-font-family{font-family: var(--wp--preset--font-family--inter) !important;}.has-josefin-sans-font-family{font-family: var(--wp--preset--font-family--josefin-sans) !important;}.has-jost-font-family{font-family: var(--wp--preset--font-family--jost) !important;}.has-libre-baskerville-font-family{font-family: var(--wp--preset--font-family--libre-baskerville) !important;}.has-libre-franklin-font-family{font-family: var(--wp--preset--font-family--libre-franklin) !important;}.has-literata-font-family{font-family: var(--wp--preset--font-family--literata) !important;}.has-lora-font-family{font-family: var(--wp--preset--font-family--lora) !important;}.has-merriweather-font-family{font-family: var(--wp--preset--font-family--merriweather) !important;}.has-montserrat-font-family{font-family: var(--wp--preset--font-family--montserrat) !important;}.has-newsreader-font-family{font-family: var(--wp--preset--font-family--newsreader) !important;}.has-noto-sans-mono-font-family{font-family: var(--wp--preset--font-family--noto-sans-mono) !important;}.has-nunito-font-family{font-family: var(--wp--preset--font-family--nunito) !important;}.has-open-sans-font-family{font-family: var(--wp--preset--font-family--open-sans) !important;}.has-overpass-font-family{font-family: var(--wp--preset--font-family--overpass) !important;}.has-pt-serif-font-family{font-family: var(--wp--preset--font-family--pt-serif) !important;}.has-petrona-font-family{font-family: var(--wp--preset--font-family--petrona) !important;}.has-piazzolla-font-family{font-family: var(--wp--preset--font-family--piazzolla) !important;}.has-playfair-display-font-family{font-family: var(--wp--preset--font-family--playfair-display) !important;}.has-plus-jakarta-sans-font-family{font-family: var(--wp--preset--font-family--plus-jakarta-sans) !important;}.has-poppins-font-family{font-family: var(--wp--preset--font-family--poppins) !important;}.has-raleway-font-family{font-family: var(--wp--preset--font-family--raleway) !important;}.has-roboto-font-family{font-family: var(--wp--preset--font-family--roboto) !important;}.has-roboto-slab-font-family{font-family: var(--wp--preset--font-family--roboto-slab) !important;}.has-rubik-font-family{font-family: var(--wp--preset--font-family--rubik) !important;}.has-rufina-font-family{font-family: var(--wp--preset--font-family--rufina) !important;}.has-sora-font-family{font-family: var(--wp--preset--font-family--sora) !important;}.has-source-sans-3-font-family{font-family: var(--wp--preset--font-family--source-sans-3) !important;}.has-source-serif-4-font-family{font-family: var(--wp--preset--font-family--source-serif-4) !important;}.has-space-mono-font-family{font-family: var(--wp--preset--font-family--space-mono) !important;}.has-syne-font-family{font-family: var(--wp--preset--font-family--syne) !important;}.has-texturina-font-family{font-family: var(--wp--preset--font-family--texturina) !important;}.has-urbanist-font-family{font-family: var(--wp--preset--font-family--urbanist) !important;}.has-work-sans-font-family{font-family: var(--wp--preset--font-family--work-sans) !important;} 249/*# sourceURL=global-styles-inline-css */ 250</style> 251 252<link crossorigin='anonymous' rel='stylesheet' id='all-css-14-1' href='/_static/??-eJydkMEKwjAQRH/IuFoEzUH8FEnjmqZNNiGbUPx70yJYUTz0sswc3szuwhiFDpSRMvgioivGEkOPOSo9vDxwIbha0mCQMNkK8G+51cwbWGTmDj0yxNKCpRtGrIOyqN5Z7jCJBjg/HK4BKwKtC3pYVTudM1eLMergvyIW30hYa0yVZu5823+QwSDqcirbQB9G3J2yaUIv/rw/nhopd/Ig+yckdJQO&cssminify=yes' type='text/css' media='all' /> 253<link crossorigin='anonymous' rel='stylesheet' id='print-css-15-1' href='/wp-content/mu-plugins/global-print/global-print.css?m=1465851035i&cssminify=yes' type='text/css' media='print' /> 254<style id="jetpack-global-styles-frontend-style-inline-css"> 255:root { --font-headings: unset; --font-base: unset; --font-headings-default: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; --font-base-default: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;} 256/*# sourceURL=jetpack-global-styles-frontend-style-inline-css */ 257</style> 258<link crossorigin='anonymous' rel='stylesheet' id='all-css-18-1' href='/wp-content/themes/h4/global.css?m=1420737423i&cssminify=yes' type='text/css' media='all' />
259<script id="wpcom-actionbar-placeholder-js-extra"> 260var actionbardata = {"siteID":"3802920","postID":"0","siteURL":"https://raysinnema.blog","xhrURL":"https://raysinnema.blog/wp-admin/admin-ajax.php","nonce":"5dd2158686","isLoggedIn":"","statusMessage":"","subsEmailDefault":"instantly","proxyScriptUrl":"https://s0.wp.com/wp-content/js/wpcom-proxy-request.js?m=1513050504i&ver=20211021","i18n":{"followedText":"New posts from this site will now appear in your \u003Ca href=\"https://wordpress.com/reader\"\u003EReader\u003C/a\u003E","foldBar":"Collapse this bar","unfoldBar":"Expand this bar","shortLinkCopied":"Shortlink copied to clipboard."}}; 261//# sourceURL=wpcom-actionbar-placeholder-js-extra 262</script>
vendor: 1 bytes, line 262
262
263<script id="jetpack-mu-wpcom-settings-js-before"> 264var JETPACK_MU_WPCOM_SETTINGS = {"assetsUrl":"https://s1.wp.com/wp-content/mu-plugins/jetpack-mu-wpcom-plugin/sun/jetpack_vendor/automattic/jetpack-mu-wpcom/src/build/"}; 265//# sourceURL=jetpack-mu-wpcom-settings-js-before 266</script>
vendor: 1 bytes, line 266
266
267<script crossorigin='anonymous' type='text/javascript' src='/wp-content/js/rlt-proxy.js?m=1720530689i'></script>
vendor: 1 bytes, line 267
267
268<script id="rlt-proxy-js-after"> 269 rltInitialize( {"token":null,"iframeOrigins":["https:\/\/widgets.wp.com"]} ); 270//# sourceURL=rlt-proxy-js-after 271</script>
271 272<link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://sinnema313.wordpress.com/xmlrpc.php?rsd" /> 273<meta name="generator" content="WordPress.com" /> 274 275<!-- Jetpack Open Graph Tags --> 276<meta property="og:type" content="profile" /> 277<meta property="og:title" content="Ray Sinnema" /> 278<meta property="og:url" content="https://remonsinnema.com" /> 279<meta property="profile:first_name" content="Rémon" /> 280<meta property="profile:last_name" content="Sinnema" /> 281<meta property="og:site_name" content="Ray on Software Architecture" /> 282<meta property="og:image" content="https://secure.gravatar.com/avatar/2c25573914458990f88492562fbae524d5302a662b7555d4d46428c8e0e2a595?s=200&r=g" /> 283<meta property="og:image:alt" content="Ray Sinnema" /> 284<meta property="og:locale" content="en_US" /> 285 286<!-- End Jetpack Open Graph Tags --> 287<link rel="shortcut icon" type="image/x-icon" href="https://s1.wp.com/i/favicon.ico?m=1713425267i" sizes="16x16 24x24 32x32 48x48" /> 288<link rel="icon" type="image/x-icon" href="https://s1.wp.com/i/favicon.ico?m=1713425267i" sizes="16x16 24x24 32x32 48x48" /> 289<link rel="apple-touch-icon" href="https://s2.wp.com/i/webclip.png?m=1713868326i" /> 290<link rel='openid.server' href='https://raysinnema.blog/?openidserver=1' /> 291<link rel='openid.delegate' href='https://raysinnema.blog/' /> 292<link rel="search" type="application/opensearchdescription+xml" href="https://raysinnema.blog/osd.xml" title="Ray on Software Architecture" /> 293<link rel="search" type="application/opensearchdescription+xml" href="https://s1.wp.com/opensearch.xml" title="WordPress.com" /> 294<meta name="theme-color" content="#fcfcfc" /> 295 <style type="text/css"> 296 .recentcomments a { 297 display: inline !important; 298 padding: 0 !important; 299 margin: 0 !important; 300 } 301 302 table.recentcommentsavatartop img.avatar, table.recentcommentsavatarend img.avatar { 303 border: 0px; 304 margin: 0; 305 } 306 307 table.recentcommentsavatartop a, table.recentcommentsavatarend a { 308 border: 0px !important; 309 background-color: transparent !important; 310 } 311 312 td.recentcommentsavatarend, td.recentcommentsavatartop { 313 padding: 0px 0px 1px 0px; 314 margin: 0px; 315 } 316 317 td.recentcommentstextend { 318 border: none !important; 319 padding: 0px 0px 2px 10px; 320 } 321 322 .rtl td.recentcommentstextend { 323 padding: 0px 10px 2px 0px; 324 } 325 326 td.recentcommentstexttop { 327 border: none; 328 padding: 0px 0px 0px 10px; 329 } 330 331 .rtl td.recentcommentstexttop { 332 padding: 0px 10px 0px 0px; 333 } 334 </style> 335 <meta name="description" content="Read all of the posts by Ray Sinnema on Ray on Software Architecture" /> 336<style id="custom-background-css"> 337body.custom-background { background-color: #fcfcfc; } 338</style> 339<style type="text/css" id="custom-colors-css"> .has-header-image .site-title a, 340 .has-header-image .site-title a:visited { 341 color: #fff; 342 } 343 344 @media screen and ( max-width: 32.374em ) { 345 .main-navigation ul ul { 346 background: transparent !important; 347 } 348 .main-navigation ul ul a { 349 color: inherit !important; 350 } 351 } 352 .widget_recent_comments a, 353 .widget_recent_entries a, 354 body, 355 input, 356 select, 357 textarea, 358 .menu-toggle { color: #383838;} 359#infinite-footer .blog-info a:hover, 360 #infinite-footer .blog-credits a:hover { color: #383838;} 361.posts-navigation .nav-links a, 362 .main-navigation ul ul a, 363 .main-navigation > div > ul > li.current-menu-item > ul > li a, 364 .main-navigation > div > ul > li.current_page_item > ul > li a { color: #191919;} 365input[type="button"], 366 input[type="button"]:hover, 367 input[type="reset"], 368 input[type="reset"]:hover, 369 input[type="submit"], 370 input[type="submit"]:hover, 371 button, 372 .button, 373 .button:hover, 374 #content #infinite-handle span button, 375 #content #infinite-handle span button:hover, 376 .more-link, 377 .more-link:hover, 378 .more-link:visited { color: #4F4F4F;} 379.site-main > .hentry:nth-child(n+2), .site .infinite-wrap > .hentry:nth-child(n+2), 380 .entry-author-wrapper, 381 .post-navigation, 382 .comment, 383 .page-links a:hover, 384 .main-navigation li { border-color: #dddddd;} 385.site-main > .hentry:nth-child(n+2), .site .infinite-wrap > .hentry:nth-child(n+2), 386 .entry-author-wrapper, 387 .post-navigation, 388 .comment, 389 .page-links a:hover, 390 .main-navigation li { border-color: rgba( 221, 221, 221, 0.25 );} 391#infinite-footer .blog-info a, 392 #infinite-footer .blog-credits, 393 #infinite-footer .blog-credits a { color: #6D6D6D;} 394.post-details, 395 .post-details a, 396 .post-details a:visited, 397 .post-edit-link a, 398 .post-edit-link a:visited { color: #6C6C6A;} 399.post-tags li:first-child, 400 .jetpack-social-navigation li a:hover, 401 .widget_wpcom_social_media_icons_widget li a:hover, 402 .jetpack-social-navigation li a:focus, 403 .widget_wpcom_social_media_icons_widget li a:focus, 404 .jetpack-social-navigation li a:active, 405 .widget_wpcom_social_media_icons_widget li a:active { color: #515151;} 406.jetpack-social-navigation li a, 407 .widget_wpcom_social_media_icons_widget li a { color: #6D6D6D;} 408.post-navigation .nav-links a:hover, 409 .post-navigation .nav-links a:focus, 410 .post-navigation .nav-links a:active, 411 .entry-author .author-bio,
412 .site-posted-on time, 413 .site-description { color: #6D6D6D;} 414.comment .comment-meta, 415 .comment-form label, 416 .light-text, 417 .light-text a, 418 .light-text a:visited, 419 .widget_rss .rss-date, 420 .widget_rss li > cite { color: #6C6C6A;} 421.light-text a:hover { color: #6C6C6A;} 422body { background-color: #fcfcfc;} 423#infinite-footer .container { background-color: #fcfcfc;} 424#infinite-footer .container { background-color: rgba( 252, 252, 252, 0.7 );} 425.post-edit-link a { background-color: #F7F7F7;} 426.entry-author .author-title, 427 .entry-title, 428 .entry-title a, 429 .entry-title a:visited, 430 .site-posted-on strong, 431 .site-title, 432 .site-title a, 433 .site-title a:visited, 434 .entry-title a:hover, 435 .site-title a:hover, 436 h1, 437 h2, 438 h3, 439 h4, 440 h5, 441 h6, 442 .page-header:not(.page-header-light) h1, 443 .comment .comment-meta .comment-author .fn { color: #1B757D;} 444.comment-form input[type="email"]:active, 445 .comment-form input[type="email"]:focus, 446 .comment-form input[type="password"]:active, 447 .comment-form input[type="password"]:focus, 448 .comment-form input[type="search"]:active, 449 .comment-form input[type="search"]:focus, 450 .comment-form input[type="text"]:active, 451 .comment-form input[type="text"]:focus, 452 .comment-form input[type="url"]:active, 453 .comment-form input[type="url"]:focus, 454 .comment-form textarea:active, 455 .comment-form textarea:focus, 456 blockquote, 457 input[type="email"]:focus, 458 input[type="password"]:focus, 459 input[type="search"]:focus, 460 input[type="text"]:focus, 461 input[type="url"]:focus, 462 textarea:focus { border-color: #b66fc9;} 463.comment .comment-meta .comment-metadata a:hover, 464 .comment-form span.required, 465 .pingback:before, 466 .post-details a:hover, 467 .post-edit-link a:active, 468 .post-edit-link a:focus, 469 .post-edit-link a:hover, 470 .site-info a:hover, 471 .trackback:before, 472 a, 473 a:visited { color: #9F47B7;} 474.main-navigation > div > ul > li.current-menu-item > a, 475 .main-navigation > div > ul > li.current_page_item > a, 476 a:active, 477 a:focus, 478 a:hover, 479 .page-links a:hover { color: #B66FC9;} 480.posts-navigation .nav-links a, 481 .main-navigation ul ul { background-color: #b66fc9;} 482button, 483 input[type="button"], 484 input[type="reset"], 485 input[type="submit"], 486 .button, 487 #content #infinite-handle span button, 488 .more-link { background-color: #f5c18e;} 489button:not(".components-button"):hover, 490 input[type="button"]:hover, 491 input[type="reset"]:hover, 492 input[type="submit"]:hover, 493 .button:hover, 494 #content #infinite-handle span button:hover, 495 .more-link:hover { background-color: #F1A85F;} 496</style> 497<link crossorigin='anonymous' rel='stylesheet' id='all-css-0-5' href='/_static/??-eJydzM0KAjEMBOAXssYVf/YiPorUNpTupk1pUsq+vQrqUWRvM8PwQS/GcVbMCqmZQi3ELDChFuvmdwdpGW4xO7gTu1lAeixYt05kA38DiX0jFHC2chOk7+czrPR69AFVQLmYwvJMogvhSk0joTfBEmFdfrWXf02X4Twe98NhN56mB6N+fWU=&cssminify=yes' type='text/css' media='all' /> 498 499</head> 500 501<body class="archive author author-sinnema313 author-4041491 custom-background wp-embed-responsive wp-theme-pubindependent-publisher-2 customizer-styles-applied has-sidebar has-header-image jetpack-reblog-enabled custom-colors"> 502 503<div id="page" class="hfeed site"> 504 <a class="skip-link screen-reader-text" href="#content">Skip to content</a> 505 506 <div id="hero-header" class="site-hero-section"> 507 <img src="https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg" width="1440" height="600" alt="" class="hero-header-image" loading="eager" fetchpriority="high" srcset="https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg 1440w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=160&h=67 160w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=240&h=100 240w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=768&h=320 768w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=1024&
507h=427 1024w" sizes="(max-width: 1440px) 100vw, 1440px" /> <header id="masthead" class="site-header" role="banner"> 508 <div class="inner"> 509 <div class="site-branding"> 510 511 <p class="site-title"><a href="https://raysinnema.blog/" rel="home">Ray on Software Architecture</a></p> 512 <p class="site-description">Thinking Clearly About Code, Context, and Complexity</p> 513 </div><!-- .site-branding --> 514 515 516 517 </div><!-- .inner --> 518 </header><!-- #masthead --> 519 </div> 520 521 522 523 524 <div id="content-wrapper" class="content-wrapper"> 525 <div id="content" class="site-content"> 526 527 <div id="primary" class="content-area"> 528 <main id="main" class="site-main" role="main"> 529 530 531 <header class="page-header"> 532 <h1 class="page-title">Author: <span>Ray Sinnema</span></h1> </header><!-- .page-header --> 533 534 535 536<article id="post-8428" class="post-8428 post type-post status-publish format-standard hentry category-problem-solving tag-systems-thinking"> 537 <header class="entry-header"> 538 <h1 class="entry-title"><a href="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/" rel="bookmark">Solve problems with systems thinking</a></h1> </header><!-- .entry-header --> 539 <div class="entry-content"> 540 541<p class="wp-block-paragraph">There is a field called <a href="https://en.wikipedia.org/wiki/Systems_thinking">systems thinking</a> that reverses the reductive thinking most of us have become accustomed to. Some of its <a href="https://untools.co/systems-thinking/#tools">tools</a> can be quite helpful, like this one:</p> 542 543 544 545<figure class="wp-block-image size-large is-resized"><img data-attachment-id="8466" data-permalink="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg" data-orig-size="800,800" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"VectorMine | Dreamstime.com","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg?w=800" width="800" height="800" src="https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg" alt="" class="wp-image-8466" style="width:542px;height:auto" srcset="https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg 800w, https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg?w=160&h=160 160w, https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg?w=240&h=240 240w, https://raysinnema.blog/wp-content/uploads/2026/01/systems-thinking-iceberg-shows-events-patterns-structures-mental-models-doodle-style-diagram-376991823.jpg?w=768&h=768 768w" sizes="(max-width: 800px) 100vw, 800px" /></figure> 546 547 548 549<p class="wp-block-paragraph">Instead of reacting to events, the <a href="https://untools.co/iceberg-model/">iceberg model</a> encourages us to look deeper to get better results. Let’s apply it to the practice of deploying code changes to production for a B2B SaaS company:</p> 550 551 552 553<ul class="wp-block-list"> 554<li><strong>Event</strong>: Deployment on January 15th at 7 PM MST to production.</li> 555 556 557 558<li><strong>Pattern</strong>: Deployment to production every other Thursday evening.</li> 559 560 561 562<li><strong>Underlying structure</strong> (process): After a 2-week sprint ends on Wednesdays, we compile a release and send it to the Change Control Board (CCB) for approval. The CCB convenes on Thursdays. Once approved, we deploy to the demo and production environments during our evening downtime windows on Tuesdays and Thursdays.</li> 563 564 565 566<li><strong>Mental model</strong>: Deployments to production are risky, so we deploy outside of business hours, which gives us the time to test and roll back if we find anything wrong, all without impacting customers. But since we don’t like working outside business hours, we can’t do this too often. Aligning with our sprints seems like a good compromise.</li> 567</ul> 568 569 570 571<p class="wp-block-paragraph">Another useful systems thinking tool is a <a href="https://en.wikipedia.org/wiki/Causal_loop_diagram">causal loop diagram</a> (CLD), which shows how variables influence a system. Here’s one that visualizes the above mental model:</p> 572 573 574 575<figure class="wp-block-image size-large"><img data-attachment-id="8443" data-permalink="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/cld-deployment-11/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png" data-orig-size="721,61" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="cld-deployment-1(1)" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png?w=721" width="721" height="61" src="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png?w=721" alt="" class="wp-image-8443" srcset="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png 721w, https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-11.png?w=240 240w" sizes="(max-width: 721px) 100v
575w, 721px" /></figure> 576 577 578 579<p class="wp-block-paragraph">In a CLD, a <strong>+</strong> indicates a positive relationship between variables. For instance, deployment risk leads to more desire to deploy outside of business hours. A <strong>–</strong> indicates a negative relationship. For example, deploying outside of business hours reduces the desire to deploy.</p> 580 581 582 583<p class="wp-block-paragraph">When you visualize a mental model with a CLD, you can better see the assumptions that people make. This helps spot errors and omissions. For instance, deployment frequency is related to deployment risk via the size of the deployed changes:</p> 584 585 586 587<figure class="wp-block-image size-large"><img data-attachment-id="8452" data-permalink="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/cld-deployment-22/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png" data-orig-size="721,161" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="cld-deployment-2(2)" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png?w=721" width="721" height="161" src="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png?w=721" alt="" class="wp-image-8452" srcset="https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png 721w, https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2026/01/cld-deployment-22.png?w=240 240w" sizes="(max-width: 721px) 100vw, 721px" /></figure> 588 589 590 591<p class="wp-block-paragraph">This shows us that, while we set out to reduce deployment risk, the way we did that also ends up increasing it: less frequent deployments lead to more changes per deployment and thus to more risk per deployment. This addition shows why the diagram’s name contains “loop”: most systems contain loops.</p> 592 593 594 595<p class="wp-block-paragraph">It’s hard to tell from a simple diagram like this how conflicting effects combine. The example CLD doesn’t tell us whether the practice of deploying outside business hours actually reduces or increases deployment risk, but at least it begs the question.</p> 596 597 598 599<p class="wp-block-paragraph">Asking questions like that naturally leads to considering alternatives. Can we reduce deployment risk by adding more checks to our test automation? By gathering post-deployment metrics and automatically rolling the deployment back if they are worse than before the deployment? Etc. We can then visualize the (most promising) alternatives in the CLD and pick the best.</p> 600 601 602 603<p class="wp-block-paragraph">Note that with these tools, we no longer have to rely on authority, i.e. what others say are “best practices.” We can add in all the relations that are relevant <strong>in our specific context</strong> to reach a solution that works best <strong>for us</strong>, <strong>at this time</strong>.</p> 604 605 606 607<p class="wp-block-paragraph">As the example shows, systems thinking can help find good solutions to tricky problems. If you’d like to dig further, I can recommend <a href="https://www.amazon.com/Thinking-Systems-Donella-H-Meadows-ebook/dp/B005VSRFEA/ref=sr_1_1?crid=29BPAMO8MR1EG&dib=eyJ2IjoiMSJ9.xG56AEYJVPwm2PrfYOJ_HFqCMQqS0miAViG4DEVTqywuWcjAnsHKNvZtNvm75F_dH9ez6TcawfDlZpsh0wBjbozNHQ5iTjV741qg3xCAPdMFWi1rdTawd5ENRIprSA2fkAyLcqUAE2AjO63Qye4sIiqs-GQyHA4E2VtLrHmUfWC-wg4UtCD7EKEawP7Jwou6R7b5XhLhIsHqtG037tIRlSi9f024EWN2T5KBhW0SqCM.8ugYDT5PVHraYdkTsYENOSEtZ9w00ZagCh3iZ1hE6Gg&dib_tag=se&keywords=donella+meadows&qid=1768091285&sprefix=done%2Caps%2C210&sr=8-1">Thinking in systems</a> and <a href="https://www.amazon.com/Learning-Systems-Thinking-Esse
607ntial-Professionals-ebook/dp/B0D9KWZRT2/ref=sr_1_1?crid=2WRAI7FYVK7UE&dib=eyJ2IjoiMSJ9.UxUsh767EBqycyNVxerlN5a7jdVwgsET3rCXycEh8CQjDzz9TvbSP2bW5W2U-ZrNx3v4Wfw81VMLKjHKttfHZKKFLF2-sRjkMnygAR08AiCLZ1zSDbQgpPLhNgC-zmfqLLfgxa2hWQwyLh7CVlXu5C4cL8BcO69vD67B-8TM9ZnraGLs9Nas4p-RE9mRxDLm33p-RkD_NI5LohlYCao8fVFznHcBh-aeiYOPRkefMrY.yxaB8RGb9nds-bHewFLuDabX64ixou58Y6arLGlcLs4&dib_tag=se&keywords=learning+systems+thinking&qid=1768091349&sprefix=learning+systems+thinking+%2Caps%2C166&sr=8-1">Learning systems thinking</a>. Also, try out some of the <a href="https://bryanlindsley.com/systems-thinking-tools/">many tools</a>.</p> 608 </div><!-- .entry-content --> 609 610 <footer class="entry-footer"> 611 612 <div class="entry-meta"> 613 <span class="byline"> 614 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 615 <span class="cat-links"> 616 <a href="https://raysinnema.blog/category/problem-solving/" rel="category tag">Problem solving</a> </span><!-- .cat-links --> 617 618 <span class="comments-link"><a href="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/#respond">Leave a comment</a></span><!-- .comments-link --> 619 <span class="published-on"> 620 <a href="https://raysinnema.blog/2026/01/12/solve-problems-with-systems-thinking/" rel="bookmark"><time class="entry-date published" datetime="2026-01-12T05:00:00-07:00">2026-01-12</time><time class="updated" datetime="2026-01-11T15:51:04-07:00">2026-01-11</time></a> 621 </span> 622 623 <span class="word-count">2 Minutes</span> </div><!-- .entry-meta --> 624 </footer><!-- .entry-footer --> 625</article><!-- #post-## --> 626 627 628 629<article id="post-8327" class="post-8327 post type-post status-publish format-standard hentry category-architecture tag-conference tag-ddd"> 630 <header class="entry-header"> 631 <h1 class="entry-title"><a href="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/" rel="bookmark">Conference report: Explore DDD 2025</a></h1> </header><!-- .entry-header --> 632 <div class="entry-content"> 633 634<p class="wp-block-paragraph"><a href="https://exploreddd.com/">Explore DDD</a> is a two-day conference about <a href="https://en.wikipedia.org/wiki/Domain-driven_design">Domain-Driven Design</a> (DDD) in Denver, Colorado, that my employer <a href="https://www.ehs.com/">VelocityEHS</a> was kind enough to let me go to. Below, I give an impression of the sessions that I attended.</p> 635 636 637 638<h2 class="wp-block-heading">Wednesday</h2> 639 640 641 642<h2 class="wp-block-heading"><strong>Opening keynote: Diana Montalion</strong></h2> 643 644 645 646<p class="wp-block-paragraph">Diana spoke about how systems thinking can inform architecture. In <a href="https://en.wikipedia.org/wiki/Complex_adaptive_system">complex adaptive systems</a>, the relationships between the parts that make up the system are actually more important than the parts themselves, leading her to claim that <strong>“architecture is relationship therapy for software systems.”</strong></p> 647 648 649 650<p class="wp-block-paragraph">Because of non-linear dynamics, there are often leverage points in systems, where a small shift produces significant, lasting change. However, people won’t believe you if you find one, so it makes sense to let them discover patterns for themselves by building an easily accessible <a href="https://en.wikipedia.org/wiki/Knowledge_graph">knowledge graph</a>.</p> 651 652 653 654<h2 class="wp-block-heading"><strong>DDD & LLM brainstorming: Eric Evans</strong></h2> 655 656 657<div class="wp-block-image"> 658<figure class="alignleft"><img data-attachment-id="8376" data-permalink="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/ddd-linter/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png" data-orig-size="400,238" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="DDD linter" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png?w=400" loading="lazy" width="400" height="238" src="https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png" alt="Whiteboard discussion about DDD linter" class="wp-image-8376" srcset="https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png 400w, https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png?w=160&h=95 160w, https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png?w=240&
658h=143 240w" sizes="auto, (max-width: 400px) 100vw, 400px" /></figure> 659</div> 660 661 662<p class="wp-block-paragraph">Eric led a two-hour workshop where we explored how <a href="https://en.wikipedia.org/wiki/Large_language_model">LLMs</a> can help with DDD. We crowdsourced topics and discussed them in groups.</p> 663 664 665 666<p class="wp-block-paragraph">My group worked on the concept of a DDD <a href="https://en.wikipedia.org/wiki/Lint_(software)">linter</a>, which would work off a glossary to help team members use the <a href="https://en.wikipedia.org/wiki/Ubiquitous_language">ubiquitous language</a> properly.</p> 667 668 669 670<p class="wp-block-paragraph">The group set up a <a href="https://discord.gg/BwZJYxrP">Discord server</a> to continue after the conference, hopefully producing an actual tool at some point.</p> 671 672 673 674<h2 class="wp-block-heading"><strong>Strategies for Fearlessly Making Change Happen: Mary Lynn<a href="https://exploreddd.com/schedule/#"> </a>Manns</strong></h2> 675 676 677 678<p class="wp-block-paragraph">In this workshop, we got to play the <a href="https://fearlessjourney.info/">Fearless Journey</a> game. We started by defining the as-is and desired to-be states. Then we defined obstacles that may prevent us from reaching that goal. Each obstacle went into a bag and we took them out one by one in random order. We used one or more of the 60+ <a href="https://img1.wsimg.com/blobby/go/c83004a7-49d2-40b5-acc2-bc54eb8dbadb/downloads/FearlessChangeCardFronts_Sept2024-0001.pdf?ver=1736724748334">fearless change pattern cards</a> to attack the obstacle.</p> 679 680 681 682<p class="wp-block-paragraph">This session connected several dots for me, which I may write about later.</p> 683 684 685 686<h2 class="wp-block-heading"><strong>The EventStorming Process Modelling Rat Race: Alberto Brandolini & Paul Rayner</strong></h2> 687 688 689<div class="wp-block-image"> 690<figure class="alignright"><img data-attachment-id="8378" data-permalink="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/event-storming/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png" data-orig-size="274,400" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="event-storming" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png?w=274" loading="lazy" width="274" height="400" src="https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png" alt="" class="wp-image-8378" srcset="https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png 274w, https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png?w=110&h=160 110w, https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png?w=164&h=240 164w" sizes="auto, (max-width: 274px) 100vw, 274px" /></figure> 691</div> 692 693 694<p class="wp-block-paragraph">In this workshop I got to practice the process modeling variant of <a href="https://en.wikipedia.org/wiki/Event_storming">event storming</a> on the problem of selecting talks for a conference. It was set up as a competitive game, where teams worked on the problem in 3 rounds of 20 minutes, with scoring and small retrospectives.</p> 695 696 697 698<p class="wp-block-paragraph">I got to ask Alberto himself some in-depth questions about how he uses event storming in practice.</p> 699 700 701 702<h2 class="wp-block-heading">Thursday</h2> 703 704 705 706<h2 class="wp-block-heading"><strong>Systems Theory and Practice Applied to System Design: Ruth Malan</strong></h2> 707 708 709 710<p class="wp-block-paragraph">In this workshop, Ruth helped us apply some systems thinking tools to a toy problem. Examples are <a href="https://creately.com/guides/bubble-map-guide/">bubble diagrams</a> and <a href="https://www.presentlearning.com/concept-cards.html">concept cards</a>
710. One of the main lessons learned is to always design a system in its next larger context, e.g. a design a chair in a room, or a room in a house, since the system’s properties interact with the system’s context.</p> 711 712 713 714<p class="wp-block-paragraph">Another lesson is to record decisions are review them later, both when an issue arises and periodically. This helps see how decisions play out over time and fosters learning.</p> 715 716 717 718<h2 class="wp-block-heading"><strong>Teaching DDD – Facilitating Mindset Shifts: Tobias Goeschel</strong></h2> 719 720 721<div class="wp-block-image"> 722<figure class="alignleft"><img data-attachment-id="8379" data-permalink="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/facilitating-mindset-shift/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png" data-orig-size="400,358" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="facilitating-mindset-shift" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png?w=400" loading="lazy" width="400" height="358" src="https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png" alt="" class="wp-image-8379" srcset="https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png 400w, https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png?w=160&h=143 160w, https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png?w=240&h=215 240w" sizes="auto, (max-width: 400px) 100vw, 400px" /></figure> 723</div> 724 725 726<p class="wp-block-paragraph">In this workshop, Tobias led us through an interesting approach to solving the problem of introducing a new technique to people who may not care about the technique per se.</p> 727 728 729 730<p class="wp-block-paragraph">We first identified job roles of people we may want to influence and placed them on a diagram with two axes: engine room → c-suite and technical → non-technical. We identified the 3 top things these people care about and brainstormed facts about the technique that impact these things.</p> 731 732 733 734<p class="wp-block-paragraph">We then identified the tools that come with the technique, like ubiquitous language or context mapping for DDD, and placed these on a similar diagram. The combination of the two diagrams shows what tools are likely to be of use for a given role.</p> 735 736 737 738<h2 class="wp-block-heading"><strong>Learning from Incidents as Continuous Design: Eric Dobbs</strong></h2> 739 740 741 742<p class="wp-block-paragraph">In this workshop, Eric taught us about the <a href="https://www.learningfromincidents.io/">Learning From Incidents</a> (LFI) community. It’s easy to spot errors in hindsight, but it’s much more difficult to encourage insights. We like to attribute the “root cause” to human error, so we have someone to blame. It’s much harder to see how the system enabled the human to make that error, because <a href="https://sookocheff.com/post/systems/above-the-line-below-the-line/">we can’t see the system directly</a>, but only through representations.</p> 743 744 745 746<p class="wp-block-paragraph">After the theory, we applied some DDD techniques to the incident response process. I always love it when people combine different fields.</p> 747 748 749 750<h2 class="wp-block-heading"><strong>Closing keynote: <a href="https://exploreddd.com/schedule/#"></a>Rebecca Wirfs-Brock</strong></h2> 751 752 753<div class="wp-block-image"> 754<figure class="alignright"><img data-attachment-id="8401" data-permalink="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/screenshot-2025-04-23-at-7-04-10-am/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png" data-orig-size="254,70" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="Misspelled name" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png?w=254" loading="lazy" width="254" height="70" src="https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png" alt="Name with accent replaced by weird symbols, as sent by InnoQ." class="wp-image-8401" srcset="https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png 254w, https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png?w=160&h=44 160w, https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png?w=240&h=66 240w" sizes="auto, (max-width: 254px) 100vw, 254px" /></figure> 755</div> 756 757 758<p class="wp-block-paragraph">Rebecca talked about the various degrees of rigor a DDD model can have and how much rigor to apply in what situation. She gave examples of not enough rigor (Boeing 737 Max) and too much (limiting people’s names, like rejecting non-letter characters or characters with accents). Since my legal name is Rémon, I certainly could relate to the latter.</p> 759 760 761 762<h2 class="wp-block-heading">Conclusion</h2> 763 764 765 766<p class="wp-block-paragraph"><strong>I absolutely loved this conference!</strong> The hands-on sessions gave me something that’s hard to get anywhere else: practical application of ideas under the guidance of experts in the field. As many people can attest, reading about e.g. event storming is very different from actually doing it.</p> 767 768 769<div class="wp-block-image"> 770<figure class="alignleft"><img data-attachment-id="8377" data-permalink="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/eric-evans/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png" data-orig-size="338,400" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="Eric Evans" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png?w=338" loading="lazy" width="338" height="400" src="https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png" alt="Eric Evans and me" class="wp-image-8377" srcset="https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png 338w, https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png?w=135&h=160 135w, https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png?w=203&h=240 203w" sizes="auto, (max-width: 338px) 100vw, 338px" /></figure> 771</div> 772 773 774<p class="wp-block-paragraph">I also loved the networking opportunities. At lunch on Wednesday I sat next to the InfoQ editor for <a href="https://www.infoq.com/architecture-design/">Architecture & Design</a>, and on Thursday to µservices master <a href="https://microservices.io/about.html">Chris Richardson</a>. <em>(And the food was good too.)</em></p> 775 776 777 778<p class="wp-block-paragraph">The breaks in between sessions were 30 minutes, which gave me enough time to strike up some good conversations.
778</p> 779 780 781 782<p class="wp-block-paragraph">And to top it all of, Diana Montalion gave me a free, signed copy of her <a href="https://www.oreilly.com/library/view/learning-systems-thinking/9781098151324/">book</a>!</p> 783 784 785 786<p class="wp-block-paragraph">Many thanks to <a href="https://www.linkedin.com/in/thepaulrayner/">Paul Rayner</a> and team for organizing! Hopefully they can manage to put together another conference next year 🤞</p> 787 788 789 790<p class="wp-block-paragraph"></p> 791 </div><!-- .entry-content --> 792 793 <footer class="entry-footer"> 794 795 <div class="entry-meta"> 796 <span class="byline"> 797 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 798 <span class="cat-links"> 799 <a href="https://raysinnema.blog/category/architecture/" rel="category tag">architecture</a> </span><!-- .cat-links --> 800 801 <span class="comments-link"><a href="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/#respond">Leave a comment</a></span><!-- .comments-link --> 802 <span class="published-on"> 803 <a href="https://raysinnema.blog/2025/04/21/conference-report-explore-ddd-2025/" rel="bookmark"><time class="entry-date published" datetime="2025-04-21T13:00:00-06:00">2025-04-21</time><time class="updated" datetime="2025-04-23T15:07:49-06:00">2025-04-23</time></a> 804 </span> 805 806 <span class="word-count">4 Minutes</span> </div><!-- .entry-meta --> 807 </footer><!-- .entry-footer --> 808</article><!-- #post-## --> 809 810 811 812<article id="post-8314" class="post-8314 post type-post status-publish format-standard hentry category-architecture tag-architecture tag-hexagonal-architecture"> 813 <header class="entry-header"> 814 <h1 class="entry-title"><a href="https://raysinnema.blog/2025/03/25/better-names-for-hexagonal-architecture-inbound-outbound/" rel="bookmark">Better Names for Hexagonal Architecture: Inbound/Outbound</a></h1> </header><!-- .entry-header --> 815 <div class="entry-content"> 816 817<p class="wp-block-paragraph">I tend to remind myself (and others) that there is <a href="https://www.cs.dartmouth.edu/~cs50/Reading/NoSilverBullet.pdf">no silver bullet</a>, and claims about universal applicability of certain patterns must be approached with a default attitude of skepticism.</p> 818 819 820 821<p class="wp-block-paragraph">One of the few exceptions is the <a href="https://www.amazon.com/Hexagonal-Architecture-Explained-Alistair-Cockburn-ebook/dp/B0D4JQJ8KD/ref=sr_1_1?crid=WSZOBWNMCOSL&dib=eyJ2IjoiMSJ9.z3yeBZuTfcjY0_hLBOp8ruD2TUB2SB6Bbxpe_YpgNaoyztZAns_1mRJsYZrTZTdyII4mcwVjpoDdsUQmnlqPLUEaZ7uNMkdAO-UPdaDngFdH19mIo0FXfxgbac-h7UCkJQ1bczpftB7Xffh5MnBLxN2N0AyJdtZsCa_TrWHhnW4dVV4YQ9c2UWs7XMPoGac4DWPIlAxvyLqG8PfJ3UCOutE5lPVvK1FVZL_67RAWUOQ.PWrP-4sXXTyvK0fxSFzjz90xWhlEXLAJUgddgkkrVao&dib_tag=se&keywords=hexagonal+architecture&qid=1742768309&
821sprefix=hexagonal+ar%2Caps%2C165&sr=8-1">hexagonal architecture</a>, aka the Ports & Adapters pattern, discovered by <a href="https://www.linkedin.com/in/alistaircockburn/">Dr. Alistair Cockburn</a>. He claims that most teams face a risk of technology change and that the pattern protects against this risk at very low cost.</p> 822 823 824 825<figure class="wp-block-image size-large"><img data-attachment-id="8320" data-permalink="https://raysinnema.blog/2025/03/25/better-names-for-hexagonal-architecture-inbound-outbound/image-2/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2025/03/image.png" data-orig-size="809,575" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="image" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2025/03/image.png?w=809" loading="lazy" width="809" height="575" src="https://raysinnema.blog/wp-content/uploads/2025/03/image.png?w=809" alt="" class="wp-image-8320" srcset="https://raysinnema.blog/wp-content/uploads/2025/03/image.png 809w, https://raysinnema.blog/wp-content/uploads/2025/03/image.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2025/03/image.png?w=240 240w, https://raysinnema.blog/wp-content/uploads/2025/03/image.png?w=768 768w" sizes="auto, (max-width: 809px) 100vw, 809px" /></figure> 826 827 828 829<p class="wp-block-paragraph">I couldn’t agree more. This pattern has made it easy for me to switch technologies in the past. One event-driven application I worked on went from on-premise using Kafka to GCP with Pub/Sub, and finally to AWS with SNS & SQS, all in the span of 6 years. These migrations were relatively painless because we used the Ports & Adapters pattern.</p> 830 831 832 833<p class="wp-block-paragraph">Although I love this pattern and would recommend it for most code bases, I do have one gripe with the terminology used.</p> 834 835 836 837<p class="wp-block-paragraph">We like to say that naming things is one of the <a href="https://martinfowler.com/bliki/TwoHardThings.html">two hard problems</a> in software development, but there is in fact solid guidance on what makes good names. <a href="https://www.amazon.com/Naming-Things-Hardest-Software-Engineering-ebook/dp/B0BT15GFZG/ref=sr_1_1?crid=1I4AOKSBJ1WWU&dib=eyJ2IjoiMSJ9.IjcD2WKpMl-96TikVHSpho0j0cajWEBku8PZ3uFevIS80cDYvKDA90OW6jeTsk91V8grKgjXjNjhM5OUUoFdTLEJ3SXfpk5IoAJdtURoPZNXvMGYwSsbYg82zgcJPel5y_sAYr4FedkWr7ISCOUXXWf4vLPfI4ZJBP8LOyM4BKobOQ3c0hJCWB_UvurrNCFnI3wdHwWSSNvKmLVN2TtSlOiq9PaAL-IZ0fvpHUi_llY.gq-Np7jd9YqRcH9yVUKRpjQ3QZLWmsvjcQvxyG-7dqc&dib_tag=se&keywords=naming+things+book&qid=1742770782&sprefix=naming+thin%2Caps%2C157&sr=8-1">Naming Things</a> states that a good name has both high comprehension (it can be understood quickly) and high recall (it can be remembered easily).</p> 838 839 840 841<p class="wp-block-paragraph">One of the criteria is distinguishability: a name shouldn’t be easily confused with another name. For instance, names that differ in only one or two characters are difficult to distinguish.</p> 842 843 844 845<p class="wp-block-paragraph">This is where the Ports and Adapters <a href="https://alistair.cockburn.us/hexagonal-architecture/">pattern description</a> fails: <strong>driver</strong> and <strong>driven</strong> look too much alike. </p> 846 847 848 849<p class="wp-block-paragraph">My proposal is to use the following two terms instead: <strong>inbound</strong> and <strong>outbound.</strong> These terms are much easier to distinguish, and they capture the essence of the pattern: there is a separation between the application and the external world and the communication between the application and the external world falls into two categories: world → application (inbound) and application → world (outbound).</p> 850 851 852 853<p class="wp-block-paragraph">Has confusing terminology ever been a barrier to your team adopting useful architectural patterns? I’d love to hear your experiences in the comments.</p> 854 </div><!-- .entry-content --> 855 856 <footer class="entry-footer"> 857 858 <div class="entry-meta"> 859 <span class="byline">
860 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 861 <span class="cat-links"> 862 <a href="https://raysinnema.blog/category/architecture/" rel="category tag">architecture</a> </span><!-- .cat-links --> 863 864 <span class="comments-link"><a href="https://raysinnema.blog/2025/03/25/better-names-for-hexagonal-architecture-inbound-outbound/#respond">Leave a comment</a></span><!-- .comments-link --> 865 <span class="published-on"> 866 <a href="https://raysinnema.blog/2025/03/25/better-names-for-hexagonal-architecture-inbound-outbound/" rel="bookmark"><time class="entry-date published" datetime="2025-03-25T13:00:00-06:00">2025-03-25</time><time class="updated" datetime="2025-03-24T00:54:30-06:00">2025-03-24</time></a> 867 </span> 868 869 <span class="word-count">1 Minute</span> </div><!-- .entry-meta --> 870 </footer><!-- .entry-footer --> 871</article><!-- #post-## --> 872 873 874 875<article id="post-8293" class="post-8293 post type-post status-publish format-standard hentry category-software-development category-uncategorized tag-documentation tag-requirements"> 876 <header class="entry-header"> 877 <h1 class="entry-title"><a href="https://raysinnema.blog/2024/03/04/event-storming-icons/" rel="bookmark">Event storming icons</a></h1> </header><!-- .entry-header --> 878 <div class="entry-content"> 879 <div class="wp-block-image"> 880<figure class="alignleft size-large is-resized"><img data-attachment-id="8308" data-permalink="https://raysinnema.blog/2024/03/04/event-storming-icons/image/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2024/03/image.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="image" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=1024" loading="lazy" width="1024" height="1024" src="https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=1024" alt="" class="wp-image-8308" style="width:130px;height:auto" srcset="https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=1024 1024w, https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=240 240w, https://raysinnema.blog/wp-content/uploads/2024/03/image.png?w=768 768w, https://raysinnema.blog/wp-content/uploads/2024/03/image.png 1200w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure> 881</div> 882 883 884<p class="wp-block-paragraph">Most engineering disciplines specialize around a domain. Engineers trained in that field speak the same language as the people requesting them to build a system. In contrast, software developers need to learn the language of the domain. This makes it harder to elicit requirements.</p> 885 886 887 888<p class="wp-block-paragraph">Subject-matter experts (SMEs), by definition, are experts. They’ve accumulated a lot of knowledge over a long period of time. It’s hard for them to think back to when they didn’t have all that knowledge. This makes it hard for them to know what to explain or not, and even what to mention at all. And since the business analyst is new to the domain, they don’t know what questions to ask. The result is an iterative process that takes a lot of time to get things right.</p> 889 890 891 892<p class="wp-block-paragraph">Worse, it’s uncommon for SMEs to be experts in the entire domain. More often, multiple SMEs each have a clear picture of one part of the process and nobody of the whole. This results in conflicting points of view, which need resolution before building software. However, it takes a while before the analyst knows enough to ask the hard questions and bring conflicts into the open.</p> 893 894 895 896<p class="wp-block-paragraph"><a href="https://ziobrando.blogspot.com/2013/11/introducing-event-storming.html">Event storming</a> is a technique that solves these issues. It’s a <a href="https://blog.redelastic.com/corporate-arts-crafts-modelling-reactive-systems-with-event-storming-73c6236f5dd7">workshop</a> where the key stakeholders work together to build up a consistent picture of the entire process under consideration.</p> 897 898 899 900<p class="wp-block-paragraph">In event storming, the SMEs perform the integration of various perspectives rather than the business analyst. By giving them a standard notation, non-expert
900s can follow what they’re doing and force them to be precise. It allows them to ask the hard questions and bring conflicts out for resolution. Everybody’s learning compresses while the domain model emerges as a natural byproduct.</p> 901 902 903 904<p class="wp-block-paragraph">Event storming use the following concepts:</p> 905 906 907 908<ul class="wp-block-list"> 909<li>A <strong>domain event</strong> is anything that happens that’s of interest to an SME. <em>(orange)</em></li> 910 911 912 913<li>A <strong>command</strong> triggers an event. <em>(blue)</em></li> 914 915 916 917<li>An <strong>aggregate</strong> accepts commands and emits events. <em>(yellow)</em></li> 918 919 920 921<li>A <strong>policy</strong> contains the decision on how to react to an event. <em>(purple)</em></li> 922 923 924 925<li>A <strong>read model</strong> holds the information necessary to make a decision. <em>(green)</em></li> 926 927 928 929<li>A <strong>person</strong> is a human being responsible for a given decision. <em>(yellow)</em></li> 930 931 932 933<li>An <strong>external system</strong> is another system that interacts with the system under consideration. <em>(pink)</em></li> 934</ul> 935 936 937 938<p class="wp-block-paragraph">In an event storming workshop, sticky notes of a particular color represent each of these concepts. Workshop participants place the stickies on a wall in timeline order to visualize the business process.</p> 939 940 941 942<p class="wp-block-paragraph">A specific <a href="https://medium.com/@ziobrando/collaborative-process-modelling-with-eventstorming-17ed363650c0">grammar</a> governs event storming concepts, in the sense that certain items always come before or after others. It’s this grammar that allows people who aren’t domain experts to ask intelligent questions, like what command leads to this event, and who issues it?</p> 943 944 945 946<p class="wp-block-paragraph">I like event storming, because it employs useful concepts, like domain events, that are non-technical and yet naturally map to technical concepts, like messages published to a message broker. As such, the output of the workshop is useful long after the workshop is over.</p> 947 948 949 950<p class="wp-block-paragraph">That raises the question of how best to use that output. During the workshop, using stickies makes a lot of sense to facilitate interaction. Afterwards, however, a bit more formal notation would be nice.</p> 951 952 953 954<p class="wp-block-paragraph">I created a <a href="https://github.com/setms/book/tree/main/docs/img/event-storming-building-blocks">set of icons</a> that represent the event storming concepts. These icons maintain the colors of the stickies, but add symbols to visually represent the concepts. Here’s the event storming grammar visualized using these icons:</p> 955 956 957 958<figure class="wp-block-image size-large"><img data-attachment-id="8301" data-permalink="https://raysinnema.blog/2024/03/04/event-storming-icons/event-storming-grammar-1/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png" data-orig-size="822,372" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="event-storming-grammar-1" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png?w=822" loading="lazy" width="822" height="372" src="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png?w=822" alt="" class="wp-image-8301" srcset="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png 822w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png?w=240 240w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-1.png?w=768 768w" sizes="auto, (max-width: 822px) 100vw, 822px" /></figure> 959 960 961 962<figure class="wp-block-image size-large"><img data-attachment-id="8304" data-permalink="https://raysinnema.blog/2024/03/04/event-storming-icons/event-storming-grammar-2/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-2.png" data-orig-size="204,102" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="event-storming-grammar-2" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-2.png?w=204" loading="lazy" width="204" height="102" src="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-2.png?w=204" alt="" class="wp-image-8304" srcset="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-2.png 204w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-2.png?w=160 160w" sizes="auto, (max-width: 204px) 100vw, 204px" /></figure> 963 964 965 966<figure class="wp-block-image size-large"><img data-attachment-id="8306" data-permalink="https://raysinnema.blog/2024/03/04/event-storming-icons/event-storming-grammar-3/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png" data-orig-size="345,332" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="event-storming-grammar-3" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png?w=345" loading="lazy" width="345" height="332" src="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png?w=345" alt="" class="wp-image-8306" srcset="https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png 345w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2024/03/event-storming-grammar-3.png?w=240 240w" sizes="auto, (max-width: 345px) 100v
966w, 345px" /></figure> 967 968 969 970<p class="wp-block-paragraph">I’ve used these icons to document processes in a way that both techies and non-techies can follow. The icons are released under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons By Attribution 4.0 International</a> license, so feel free to use them in your own work.</p> 971 </div><!-- .entry-content --> 972 973 <footer class="entry-footer"> 974 975 <div class="entry-meta"> 976 <span class="byline"> 977 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 978 <span class="cat-links"> 979 <a href="https://raysinnema.blog/category/software-development/" rel="category tag">Software Development</a>, <a href="https://raysinnema.blog/category/uncategorized/" rel="category tag">Uncategorized</a> </span><!-- .cat-links --> 980 981 <span class="comments-link"><a href="https://raysinnema.blog/2024/03/04/event-storming-icons/#respond">Leave a comment</a></span><!-- .comments-link --> 982 <span class="published-on"> 983 <a href="https://raysinnema.blog/2024/03/04/event-storming-icons/" rel="bookmark"><time class="entry-date published" datetime="2024-03-04T13:00:00-07:00">2024-03-04</time><time class="updated" datetime="2024-03-02T22:43:21-07:00">2024-03-02</time></a> 984 </span> 985 986 <span class="word-count">2 Minutes</span> </div><!-- .entry-meta --> 987 </footer><!-- .entry-footer --> 988</article><!-- #post-## --> 989 990 991 992<article id="post-8233" class="post-8233 post type-post status-publish format-standard hentry category-software-development tag-performance tag-tdd"> 993 <header class="entry-header"> 994 <h1 class="entry-title"><a href="https://raysinnema.blog/2024/01/22/performance-and-tdd/" rel="bookmark">Performance and TDD</a></h1> </header><!-- .entry-header --> 995 <div class="entry-content"> 996 997<p class="wp-block-paragraph">TDD works wonders for developing code that meets functional requirements. But what about non-functional requirements? Let’s take a look at one that most users care about: performance.</p> 998 999 1000 1001<p class="wp-block-paragraph">Most TDD examples are necessarily small, so that the author can see the process through to completion. That reduces the chances for intricacies like performance aspects. But it’s not impossible. Let’s try this with the <a href="https://codingdojo.org/kata/WordWrap/">WordWrap kata</a>, where we have to ensure lines aren’t too long by inserting newline characters in appropriate places.</p> 1002 1003 1004 1005<p class="wp-block-paragraph">As usual, we start with input validation:</p> 1006 1007 1008<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1009class WhenWrappingWords { 1010 1011 @Test 1012 void shouldHandleEmptyText() { 1013 assertWrap(null, ""); 1014 } 1015 1016 private void assertWrap(String text, String expected) { 1017 assertThat(text, Wrapper.wrap(text, 5), is(expected)); 1018 } 1019 1020} 1021</pre></div> 1022 1023 1024<p class="wp-block-paragraph">Which is easy enough:</p> 1025 1026 1027<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1028public class Wrapper { 1029 1030 public static String wrap(String text, int length) { 1031 return ""; 1032 } 1033 1034} 1035</pre></div> 1036 1037 1038<p class="wp-block-paragraph">Next, the degenerate case, where the text doesn’t require newlines:</p> 1039 1040 1041<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1042 @Test 1043 void shouldNotWrapShortText() { 1044 assertWrap("short", "short"); 1045 } 1046</pre></div> 1047 1048<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1049 public static String wrap(String text, int length) { 1050 if (text == null) { 1051 return ""; 1052 } 1053 return text; 1054 } 1055</pre></div> 1056 1057 1058<p class="wp-block-paragraph">Now we get to the meat: longer texts require wrapping:</p> 1059 1060 1061<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1062 @Test 1063 void shouldWrapLongText() { 1064 assertWrap("toolong", "toolo\nng"); 1065 } 1066</pre></div> 1067 1068<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1069 private static final char NL = '\n'; 1070 1071 public static String wrap(String text, int length) { 1072 if (text == null) { 1073 return ""; 1074 } 1075 if (text.length() <= length) { 1076 return text; 1077 } 1078 return text.substring(0, length) + NL 1079 + text.substring(length); 1080 } 1081</pre></div> 1082 1083 1084<p class="wp-block-paragraph">But, if possible, we should wrap at word boundaries rather than
1084in the middle of a word:</p> 1085 1086 1087<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1088 @Test 1089 void shouldPreferToWrapAtWhitespace() { 1090 assertWrap("too long", "too\nlong"); 1091 } 1092</pre></div> 1093 1094<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1095 public static String wrap(String text, int length) { 1096 if (text == null) { 1097 return ""; 1098 } 1099 if (text.length() <= length) { 1100 return text; 1101 } 1102 var index = text.lastIndexOf(' ', length); 1103 if (index < 0) { 1104 return text.substring(0, length) + NL 1105 + text.substring(length); 1106 } 1107 return text.substring(0, index) + NL 1108 + text.substring(index + 1); 1109 } 1110</pre></div> 1111 1112 1113<p class="wp-block-paragraph">And finally, we should wrap into multiple lines if needed:</p> 1114 1115 1116<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1117 @Test 1118 void shouldWrapVeryLongTextMultipleTimes() { 1119 assertWrap("toolongtext", "toolo\nngtex\nt"); 1120 assertWrap("too long text", "too\nlong\ntext"); 1121 } 1122</pre></div> 1123 1124<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1125 public static String wrap(String text, int length) { 1126 if (text == null) { 1127 return ""; 1128 } 1129 if (text.length() <= length) { 1130 return text; 1131 } 1132 var index = text.lastIndexOf(' ', length); 1133 if (index < 0) { 1134 return text.substring(0, length) + NL 1135 + wrap(text.substring(length), length); 1136 } 1137 return text.substring(0, index) + NL 1138 + wrap(text.substring(index + 1), length); 1139 } 1140</pre></div> 1141 1142 1143<p class="wp-block-paragraph">Which we can clean up a bit:</p> 1144 1145 1146<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1147 public static String wrap(String text, int length) { 1148 if (text == null) { 1149 return ""; 1150 } 1151 if (text.length() <= length) { 1152 return text; 1153 } 1154 var index = text.lastIndexOf(' ', length); 1155 var skip = 1; 1156 if (index < 0) { 1157 index = length; 1158 skip = 0; 1159 } 1160 return text.substring(0, index) + NL 1161 + wrap(text.substring(index + skip), length); 1162 } 1163</pre></div> 1164 1165 1166<p class="wp-block-paragraph">Now let’s consider the performance of this code. Can we use it to format a book? A novel has around 100,000 words and an English word consists of 5.1 letters on average. Let’s say we want to wrap lines at 80 characters:</p> 1167 1168 1169<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1170 private static final int NUM_WORDS_IN_BOOK = 100_000; 1171 private static final float AVG_NUM_CHARS_PER_WORD = 5.1f; 1172 private static final int MAX_BOOK_LINE_LENGTH = 80; 1173 private static final int NUM_TRIES = 10; 1174 private static final float MAX_WRAPPING_MS = 1000; 1175 1176 private final Random random = new SecureRandom(); 1177 1178 @Test 1179 void shouldWrapBook() { 1180 var time = 0; 1181 for (var i = 0; i < NUM_TRIES; i++) { 1182 var text = randomStringOfBookLength(); 1183 var start = System.currentTimeMillis(); 1184 Wrapper.wrap(text, MAX_BOOK_LINE_LENGTH); 1185 var stop = System.currentTimeMillis(); 1186 time += stop - start; 1187 } 1188 assertThat(1.0f * time / NUM_TRIES, 1189 lessThanOrEqualTo(MAX_WRAPPING_MS)); 1190 } 1191 1192 private String randomStringOfBookLength() { 1193 var numCharsInBook = (int) (NUM_WORDS_IN_BOOK * ( 1 + AVG_NUM_CHARS_PER_WORD)); 1194 var result = new StringBuilder(numCharsInBook); 1195 for (var i = 0; i < numCharsInBook; i++) { 1196 result.append(randomChar()); 1197 } 1198 return result.toString(); 1199 } 1200 1201 private char randomChar() { 1202 if (random.nextFloat() < 1.0 / (1 + AVG_NUM_CHARS_PER_WORD)) { 1203 return ' '; 1204 } 1205 return (char) (random.nextInt(26) + 'a'); 1206 } 1207</pre></div> 1208 1209 1210<p class="wp-block-paragraph">Normally, you’d use the <a href="https://github.com/openjdk/jmh">Java Microbenchmark Harness</a> to investigate the performance of an algorithm like this. I don’t want to introduce new tech for this already long post, however, so this test will have to do. Note that we have to run multiple tries, since we’re using randomness.</p> 1211 1212 1213 1214<p class="wp-block-paragraph">Running this test gives a stack overflow, so clearly we need to do something about that.</p> 1215 1216 1217 1218<p class="wp-block-paragraph">In this case, it would be easy to replace the recursion with a <code>while</code> loop, so we could just go do that and see the test pass. In the real world, however, things usually aren’t that simple.</p> 1219 1220 1221 1222<p class="wp-block-paragraph">This is where the <a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy pattern</a> can come in handy. With multiple implementations of the strategy interface, we can run our tests against all of them. We can develop alternative implementations from scratch, using TDD, or copy some code into a
1222new implementation and start modifying it. Once we’re satisfied with the results, we can keep the best implementation and remove the others.</p> 1223 1224 1225 1226<p class="wp-block-paragraph">But hang on, we used TDD to get to this implementation, so how is doing that again going to give us a different result?</p> 1227 1228 1229 1230<p class="wp-block-paragraph">Well, when we did it the first time, we weren’t focused on performance. We shouldn’t have been, since <a href="https://en.wikiquote.org/wiki/Donald_Knuth#Computer_Programming_as_an_Art_(1974)">premature optimization is the root of all evil</a>. Now that we have <strong>proof</strong> that our performance isn’t good enough, things are different. Let’s see how that plays out.</p> 1231 1232 1233 1234<p class="wp-block-paragraph">The implementation of the first two tests can remain the same:</p> 1235 1236 1237<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1238 public static String wrap(String text, int length) { 1239 if (text == null) { 1240 return ""; 1241 } 1242 return text; 1243 } 1244</pre></div> 1245 1246 1247<p class="wp-block-paragraph">To make <code>shouldWrapLongText()</code> pass, we need to pay more attention to performance this time. We don’t want to use <code>substring()</code> and add two <code>String</code>s together, since that involves copying characters. So let’s use a <code>StringBuilder</code> instead:</p> 1248 1249 1250<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1251 public static String wrap(String text, int length) { 1252 if (text == null) { 1253 return ""; 1254 } 1255 var result = new StringBuilder(text); 1256 if (result.length() > length) { 1257 result.insert(length, NL); 1258 } 1259 return result.toString(); 1260 } 1261</pre></div> 1262 1263 1264<p class="wp-block-paragraph">This still means we have to copy some arrays around to make room for the newline. We can avoid that by allocating enough capacity from the start:</p> 1265 1266 1267<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1268 public static String wrap(String text, int length) { 1269 if (text == null) { 1270 return ""; 1271 } 1272 var capacity = text.length() + text.length() / length; 1273 var result = new StringBuilder(capacity); 1274 result.append(text); 1275 if (result.length() > length) { 1276 result.insert(length, NL); 1277 } 1278 return result.toString(); 1279 } 1280</pre></div> 1281 1282 1283<p class="wp-block-paragraph">This would normally be looking ahead a bit too much for my taste, but since we already implemented the algorithm once, we know for sure we’re going to need this, so I’m cool with it.</p> 1284 1285 1286 1287<p class="wp-block-paragraph">Next let’s make <code>shouldPreferToWrapAtWhitespace()</code> pass:</p> 1288 1289 1290<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1291 public static String wrap(String text, int length) { 1292 if (text == null) { 1293 return ""; 1294 } 1295 var result = new StringBuilder(text.length() + text.length() / length); 1296 result.append(text); 1297 if (result.length() > length) { 1298 var spaceIndex = text.lastIndexOf(' ', length); 1299 if (spaceIndex < 0) { 1300 result.insert(length, NL); 1301 } else { 1302 result.setCharAt(spaceIndex, NL); 1303 } 1304 } 1305 return result.toString(); 1306 } 1307</pre></div> 1308 1309 1310<p class="wp-block-paragraph">Finally, we can generalize the <code>if</code> to a <code>while</code> to make the last test pass:</p> 1311 1312 1313<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1314 public static String wrap(String text, int length) { 1315 if (text == null) { 1316 return ""; 1317 } 1318 var capacity = text.length() + text.length() / length; 1319 var result = new StringBuilder(capacity); 1320 result.append(text); 1321 var columnEnd = length; 1322 while (columnEnd < result.length()) { 1323 var spaceIndex = result.lastIndexOf(" ", columnEnd); 1324 if (spaceIndex < columnEnd - length) { 1325 result.insert(columnEnd, NL); 1326 columnEnd += length + 1; 1327 } else { 1328 result.setCharAt(spaceIndex, NL); 1329 columnEnd = spaceIndex + 1 + length; 1330 } 1331 } 1332 return result.toString(); 1333 } 1334</pre></div> 1335 1336 1337<p class="wp-block-paragraph">This passes all our tests, including the one about performance.</p> 1338 1339 1340 1341<p class="wp-block-paragraph">The above do-over may seem like a wasteful approach: why wouldn’t we do it “right” from the start? Like I said earlier, we didn’t because we didn’t know that our implementation wasn’t going to perform well. But what if we did know from the start that performance was important?</p> 1342 1343 1344 1345<p class="wp-block-paragraph">We could’ve written our tests in a different order, tackling the test for performance earlier in the process. That would’ve prevented us from getting to green with recursion in this example, saving us a bit of time. In a real-world scenario, it might have saved a lot more time. Yet again, we see that the order of tests is important.</p> 1346 1347 1348 1349<p class="wp-block-paragraph">I would argue, however, that not much time was lost with the initial approach. I still believe that the proper order is <a href="https://kidneybone.com/c2/wiki/MakeItWorkMakeItRightMakeItFast">make it pass, make it right, make it fast</a>. One of the reasons TDD works so well is the explicit distinction between making the test green and then refactoring. Doing one thing at a time is solid advice when it comes to addressing performance as well.</p> 1350 1351 1352 1353<p class="wp-block-paragraph">I’ll accept a little bit of rework, knowing that I’ll win back that time and more in all the cases where the “right” solution is also fast enough and I don’t waste time on premature optimization.</p> 1354 </div><!-- .entry-content --> 1355 1356 <footer class="entry-footer"> 1357 1358 <div class="entry-meta"> 1359 <span class="byline">
1360 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 1361 <span class="cat-links"> 1362 <a href="https://raysinnema.blog/category/software-development/" rel="category tag">Software Development</a> </span><!-- .cat-links --> 1363 1364 <span class="comments-link"><a href="https://raysinnema.blog/2024/01/22/performance-and-tdd/#respond">Leave a comment</a></span><!-- .comments-link --> 1365 <span class="published-on"> 1366 <a href="https://raysinnema.blog/2024/01/22/performance-and-tdd/" rel="bookmark"><time class="entry-date published" datetime="2024-01-22T13:00:00-07:00">2024-01-22</time><time class="updated" datetime="2024-01-21T01:41:14-07:00">2024-01-21</time></a> 1367 </span> 1368 1369 <span class="word-count">5 Minutes</span> </div><!-- .entry-meta --> 1370 </footer><!-- .entry-footer --> 1371</article><!-- #post-## --> 1372 1373 1374 1375<article id="post-8209" class="post-8209 post type-post status-publish format-standard hentry category-software-development tag-agile"> 1376 <header class="entry-header"> 1377 <h1 class="entry-title"><a href="https://raysinnema.blog/2023/12/22/sprint-considered-harmful/" rel="bookmark">Sprint considered harmful</a></h1> </header><!-- .entry-header --> 1378 <div class="entry-content"> 1379 1380<p class="wp-block-paragraph">At this time of the year, many people like to slow down and reflect. It seems as good a time as any then, to take offense at the word “sprint” in the context of software development.</p> 1381 1382 1383 1384<p class="wp-block-paragraph">I firmly believe that <strong>words have meaning</strong>, <a href="https://martinfowler.com/bliki/SemanticDiffusion.html">semantic diffusion</a> be damned. Merriam-Webster defines <a href="https://www.merriam-webster.com/dictionary/sprint">sprint</a> as “to run or go at top speed especially for a short distance”. Most software development doesn’t go just “a short distance”. And as far as I know, nobody ever won a marathon by running 422 consecutive 100m sprints. So the sprint analogy breaks down pretty badly.</p> 1385 1386 1387 1388<p class="wp-block-paragraph">The marathon analogy isn’t any better, however. Runners often hit a wall between 30 and 35 kilometers, also known as the man with the hammer. This is due to depletion of glycogen stored in the muscles, which forces the body to transition to alternative energy sources, like breaking down fat. Since this is much less efficient, the runner’s body struggles to maintain the same level of performance.</p> 1389 1390 1391 1392<p class="wp-block-paragraph">The equivalent in software development is known as a <a href="https://en.wikipedia.org/wiki/Death_march_(project_management)">death march</a>. The team is pushed to their limits at the expense of work-life balance, the well-being of its members, and the quality of the work they produce.</p> 1393 1394 1395 1396<p class="wp-block-paragraph">This isn’t a good model for what we want to happen. We want a <a href="http://www.extremeprogramming.org/rules/overtime.html">sustainable pace</a> that developers can keep up for as long as it takes to complete the project. We need them sharp to do their best work. Sleep deprived or stressed out people don’t perform well and software development is hard enough as it is.</p> 1397 1398 1399 1400<p class="wp-block-paragraph">So <strong>let’s not talk about sprints anymore</strong>, shall we?</p> 1401 1402 1403 1404<p class="wp-block-paragraph">What then, is a good alternative?</p> 1405 1406 1407 1408<p class="wp-block-paragraph">Well, the word “sprint” is used in software development in the context of Agile methods, in particular Scrum. Agile methods are iterative in nature: they split up delivery into <a href="http://www.extremeprogramming.org/rules/releaseoften.html">small pieces</a>. The word iterative comes from <strong>iteration,</strong> which is exactly the <a href="http://www.extremeprogramming.org/rules/iterative.html">word</a> that eXtreme Programming and other Agile methods use instead of “sprint”. Turning to Merriam-Webster again, we find that <a href="https://www.merriam-webster.com/dictionary/iteration">iteration</a> is “a procedure in which repetition of a sequence of operations yields results successively closer to a desired result.” That sounds about right.</p> 1409 1410 1411 1412<p class="wp-block-paragraph"><strong>Exercise for the reader</strong>: What’s wrong with the phrase “best practices?” (And why do we always seem to need more than one?) Hint: look up the <a href="https://en.wikipedia.org/wiki/Cynefin_framework">Cynefin framework</a>.</p> 1413 </div><!-- .entry-content --> 1414 1415 <footer class="entry-footer"> 1416 1417 <div class="entry-meta"> 1418 <span class="byline">
1419 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 1420 <span class="cat-links"> 1421 <a href="https://raysinnema.blog/category/software-development/" rel="category tag">Software Development</a> </span><!-- .cat-links --> 1422 1423 <span class="comments-link"><a href="https://raysinnema.blog/2023/12/22/sprint-considered-harmful/#respond">Leave a comment</a></span><!-- .comments-link --> 1424 <span class="published-on"> 1425 <a href="https://raysinnema.blog/2023/12/22/sprint-considered-harmful/" rel="bookmark"><time class="entry-date published" datetime="2023-12-22T17:00:00-07:00">2023-12-22</time><time class="updated" datetime="2023-12-22T16:29:26-07:00">2023-12-22</time></a> 1426 </span> 1427 1428 <span class="word-count">2 Minutes</span> </div><!-- .entry-meta --> 1429 </footer><!-- .entry-footer --> 1430</article><!-- #post-## --> 1431 1432 1433 1434<article id="post-8145" class="post-8145 post type-post status-publish format-standard hentry category-software-development tag-tdd"> 1435 <header class="entry-header"> 1436 <h1 class="entry-title"><a href="https://raysinnema.blog/2023/12/12/canon-tdd-example-roman-numerals/" rel="bookmark">Canon TDD example: Roman numerals</a></h1> </header><!-- .entry-header --> 1437 <div class="entry-content"> 1438 1439<p class="wp-block-paragraph">People continue to misunderstand <a href="https://www.agilealliance.org/glossary/tdd/">Test-Driven Development</a> (TDD). Kent Beck, who wrote <a href="https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530">the book</a> on it, recently published an article called <a href="https://tidyfirst.substack.com/p/canon-tdd">Canon TDD</a>, which aims to remove some of those misunderstandings. This post shows an example.</p> 1440 1441 1442 1443<p class="wp-block-paragraph">Suppose you need Roman numerals in your application. Maybe you want to show years that way, or the hours of a clock, or the titles of Superbowl games. The problem we’ll look at is to calculate a string that represents the Roman representation of a number. We’ll solve that problem using Canon TDD.</p> 1444 1445 1446 1447<p class="wp-block-paragraph">The first step is to write a list of test scenarios we want to cover. We use Wikipedia as a <a href="https://en.wikipedia.org/wiki/Roman_numerals">reference</a> to compile this list:</p> 1448 1449 1450 1451<ol class="wp-block-list"> 1452<li>Input validation: Roman numerals can’t express numbers smaller than 1 or bigger than 3999.</li> 1453 1454 1455 1456<li>Numbers are written using a subset of the Latin alphabet (I, V, X, L, C, D, and M), which each have a fixed integer value (1, 5, 10, 50, 100, 500, 1000).</li> 1457 1458 1459 1460<li>Roman numerals are constructed by appending partial solutions, e.g. 1005 = MV.</li> 1461 1462 1463 1464<li>We should always start with the highest valued symbol, e.g. 5 = V rather than IIIII.</li> 1465 1466 1467 1468<li>Subtractive notation shortens some numerals, e.g. 40 = 50 – 10 = XL rather than XXXX.</li> 1469</ol> 1470 1471 1472 1473<p class="wp-block-paragraph">Step two is to pick one test from this list and express it in code. The order of tests matters. Some tests may force you to make a large change in your code. Always pick the next test such that you make it pass in an obvious way. It’s usually good to start with input validation, so that your programs are secure from the beginning. An additional benefit is that input validation is usually simple.</p> 1474 1475 1476 1477<p class="wp-block-paragraph">Here’s the first test:</p> 1478 1479 1480<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1481class WhenConvertingNumbersToRomanNumerals { 1482 1483 @Test 1484 void shouldRejectInexpressibleNumbers() { 1485 assertThrows(RomanNumeral.InexpressibleException.class, () -> 1486 RomanNumeral.from(0)); 1487 } 1488 1489} 1490</pre></div> 1491 1492 1493<p class="wp-block-paragraph">This is where we design our interface: we want a <code>RomanNumeral</code> class with a static method <code>from()</code> that accepts a number, returns a <code>String</code>, and throws an <code>InexpressibleException</code> exception on invalid input. To make the test compile, we have to add this code:</p> 1494 1495 1496<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1497public class RomanNumeral { 1498 1499 public static String from(int value) { 1500 return null; 1501 } 1502 1503 1504 public static class InexpressibleException 1505 extends IllegalArgumentException { 1506 } 1507 1508} 1509 1510</pre></div> 1511 1512 1513<p class="wp-block-paragraph">This fails because there is no exception thrown.</p> 1514 1515 1516 1517<p class="wp-block-paragraph">The third step is to make the test pass. The easiest way is to unconditionally throw the expected exception:</p> 1518 1519 1520<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1521 public static String from(int value) { 1522 throw new InexpressibleException(); 1523 } 1524</pre></div> 1525 1526 1527<p class="wp-block-paragraph">The fourth step is to optionally refactor. There isn’t enough code yet for that to make sense.</p> 1528 1529 1530 1531<p class="wp-block-paragraph">Now we continue with the next cycle at step two. For the next test, we could add another check for invalid input, but that wouldn’t change the code, so that’s not a smart choice. Tests for items 3-5 on the list all depend on the symbols in 2, so that’s the obvious next choice:</p> 1532 1533 1534<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1535 @Test 1536 void shouldConvertBaseSymbols() { 1537 assertThat(RomanNumeral.from(1), is("I")); 1538 } 1539</pre></div> 1540 1541 1542<p class="wp-block-paragraph">This fails, as expected, with an exception. We need to throw the exception in some cases, but not others. In other words, we need an <code>if</code> statement:</p> 1543 1544 1545<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate" title=""> 1546 public static String from(int value) { 1547 if (value == 0) { 1548 throw new InexpressibleException(); 1549 } 1550 return "I"; 1551 } 1552</pre></div> 1553 1554 1555<p class="wp-block-paragraph">Note how the code deals exclusively with the two tests that we wrote, and nothing else. Not only is the test coverage 100%, any other test than these two would fail. Such tests will force us to generalize the code.</p> 1556 1557 1558 1559<p class="wp-block-paragraph">Let’s first do the easy part and complete the input validation. First, generalize from 0 to non-positive numbers:</p> 1560 1561 1562<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1563 @ParameterizedTest 1564 @ValueSource(ints = {-1, 0}) 1565 void shouldRejectInexpressibleNumbers(int value) { 1566 assertThrows(RomanNumeral.InexpressibleException.class, () -> 1567 RomanNumeral.from(value)); 1568 } 1569</pre></div> 1570 1571<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1572 public static String from(int value) { 1573 if (value <= 0) { 1574 throw new InexpressibleException(); 1575 } 1576 return "I"; 1577 } 1578</pre></div> 1579 1580 1581<p class="wp-block-paragraph">With the lower bound in place, let’s add the upper bound:</p> 1582 1583 1584<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1585 @ParameterizedTest 1586 @ValueSource(ints = {-1, 0, 4000, 4001}) 1587 void shouldRejectInexpressibleNumbers(int value) { 1588 assertThrows(RomanNumeral.InexpressibleException.class, () -> 1589 RomanNumeral.from(value)); 1590 } 1591</pre></div> 1592 1593<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate" title=""> 1594 public static String from(int value) { 1595 if (value <= 0 || value >= 4000) { 1596 throw new InexpressibleException(); 1597 } 1598 return "I"; 1599 } 1600</pre></div> 1601 1602 1603<p class="wp-block-paragraph">This works, but doesn’t look great, so let’s do some refactoring to make it more expressive:</p> 1604 1605 1606<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1607 private static final int MIN_EXPRESSIBLE = 1; 1608 private static final int MAX_EXPRESSIBLE = 3999; 1609 1610 public static String from(int value) { 1611 if (!isExpressible(value)) { 1612 throw new InexpressibleException(); 1613 } 1614 return "I"; 1615 } 1616 1617 private static boolean isExpressible(int value) { 1618 return MIN_EXPRESSIBLE <= value && value <= MAX_EXPRESSIBLE; 1619 } 1620</pre></div> 1621 1622 1623<p class="wp-block-paragraph">Now we can cross item 1 of our list of tests. For our next test, we can pick from either 2 (more symbols) or 3 (additive form). The former would introduce an <code>if</code> statement that then generalizes into a <code>switch</code> with a bunch of random facts, while the latter would force us to develop a bit of an algorithm. That algorithm we can then apply to the other symbols. This sounds like an easier hill to climb than the reverse, where we would have to develop an algorithm that can deal with all the symbols. OK, here goes:</p> 1624 1625 1626<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1627 @ParameterizedTest 1628 @CsvSource({"1,I", "2,II"}) 1629 void shouldPerformAddition(int value, String expected) { 1630 assertThat(RomanNumeral.from(value), is(expected)); 1631 } 1632</pre></div> 1633 1634 1635<p class="wp-block-paragraph">Note that we renamed to test to better reflect what it is that we’re testing. The simplest way to make this test pass is to generalize the constant into a variable and to add to that variable inside an <code>if</code>:</p> 1636 1637 1638<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1639 public static String from(int value) { 1640 if (!isExpressible(value)) { 1641 throw new InexpressibleException(); 1642 } 1643 var result = "I"; 1644 if (value > 1) { 1645 result += "I"; 1646 } 1647 return result; 1648 } 1649</pre></div> 1650 1651 1652<p class="wp-block-paragraph">This looks messy, but bear with me. Things will become clearer after we add the next test:</p> 1653 1654 1655<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1656 @ParameterizedTest 1657 @CsvSource({"1,I", "2,II", "3,III"}) 1658 void shouldPerformAddition(int value, String expected) { 1659 assertThat(RomanNumeral.from(value), is(expected)); 1660 } 1661</pre></div> 1662 1663 1664<p class="wp-block-paragraph">To make this pass, we have to generalize the <code>if</code> to a <code>while</code>:</p> 1665 1666 1667<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1668 public static String from(int value) { 1669 if (!isExpressible(value)) { 1670 throw new InexpressibleException(); 1671 } 1672 var result = "I"; 1673 while (value > 1) { 1674 result += "I"; 1675 value--; 1676 } 1677 return result; 1678 } 1679</pre></div> 1680 1681 1682<p class="wp-block-paragraph">If we clean up a bit, we’re starting to see an algorithm form:</p> 1683 1684 1685<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1686 public static String from(int value) { 1687 if (!isExpressible(value)) { 1688 throw new InexpressibleException(); 1689 } 1690 var result = ""; 1691 while (value >= 1) { 1692 result += "I"; 1693 value -= 1; 1694 } 1695 return result; 1696 } 1697</pre></div> 1698 1699 1700<p class="wp-block-paragraph">The constants in this piece of code are related: <code>"I"</code> is the symbol for <code>1</code>, so the algorithm adds <code>"I"</code> for as long as it needs. Let’s make this relationship clearer:</p> 1701 1702 1703<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1704 public static String from(int value) { 1705 if (!isExpressible(value)) { 1706 throw new InexpressibleException(); 1707 } 1708 var result = ""; 1709 var numeral = new Numeral("I", 1); 1710 while (value >= numeral.value()) { 1711 result += numeral.text(); 1712 value -= numeral.value(); 1713 } 1714 return result; 1715 } 1716 1717 private record Numeral(String text, int value) { 1718 } 1719</pre></div> 1720 1721 1722<p class="wp-block-paragraph">We can improve this code further. The <code>from()</code> method deals with a nice abstraction of inexpressible numbers, but also with a whole bunch of details about adding texts and subtracting numbers. So let’s extract all those details into its own method:</p> 1723 1724 1725<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1726 public static String from(int value) { 1727 if (!isExpressible(value)) { 1728 throw new InexpressibleException(); 1729 } 1730 return convert(value); 1731 } 1732 1733 private static String convert(int value) { 1734 var result = ""; 1735 var numeral = new Numeral("I", 1); 1736 while (value >= numeral.value()) { 1737 result += numeral.text(); 1738 value -= numeral.value(); 1739 } 1740 return result; 1741 } 1742</pre></div> 1743 1744 1745<p class="wp-block-paragraph">Another code smell here is that we change the value of the parameter. We can solve that in two ways. The first is to assign the parameter to a local variable and then use that variable everywhere we now use the parameter. The second is to introduce a local variable and compare it to the parameter. This turns out to be more instructive:</p> 1746 1747 1748<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1749 private static String convert(int value) { 1750 var result = ""; 1751 var progress = 0; 1752 var numeral = new Numeral("I", 1); 1753 while (value - progress >= numeral.value()) { 1754 result += numeral.text(); 1755 progress += numeral.value(); 1756 } 1757 return result; 1758 } 1759</pre></div> 1760 1761 1762<p class="wp-block-paragraph">Now we can see something interesting: the combination of <code>result</code> and <code>progress</code> is very much like a <code>Numeral</code>. But in order to express that, we need to be able to add two <code>Numeral</code>s:</p> 1763 1764 1765<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1766 private static String convert(int value) { 1767 var result = new Numeral("", 0); 1768 var numeral = new Numeral("I", 1); 1769 while (value - result.value() >= numeral.value()) { 1770 result = result.add(numeral); 1771 } 1772 return result.text(); 1773 } 1774 1775 private record Numeral(String text, int value) { 1776 1777 Numeral add(Numeral addens) { 1778 return new Numeral(text + addens.text, 1779 value + addens.value); 1780 } 1781 1782 } 1783</pre></div> 1784 1785 1786<p class="wp-block-paragraph">Now the contours of the algorithm are starting to become more apparent: we will build up the result by processing our numeral. Presumably, we’ll add processing of other numerals later. In order to prepare for that, let’s tidy the code a bit more by extracting the part that handles the <code>numeral</code> variable. If we did that on the current code, however, the extracted method would need to take <code>result</code> and <code>value</code> as parameters, in addition to <code>numeral</code>. That’s because this is a <code>static</code> method, and thus not able to use fields. Let’s fix that. First we make <code>convert()</code> an instance method:</p> 1787 1788 1789<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1790 public static String from(int value) { 1791 if (!isExpressible(value)) { 1792 throw new InexpressibleException(); 1793 } 1794 return new RomanNumeral().convert(value); 1795 } 1796 1797 private String convert(int value) { 1798 var result = new Numeral("", 0); 1799 var numeral = new Numeral("I", 1); 1800 while (value - result.value() >= numeral.value()) { 1801 result = result.add(numeral); 1802 } 1803 return result.text(); 1804 } 1805</pre></div> 1806 1807 1808<p class="wp-block-paragraph">Then we can turn <code>result</code> and <code
1808>value</code> into fields. We also rename <code>value</code> to <code>target</code> and <code>convert()</code> to <code>complete()</code> to better express their meaning:</p> 1809 1810 1811<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1812 private final int target; 1813 private Numeral current = new Numeral("", 0); 1814 1815 private RomanNumeral(int target) { 1816 this.target = target; 1817 } 1818 1819 public static String from(int value) { 1820 if (!isExpressible(value)) { 1821 throw new InexpressibleException(); 1822 } 1823 return new RomanNumeral(value).complete(); 1824 } 1825 1826 private String complete() { 1827 var numeral = new Numeral("I", 1); 1828 while (target - current.value() >= numeral.value()) { 1829 current = current.add(numeral); 1830 } 1831 return current.text(); 1832 } 1833</pre></div> 1834 1835 1836<p class="wp-block-paragraph">Now we can finally extract the handling of one numeral into its own method:</p> 1837 1838 1839<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1840 private String complete() { 1841 var numeral = new Numeral("I", 1); 1842 add(numeral); 1843 return current.text(); 1844 } 1845 1846 private void add(Numeral numeral) { 1847 while (target - current.value() >= numeral.value()) { 1848 current = current.add(numeral); 1849 } 1850 } 1851</pre></div> 1852 1853 1854<p class="wp-block-paragraph">We may even want to extract another method to express the algorithm better:</p> 1855 1856 1857<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1858 private void add(Numeral numeral) { 1859 while (remainder() >= numeral.value()) { 1860 current = current.add(numeral); 1861 } 1862 } 1863 1864 private int remainder() { 1865 return target - current.value(); 1866 } 1867</pre></div> 1868 1869 1870<p class="wp-block-paragraph">That was a lot of refactoring, but look at what it did to the design.</p> 1871 1872 1873 1874<p class="wp-block-paragraph">We have now finished item 3 on our list. Let’s continue with item 2:</p> 1875 1876 1877<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1878 @ParameterizedTest 1879 @CsvSource({"1,I", "5,V"}) 1880 void shouldConvertBaseSymbols(int value, String expected) { 1881 assertThat(RomanNumeral.from(value), is(expected)); 1882 } 1883</pre></div> 1884 1885 1886<p class="wp-block-paragraph">We can make this pass by generalizing our single numeral into a list:</p> 1887 1888 1889<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1890 private String complete() { 1891 var numerals = List.of(new Numeral("V", 5), new Numeral("I", 1)); 1892 numerals.forEach(this::add); 1893 return current.text(); 1894 } 1895</pre></div> 1896 1897 1898<p class="wp-block-paragraph">Note that our algorithm only works if we process the numerals from high to low (item 4 on our test list). It’s now easy to add the other base symbols:</p> 1899 1900 1901<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1902 @ParameterizedTest 1903 @CsvSource({"1,I", "5,V", "10,X", "50,L", "100,C", "500,D", "1000,M"}) 1904 void shouldConvertBaseSymbols(int value, String expected) { 1905 assertThat(RomanNumeral.from(value), is(expected)); 1906 } 1907</pre></div> 1908 1909<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1910 private String complete() { 1911 var numerals = List.of( 1912 new Numeral("M", 1000), 1913 new Numeral("D", 500), 1914 new Numeral("C", 100), 1915 new Numeral("L", 50), 1916 new Numeral("X", 10), 1917 new Numeral("V", 5), 1918 new Numeral("I", 1)); 1919 numerals.forEach(this::add); 1920 return current.text(); 1921 } 1922</pre></div> 1923 1924 1925<p class="wp-block-paragraph">This concludes items 2 and 4 of our test list. The only thing left to do is item 5:</p> 1926 1927 1928<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1929 @ParameterizedTest 1930 @CsvSource({"4,IV"}) 1931 void shouldShortenWithSubtractiveNotation(int value, String expected) { 1932 assertThat(RomanNumeral.from(value), is(expected)); 1933 } 1934</pre></div> 1935 1936 1937<p class="wp-block-paragraph">We can make this pass simply by adding that numeral explicitly:</p> 1938 1939 1940<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1941 private String complete() { 1942 var numerals = List.of( 1943 new Numeral("M", 1000), 1944 new Numeral("D", 500), 1945 new Numeral("C", 100), 1946 new Numeral("L", 50), 1947 new Numeral("X", 10), 1948 new Numeral("V", 5), 1949 new Numeral("IV", 4), 1950 new Numeral("I", 1));
1951 numerals.forEach(this::add); 1952 return current.text(); 1953 } 1954</pre></div> 1955 1956 1957<p class="wp-block-paragraph">While that works, it isn’t pretty. There is duplication in the values, since <code>"IV" == "V" - "I" == 5 - 1 == 4</code>. Let’s express that better:</p> 1958 1959 1960<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate" title=""> 1961 private String complete() { 1962 var v = new Numeral("V", 5); 1963 var i = new Numeral("I", 1); 1964 var numerals = List.of( 1965 new Numeral("M", 1000), 1966 new Numeral("D", 500), 1967 new Numeral("C", 100), 1968 new Numeral("L", 50), 1969 new Numeral("X", 10), 1970 v, 1971 v.subtract(i), 1972 i); 1973 numerals.forEach(this::add); 1974 return current.text(); 1975 } 1976 1977 private record Numeral(String text, int value) { 1978 1979 public Numeral subtract(Numeral subtrahens) { 1980 return new Numeral(subtrahens.text + text, 1981 value - subtrahens.value); 1982 } 1983 1984 } 1985</pre></div> 1986 1987 1988<p class="wp-block-paragraph">The others are similar:</p> 1989 1990 1991<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 1992 @ParameterizedTest 1993 @CsvSource({"4,IV", "9,IX", "40,XL", "90,XC", "400,CD", "900,CM"}) 1994 void shouldShortenWithSubtractiveNotation(int value, String expected) { 1995 assertThat(RomanNumeral.from(value), is(expected)); 1996 } 1997</pre></div> 1998 1999<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2000 private String complete() { 2001 var i = new Numeral("I", 1); 2002 var v = new Numeral("V", 5); 2003 var x = new Numeral("X", 10); 2004 var l = new Numeral("L", 50); 2005 var c = new Numeral("C", 100); 2006 var d = new Numeral("D", 500); 2007 var m = new Numeral("M", 1000); 2008 var numerals = List.of( 2009 m, 2010 m.subtract(c), 2011 d, 2012 d.subtract(c), 2013 c, 2014 c.subtract(x), 2015 l, 2016 l.subtract(x), 2017 x, 2018 x.subtract(i), 2019 v, 2020 v.subtract(i), 2021 i); 2022 numerals.forEach(this::add); 2023 return current.text(); 2024 } 2025</pre></div> 2026 2027 2028<p class="wp-block-paragraph">Now we see another form of duplication in the way the list of numerals is constructed. There is a pattern that repeats itself for every power of 10. We should extract that pattern:</p> 2029 2030 2031<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2032 private String complete() { 2033 var i = new Numeral("I", 1); 2034 var v = new Numeral("V", 5); 2035 var x = new Numeral("X", 10); 2036 var l = new Numeral("L", 50); 2037 var c = new Numeral("C", 100); 2038 var d = new Numeral("D", 500); 2039 var m = new Numeral("M", 1000); 2040 var numerals = new TreeSet<>(comparing(Numeral::value).reversed()); 2041 numerals.addAll(includeSubtractives(i, v, x)); 2042 numerals.addAll(includeSubtractives(x, l, c)); 2043 numerals.addAll(includeSubtractives(c, m, d)); 2044 numerals.forEach(this::add); 2045 return current.text(); 2046 } 2047 2048 private Collection<Numeral> includeSubtractives(Numeral one, 2049 Numeral five, Numeral ten) { 2050 return List.of( 2051 one, 2052 five.subtract(one), 2053 five, 2054 ten.subtract(one), 2055 ten); 2056 } 2057</pre></div> 2058 2059 2060<p class="wp-block-paragraph">Note that we have to use a set to remove duplicate numerals and we need to sort that set in the correct order for the algorithm to work.</p> 2061 2062 2063 2064<p class="wp-block-paragraph">We’re still not done, though. The way we add the pattern of subtractives isn’t random; there’s a pattern to that too:</p> 2065 2066 2067<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2068 private String complete() { 2069 var i = new Numeral("I", 1);
2070 var v = new Numeral("V", 5); 2071 var x = new Numeral("X", 10); 2072 var l = new Numeral("L", 50); 2073 var c = new Numeral("C", 100); 2074 var d = new Numeral("D", 500); 2075 var m = new Numeral("M", 1000); 2076 var baseNumerals = List.of(i, v, x, l, c, d, m); 2077 var numerals = new TreeSet<>(comparing(Numeral::value).reversed()); 2078 for (var index = 0; index < baseNumerals.size() - 1; index += 2) { 2079 numerals.addAll(includeSubtractives( 2080 baseNumerals.get(index), 2081 baseNumerals.get(index + 1), 2082 baseNumerals.get(index + 2))); 2083 } 2084 numerals.forEach(this::add); 2085 return current.text(); 2086 } 2087</pre></div> 2088 2089 2090<p class="wp-block-paragraph">We can clean that up further by extracting the base numerals into a constant:</p> 2091 2092 2093<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2094 private static final List<Numeral> BASE_NUMERALS = List.of( 2095 new Numeral("I", 1), 2096 new Numeral("V", 5), 2097 new Numeral("X", 10), 2098 new Numeral("L", 50), 2099 new Numeral("C", 100), 2100 new Numeral("D", 500), 2101 new Numeral("M", 1000)); 2102 2103 private String complete() { 2104 var numerals = new TreeSet<>(comparing(Numeral::value).reversed()); 2105 for (var index = 0; index < BASE_NUMERALS.size() - 1; index += 2) { 2106 numerals.addAll(includeSubtractives( 2107 BASE_NUMERALS.get(index), 2108 BASE_NUMERALS.get(index + 1), 2109 BASE_NUMERALS.get(index + 2))); 2110 } 2111 numerals.forEach(this::add); 2112 return current.text(); 2113 } 2114</pre></div> 2115 2116 2117<p class="wp-block-paragraph">And extracting the creation of <code>numerals</code> into its own method:</p> 2118 2119 2120<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2121 private String complete() { 2122 numerals().forEach(this::add); 2123 return current.text(); 2124 } 2125 2126 private Collection<Numeral> numerals() { 2127 var result = new TreeSet<>(comparing(Numeral::value).reversed()); 2128 for (var index = 0; index < BASE_NUMERALS.size() - 1; index += 2) { 2129 result.addAll(includeSubtractives( 2130 BASE_NUMERALS.get(index), 2131 BASE_NUMERALS.get(index + 1), 2132 BASE_NUMERALS.get(index + 2))); 2133 } 2134 return result; 2135 } 2136</pre></div> 2137 2138 2139<p class="wp-block-paragraph">The code now expresses all concepts well and our test list is empty, so we’re done.</p> 2140 2141 2142 2143<p class="wp-block-paragraph">Here’s the complete solution:</p> 2144 2145 2146<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate" title=""> 2147import java.util.*; 2148 2149import static java.util.Comparator.comparing; 2150 2151 2152public class RomanNumeral { 2153 2154 private static final int MIN_EXPRESSIBLE = 1; 2155 private static final int MAX_EXPRESSIBLE = 3999; 2156 private static final List<Numeral> BASE_NUMERALS = List.of( 2157 new Numeral("I", 1), 2158 new Numeral("V", 5), 2159 new Numeral("X", 10), 2160 new Numeral("L", 50), 2161 new Numeral("C", 100), 2162 new Numeral("D", 500), 2163 new Numeral("M", 1000)); 2164 2165 private final int target; 2166 private Numeral current = new Numeral("", 0); 2167 2168 private RomanNumeral(int target) { 2169 this.target = target; 2170 } 2171 2172 public static String from(int value) { 2173 if (!isExpressible(value)) { 2174 throw new InexpressibleException(); 2175 } 2176 return new RomanNumeral(value).complete(); 2177 } 2178 2179 private static boolean isExpressible(int value) { 2180 return MIN_EXPRESSIBLE <= value && value <= MAX_EXPRESSIBLE; 2181 } 2182 2183 private String complete() {
2184 numerals().forEach(this::add); 2185 return current.text(); 2186 } 2187 2188 private Collection<Numeral> numerals() { 2189 var result = new TreeSet<>(comparing(Numeral::value).reversed()); 2190 for (var index = 0; index < BASE_NUMERALS.size() - 1; index += 2) { 2191 result.addAll(includeSubtractives( 2192 BASE_NUMERALS.get(index), 2193 BASE_NUMERALS.get(index + 1), 2194 BASE_NUMERALS.get(index + 2))); 2195 } 2196 return result; 2197 } 2198 2199 private Collection<Numeral> includeSubtractives(Numeral one, 2200 Numeral five, Numeral ten) { 2201 return List.of( 2202 one, 2203 five.subtract(one), 2204 five, 2205 ten.subtract(one), 2206 ten); 2207 } 2208 2209 private void add(Numeral numeral) { 2210 while (remainder() >= numeral.value()) { 2211 current = current.add(numeral); 2212 } 2213 } 2214 2215 private int remainder() { 2216 return target - current.value(); 2217 } 2218 2219 2220 public static class InexpressibleException 2221 extends IllegalArgumentException { 2222 } 2223 2224 2225 private record Numeral(String text, int value) { 2226 2227 Numeral add(Numeral addens) { 2228 return new Numeral(text + addens.text, 2229 value + addens.value); 2230 } 2231 2232 public Numeral subtract(Numeral subtrahens) { 2233 return new Numeral(subtrahens.text + text, 2234 value - subtrahens.value); 2235 } 2236 2237 } 2238 2239} 2240</pre></div> </div><!-- .entry-content --> 2241 2242 <footer class="entry-footer"> 2243 2244 <div class="entry-meta"> 2245 <span class="byline"> 2246 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 2247 <span class="cat-links"> 2248 <a href="https://raysinnema.blog/category/software-development/" rel="category tag">Software Development</a> </span><!-- .cat-links --> 2249 2250 <span class="comments-link"><a href="https://raysinnema.blog/2023/12/12/canon-tdd-example-roman-numerals/#respond">Leave a comment</a></span><!-- .comments-link --> 2251 <span class="published-on"> 2252 <a href="https://raysinnema.blog/2023/12/12/canon-tdd-example-roman-numerals/" rel="bookmark"><time class="entry-date published" datetime="2023-12-12T13:00:00-07:00">2023-12-12</time><time class="updated" datetime="2023-12-23T16:00:48-07:00">2023-12-23</time></a> 2253 </span> 2254 2255 <span class="word-count">11 Minutes</span> </div><!-- .entry-meta --> 2256 </footer><!-- .entry-footer --> 2257</article><!-- #post-## --> 2258 2259 2260 2261<article id="post-8103" class="post-8103 post type-post status-publish format-standard hentry category-architecture tag-adr"> 2262 <header class="entry-header"> 2263 <h1 class="entry-title"><a href="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/" rel="bookmark">Should we put the names of deciders in ADRs?</a></h1> </header><!-- .entry-header --> 2264 <div class="entry-content"> 2265 2266<p class="wp-block-paragraph"><a href="https://adr.github.io/">Architecture Decision Records</a> (ADRs) are a wonderful tool. They force you to think through the options before you make a decision. They also provide institutional memory that’s independent of people, which is great for new joiners with their inevitable <em>Why?</em> questions.</p> 2267 2268 2269 2270<p class="wp-block-paragraph">Michael Nygard’s <a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions">template</a> shows what sections an ADRs should have. There are <a href="https://github.com/joelparkerhenderson/architecture-decision-record">several other templates</a> out there as well.</p> 2271 2272 2273 2274<p class="wp-block-paragraph">Nygard’s template has a <code>Status</code> section, which only contains the status of the ADR. At most teams in <a href="https://www.adevinta.com/">Adevinta</a>, we add the names of the people involved in making the decision to the <code>Status</code> section, something like <code>Accepted by ray.sinnema on 2023-09-04</code>. Most of the templates I’ve seen don’t have this, although some do, e.g. <a href="https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-madr/index.md">Joel Parker Henderson</a>, and <a href="https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-using-planguage/index.md">Planguage</a>.</p> 2275 2276 2277 2278<p class="wp-block-paragraph">Recently, I got challenged on this practice. They argued that individuals don’t matter, since the team as a whole makes the decisions. I love such challenges, since they force me to think through and articulate my reasons better. So let’s look at why adding names to ADRs adds value.</p> 2279 2280 2281 2282<h2 class="wp-block-heading">Personal branding</h2> 2283 2284 2285<div class="wp-block-image"> 2286<figure class="alignleft is-resized"><img data-attachment-id="8122" data-permalink="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/personal-branding-2/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="personal-branding" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=1024" loading="lazy" width="1200" height="1200" src="https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png" alt="" class="wp-image-8122" style="width:119px;height:auto" srcset="https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png 1200w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=160&h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=240&h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=768&h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=1024&
2286h=1024 1024w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></figure> 2287</div> 2288 2289 2290<p class="wp-block-paragraph">Like it or not, it’s important for your name to pop up if you want a promotion. Now, inside your team people will know your strengths. But not all promotions are decided inside a team, i.e. by your manager.</p> 2291 2292 2293 2294<p class="wp-block-paragraph">The more you climb the ranks, the more important it becomes for people outside your team to see the value that you add.</p> 2295 2296 2297 2298<p class="wp-block-paragraph">Having your name in ADRs, especially the more impactful ones, helps with your personal brand. You can point to those ADRs as proof that you can make difficult decisions well and are ready for the next step in your career.</p> 2299 2300 2301 2302<h2 class="wp-block-heading">External stakeholders</h2> 2303 2304 2305<div class="wp-block-image"> 2306<figure class="alignright is-resized"><img data-attachment-id="8124" data-permalink="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/stakeholders/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="stakeholders" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=1024" loading="lazy" width="1200" height="1200" src="https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png" alt="" class="wp-image-8124" style="width:129px;height:auto" srcset="https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png 1200w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=160&h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=240&h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=768&h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=1024&h=1024 1024w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></figure> 2307</div> 2308 2309 2310<p class="wp-block-paragraph">Sometimes a decision can’t be made by the team alone. The decision may have budget impact, for instance, and need approval from a budget owner or from the Procurement department.</p> 2311 2312 2313 2314<p class="wp-block-paragraph">Or the decision may affect how you store personal data and the Privacy department needs to give their blessing.</p> 2315 2316 2317 2318<p class="wp-block-paragraph">Having the names of those outside collaborators in the ADR shows that you’ve consulted the right people and may prevent challenges to the decision from outside the team (<a href="https://en.wikipedia.org/wiki/Cover_your_ass">CYA</a>).</p> 2319 2320 2321 2322<h2 class="wp-block-heading">Contact person</h2> 2323 2324 2325<div class="wp-block-image"> 2326<figure class="alignleft is-resized"><img data-attachment-id="8128" data-permalink="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/chat/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2023/09/chat.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="chat" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=1024" loading="lazy" width="1200" height="1200" src="https://raysinnema.blog/wp-content/uploads/2023/09/chat.png" alt="" class="wp-image-8128" style="width:92px;height:auto" srcset="https://raysinnema.blog/wp-content/uploads/2023/09/chat.png 1200w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=160&h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=240&h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=768&h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=1024&
2326h=1024 1024w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></figure> 2327</div> 2328 2329 2330<p class="wp-block-paragraph">Most companies have more than one team. It may be interesting for other teams to look at your team’s decisions.</p> 2331 2332 2333 2334<p class="wp-block-paragraph">For instance, you may have an ADR on a public cloud migration and the other team is about to embark on a similar migration. They could learn from your research into the options and their pros and cons.</p> 2335 2336 2337 2338<p class="wp-block-paragraph">Having your name in the ADR gives the other team a person to contact. If it’s not there, they need to contact the team’s manager and ask them for a person to reach out to. That may sound like a small step, but every extra roadblock means you could hamper the transfer of knowledge.</p> 2339 2340 2341 2342<p class="wp-block-paragraph">Writing an ADR isn’t a daily activity. Many teams may go long periods of time without needing to write one. This means the opportunities for team members to learn how to make good decisions and write them up in ADRs are relatively scarce. Looking at other team’s ADRs may help, especially if you can have a follow-up conversation with the decision makers.</p> 2343 2344 2345 2346<h2 class="wp-block-heading">Counterargument</h2> 2347 2348 2349 2350<p class="wp-block-paragraph">Some might argue that you can see the ADR contributors from the Git history (you <em>are</em> versioning your ADRs, aren’t you?). However, that’s not universally true, because</p> 2351 2352 2353 2354<ul class="wp-block-list"> 2355<li>Not everyone who contributes ideas to an ADR will create a commit or PR comment. This is especially true when you mob-write your ADRs.</li> 2356 2357 2358 2359<li>Some people may not have access to Git, like PMs, or VPs. </li> 2360 2361 2362 2363<li>If you make your ADRs available via some other system, like <a href="https://backstage.io/">Backstage</a>, it may not be clear to the reader where the ADR is stored.</li> 2364</ul> 2365 2366 2367 2368<h2 class="wp-block-heading">Conclusion</h2> 2369 2370 2371 2372<p class="wp-block-paragraph">I think there are good reasons for adding the names of the deciders to ADRs, especially in larger organizations. Whether this is in the <code>Status</code> section, or in some other section doesn’t matter much.</p> 2373 2374 2375 2376<h2 class="wp-block-heading">Acknowledgments</h2> 2377 2378 2379 2380<p class="wp-block-paragraph">Thanks to <a href="https://www.linkedin.com/in/francescorainone/">Francesco</a> for raising this topic, to <a href="https://www.linkedin.com/in/alexeykobyakov/">Alexey</a> for helping me crystallize my thoughts, and to <a href="https://www.linkedin.com/in/gabrielfabic/">Gabe</a> for reminding me to write about the counterargument.</p> 2381 2382 2383 2384<p class="wp-block-paragraph"></p> 2385 2386 2387 2388<p class="wp-block-paragraph"></p> 2389 </div><!-- .entry-content --> 2390 2391 <footer class="entry-footer"> 2392 2393 <div class="entry-meta"> 2394 <span class="byline"> 2395 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 2396 <span class="cat-links"> 2397 <a href="https://raysinnema.blog/category/architecture/" rel="category tag">architecture</a> </span><!-- .cat-links --> 2398 2399 <span class="comments-link"><a href="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/#respond">Leave a comment</a></span><!-- .comments-link --> 2400 <span class="published-on"> 2401 <a href="https://raysinnema.blog/2023/09/05/should-we-put-the-names-of-deciders-in-adrs/" rel="bookmark"><time class="entry-date published" datetime="2023-09-05T13:00:00-06:00">2023-09-05</time><time class="updated" datetime="2023-09-05T15:16:40-06:00">2023-09-05</time></a> 2402 </span> 2403 2404 <span class="word-count">3 Minutes</span> </div><!-- .entry-meta --> 2405 </footer><!-- .entry-footer --> 2406</article><!-- #post-## --> 2407 2408 2409 2410<article id="post-8093" class="post-8093 post type-post status-publish format-standard hentry category-architecture tag-anti-corruption-layer tag-architecture tag-bounded-context tag-coupling tag-hexagonal-architecture tag-micro-service tag-microservices tag-modularity"> 2411 <header class="entry-header"> 2412 <h1 class="entry-title"><a href="https://raysinnema.blog/2023/08/28/the-anti-corruption-microservice-pattern/" rel="bookmark">The Anti-Corruption Microservice Pattern</a></h1> </header><!-- .entry-header --> 2413 <div class="entry-content"> 2414 2415<p class="wp-block-paragraph">Implement an anti-corruption microservice (ACM) to talk to an external system. Other microservices only communicate with the external system via the ACM. The ACM translates requests from the microservices system to the external system and back.</p> 2416 2417 2418 2419<p class="wp-block-paragraph">Use this pattern when you employ a <a href="https://microservices.io/patterns/microservices.html">microservices architecture</a> and you want to ensure their designs are not limited by an external system. This pattern is an adaptation of the <a href="https://learn.microsoft.com/en-us
2419/azure/architecture/patterns/anti-corruption-layer">Anti-Corruption Layer</a> (ACL) pattern for microservices architectures.</p> 2420 2421 2422 2423<h2 class="wp-block-heading">Context and problem</h2> 2424 2425 2426 2427<p class="wp-block-paragraph">The context of the ACL pattern applies, but additionally, your application is made up of microservices and multiple of those communicate with the external system. If you’d only apply the ACL pattern, then you’d end up with multiple microservices that each have an ACL for the same external system.</p> 2428 2429 2430 2431<h2 class="wp-block-heading">Solution</h2> 2432 2433 2434 2435<p class="wp-block-paragraph">Isolate the microservices from the external system by placing an Anti-Corruption Microservice (ACM) between them. This layer translates communications between the application’s microservices on the one hand and the external system on the other.</p> 2436 2437 2438 2439<figure class="wp-block-image"><img data-attachment-id="8098" data-permalink="https://raysinnema.blog/2023/08/28/the-anti-corruption-microservice-pattern/anti-corruption-microservice/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png" data-orig-size="761,431" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="anti-corruption-microservice" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png?w=761" loading="lazy" width="761" height="431" src="https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png" alt="" class="wp-image-8098" srcset="https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png 761w, https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png?w=160&h=91 160w, https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png?w=240&
2439h=136 240w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure> 2440 2441 2442 2443<p class="wp-block-paragraph">The diagram above shows an application consisting of 4 microservices, 3 of which communicate with the same external system via an Anti-Corruption Microservice. Communication between the microservices and the ACM always uses the data model and architecture of the application. Calls from the ACM to the external system conform to the external system’s data model and methods. The ACM contains all the logic necessary to translate between the two systems.</p> 2444 2445 2446 2447<h2 class="wp-block-heading">Issues and considerations</h2> 2448 2449 2450 2451<ul class="wp-block-list"> 2452<li>The ACM may add latency to calls between the two systems. Conversely, by caching data from calls by one microservice, the ACM might speed up calls by a different microservice.</li> 2453 2454 2455 2456<li>The ACM is another microservice to be managed, maintained, and scaled.</li> 2457 2458 2459 2460<li>The ACM doesn’t need to support all the features of the external system, just the ones used by the application’s microservices.</li> 2461</ul> 2462 2463 2464 2465<h2 class="wp-block-heading">When to use this pattern</h2> 2466 2467 2468 2469<p class="wp-block-paragraph">Use this pattern when:</p> 2470 2471 2472 2473<ul class="wp-block-list"> 2474<li>Two or more systems have different semantics, but still need to communicate.</li> 2475 2476 2477 2478<li>The external system is provided by a vendor and you want to minimize vendor lock-in. This elevates the idea of hexagonal architecture to the microservices world, where the ACM is a port and the external system an adapter.</li> 2479</ul> 2480 2481 2482 2483<h2 class="wp-block-heading">Related resources</h2> 2484 2485 2486 2487<ul class="wp-block-list"> 2488<li><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer">Anti-Corruption Layer</a></li> 2489</ul> 2490 2491 2492 2493<h2 class="wp-block-heading">Acknowledgements</h2> 2494 2495 2496 2497<p class="wp-block-paragraph">Thanks to <a href="https://www.linkedin.com/in/dev-doongoor-7b2a1111/">Dev</a> for pushing me to write up this pattern.</p> 2498 </div><!-- .entry-content --> 2499 2500 <footer class="entry-footer"> 2501 2502 <div class="entry-meta"> 2503 <span class="byline"> 2504 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 2505 <span class="cat-links"> 2506 <a href="https://raysinnema.blog/category/architecture/" rel="category tag">architecture</a> </span><!-- .cat-links --> 2507 2508 <span class="comments-link"><a href="https://raysinnema.blog/2023/08/28/the-anti-corruption-microservice-pattern/#respond">Leave a comment</a></span><!-- .comments-link --> 2509 <span class="published-on"> 2510 <a href="https://raysinnema.blog/2023/08/28/the-anti-corruption-microservice-pattern/" rel="bookmark"><time class="entry-date published" datetime="2023-08-28T13:00:45-06:00">2023-08-28</time><time class="updated" datetime="2023-09-05T00:54:55-06:00">2023-09-05</time></a> 2511 </span> 2512 2513 <span class="word-count">1 Minute</span> </div><!-- .entry-meta --> 2514 </footer><!-- .entry-footer --> 2515</article><!-- #post-## --> 2516 2517 2518 2519<article id="post-7992" class="post-7992 post type-post status-publish format-standard hentry category-architecture tag-hexagonal-architecture tag-technical-debt"> 2520 <header class="entry-header"> 2521 <h1 class="entry-title"><a href="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/" rel="bookmark">Hexagonal Architecture helps keep tech debt low</a></h1> </header><!-- .entry-header --> 2522 <div class="entry-content"> 2523 <div class="wp-block-image"> 2524<figure class="alignleft size-large is-resized"><img loading="lazy" src="https://www.wallquotes.com/sites/default/files/arts0586-02.png" alt="" width="227" height="227" title="Hexagons" /></figure> 2525</div> 2526 2527 2528<p class="wp-block-paragraph">Some people remain skeptical of the idea that <a rel="noreferrer noopener" href="https://remonsinnema.com/2022/02/07/no-need-to-manage-technical-debt/" target="_blank">tech debt can be kept low</a> over the long haul.</p> 2529 2530 2531 2532<p class="wp-block-paragraph">I’ve noticed that when people say something can’t be done, it usually means that they don’t know how to do it. To help with that, this post explores an approach to keep one major source of tech debt under control.</p> 2533 2534 2535 2536<h2 class="wp-block-heading"><span style="color:revert;font-size:revert;font-weight:revert;">Tech debt can grow without the application changing</span></h2> 2537 2538 2539 2540<p class="wp-block-paragraph"><a rel="noreferrer noopener" href="https://www.martinfowler.com/bliki/TechnicalDebt.html" target="_blank">Martin Fowler defines technical debt</a> as “
2540deficiencies in internal quality that make it harder than it would ideally be to modify and extend the system further”. Modifications and extensions are changes and there exist many types of those.</p> 2541 2542 2543 2544<p class="wp-block-paragraph">In this post, I’d like to focus on <strong>changes in perception</strong>. That might seem odd, but bear with me. Let’s look at some examples:</p> 2545 2546 2547 2548<ul class="wp-block-list"><li>We are running our software on GCP and our company gets acquired by another that wants to standardize on AWS.</li><li>A new <a rel="noreferrer noopener" href="https://blogs.oracle.com/javamagazine/post/java-long-term-support-lts" target="_blank">LTS version</a> of Java comes out.</li><li>We use a logging library that all of a sudden <a rel="noreferrer noopener" href="https://en.wikipedia.org/wiki/Log4Shell" target="_blank">doesn’t look so great</a> anymore.</li><li>The framework that we build our system around is <a rel="noreferrer noopener" href="https://springframework.guru/getting-ready-for-spring-framework-6/" target="_blank">introducing breaking changes</a>.</li><li>We learn about a new design pattern that we realize would make our code much simpler, or more resilient, or better in some other way.</li></ul> 2549 2550 2551 2552<p class="wp-block-paragraph">These examples have in common that the change is in the way we look at our code rather than in the code itself. Although the code didn’t change, our definition of “internal quality” did and therefore so did our amount of technical debt.</p> 2553 2554 2555 2556<h2 class="wp-block-heading">Responding to changed perceptions</h2> 2557 2558 2559<div class="wp-block-image"> 2560<figure class="alignright size-large is-resized"><img data-attachment-id="8006" data-permalink="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/change/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2022/08/change.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="change" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=1024" loading="lazy" src="https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=1024" alt="" class="wp-image-8006" width="177" height="177" title="Change" srcset="https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=177 177w, https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=354 354w, https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2022/08/change.png?w=240 240w" sizes="auto, (max-width: 177px) 100vw, 177px" /></figure> 2561</div> 2562 2563 2564<p class="wp-block-paragraph">When our perception of the code changes, we think of the code as having tech debt and we want to change it. How do we best make this type of change? And if our code looks fine today but maybe not tomorrow, then what can we possibly do to prevent that?</p> 2565 2566 2567 2568<p class="wp-block-paragraph">The answers to such questions depends on the type of technology that is affected. Programming languages and frameworks are fundamentally different from infrastructure, libraries, and our own code.</p> 2569 2570 2571 2572<p class="wp-block-paragraph"><strong>Language</strong> changes come in different sizes. If you’ve picked a language that values stability, like Java, then you’re rarely if ever forced to make changes when adopting a new version. If you picked a more volatile language, well, that was a trade-off you made. (Hopefully with open eyes and documented in an <a rel="noreferrer noopener" href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions" target="_blank">ADR</a> for bonus points.)</p> 2573 2574 2575 2576<p class="wp-block-paragraph">Even when you’re not forced to change, you may still <em>want</em> to, to benefit from new language constructs (like <a rel="noreferrer noopener" href="https://docs.oracle.com/en/java/javase/14/language/records.html" target="_blank">records</a> or <a rel="noreferrer noopener" href="https://docs.oracle.com/en/java/javase/15/language/sealed-classes-and-interfaces.html" target="_blank">sealed classes</a> for Java). You can define a project to update the entire code base in one fell swoop, but you’d probably need to formally schedule that work. It’
2576s easier to only improve code that you touch in the course of your normal work, just like any other refactoring. Remember that you don’t need permission from anyone to keep your code clean, as this is the only way to keep development sustainable.</p> 2577 2578 2579 2580<p class="wp-block-paragraph"><strong>Frameworks</strong> are harder to deal with, since a framework is in control of the application and directs our code. It defines the rules and we have no choice but to modify our code if those rules change. That’s the trade-off we accepted when we opted to use the framework. Upgrading Spring Boot across major (or even minor) versions has historically been a pain, for example, but we accept that because the framework saves us a lot of time on a daily basis. There isn’t a silver bullet here, so be careful what you’re getting yourself into. Making a good trade-off analysis and recording it in an ADR is about the best we can do.</p> 2581 2582 2583 2584<p class="wp-block-paragraph"><strong>Libraries</strong> are a bit simpler because they impact the application less than frameworks. Still, there is a lot of variation in their impact. A library for a cross-cutting concern like logging may show up in many places, while one for generating passwords sees more limited use.</p> 2585 2586 2587 2588<p class="wp-block-paragraph">Much has been written about keeping <strong>application code</strong> easy to change. Follow the <a rel="noreferrer noopener" href="https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf" target="_blank">SOLID</a> (or <a rel="noreferrer noopener" href="https://www.infoq.com/articles/microservices-design-ideals/" target="_blank">IDEALS</a>) principles and employ the correct design patterns. If you do, then basically every piece of code treats every other piece of code as a library with a well-defined API.</p> 2589 2590 2591 2592<p class="wp-block-paragraph"><strong>Infrastructure</strong> can also be impactful. Luckily, work like upgrading databases, queues, and Kubernetes clusters can often economically be outsourced to cloud vendors. From the perspective of the application, that reduces infrastructure to a library as well. Obviously there is more to switching cloud vendors than switching libraries, like updating <a rel="noreferrer noopener" href="https://en.wikipedia.org/wiki/Infrastructure_as_code" target="_blank">infrastructure as code</a>, but from an application code perspective the difference is minimal.</p> 2593 2594 2595 2596<p class="wp-block-paragraph">This analysis shows that if we can figure out how to deal with changes in libraries, we would be able to effectively handle most changes that increase tech debt.</p> 2597 2598 2599 2600<h2 class="wp-block-heading">Hexagonal Architecture to the rescue</h2> 2601 2602 2603 2604<p class="wp-block-paragraph">Luckily, the solution to this problem is fairly straightforward: <strong>localize dependencies</strong>
2604. If only a small part of your code depends on a library, then any changes in that library can only affect that part of the code. A structured way of doing that is using a Hexagonal Architecture.</p> 2605 2606 2607 2608<p class="wp-block-paragraph"><a rel="noreferrer noopener" href="http://alistair.cockburn.us/Hexagonal+architecture" target="_blank">Hexagonal Architecture</a> (aka Ports & Adapters) is an approach to localize dependencies. Variations are <a rel="noreferrer noopener" href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/" target="_blank">Onion Architecture</a> and <a rel="noreferrer noopener" href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank">Clean Architecture</a>. The easiest way to explain Hexagonal Architecture is to compare it to a more traditional three layer architecture:</p> 2609 2610 2611<div class="wp-block-image"> 2612<figure class="aligncenter size-large"><img data-attachment-id="8000" data-permalink="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/hexagonal-architecture/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png" data-orig-size="1280,720" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="hexagonal-architecture" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=1024" loading="lazy" width="1024" height="576" src="https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=1024" alt="" class="wp-image-8000" srcset="https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=1024 1024w, https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=240 240w, https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png?w=768 768w, https://raysinnema.blog/wp-content/uploads/2022/08/hexagonal-architecture.png 1280w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Three layer vs hexagonal architecture</figcaption></figure> 2613</div> 2614 2615 2616<p class="wp-block-paragraph">A three layer architecture separates code into layers that may only communicate “downward”. In particular, the business logic depends on the data access layer. Hexagonal Architecture replaces the notion of downward dependencies with inward ones. The essence of the application, the business logic, sits nicely in the middle of the visualization. Data access code depends on the business logic, instead of the other way around.</p> 2617 2618 2619 2620<p class="wp-block-paragraph">The inversion of dependencies between business logic and data access is implemented using <strong>ports</strong> (interfaces) and <strong>adapters</strong> (implementations of those interfaces). For example, accounting business logic may define an output port <code>AccountRepository</code> for storing and loading <code>Account</code> objects. If you’re using MySQL to store those <code>Account</code>s, then a <code>MySqlAccountRepository</code> adapter implements the <code>AccountRepository</code> port.</p> 2621 2622 2623 2624<p class="wp-block-paragraph">When you need to upgrade MySQL, changes are limited to the adapter. If you ever wanted to replace MySQL with some other data access technology, you’d simply add a new adapter and decommission the old one. You can even have both adapters live side by side for a while and <a rel="noreferrer noopener" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html" target="_blank">activate</a> one or the other depending on the environment the application is running in. This makes testing the migration easier.</p> 2625 2626 2627 2628<p class="wp-block-paragraph">You can use ports and adapters for more than data access, however. Need to use logging? Define a <code>Logging</code> port and an adapter for Log4J or whatever your preferred logging library is. Same for building PDFs, generating passwords, or really anything you’d want to use a library for.</p> 2629 2630 2631 2632<p class="wp-block-paragraph">This approach has other benefits as well.</p> 2633 2634 2635 2636<p class="wp-block-paragraph">Your code no longer has to suffer from poor APIs offered by the library, since you can design the port such that it makes sense to you. For example, you can use names that match your context and put method parameters in <a rel="noreferrer noopener" href="https://stackoverflow.com/questions/2404978/why-are-assertequals-parameters-in-the-order-expected-actual" target="_blank">the order you prefer</a>. You can reduce cognitive load by only exposing the subset of the functionality of the library that you need for your application. If you document the port, team members no longer need to look at the library’s documentation. (Unless they’re studying the adapter’s implementation, of course.)</p> 2637 2638 2639 2640<p class="wp-block-paragraph">Testing also becomes easier. Since a port is an interface, mocking the use of the library becomes trivial. You can write an abstract test against the port’s interface and derive concrete tests for the adapters that do nothing more than instantiate the adapter under test. Such contract tests ensure a smooth transition from one adapter of the port to the next, since the tests prove that both adapters work the same way.</p> 2641 2642 2643 2644<h2 class="wp-block-heading">Adopting Hexagonal Architecture</h2> 2645 2646 2647<div class="wp-block-image"> 2648<figure class="alignleft size-large is-resized"><img data-attachment-id="8008" data-permalink="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/implementation/" data-orig-file="https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png" data-orig-size="1200,1200" data-comments-opened="1" data-image-meta="{"aperture":"0","credit":"","camera":"","caption":"","created_timestamp":"0","copyright":"","focal_length":"0","iso":"0","shutter_speed":"0","title":"","orientation":"0"}" data-image-title="implementation" data-image-description="" data-image-caption="" data-large-file="https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=1024" loading="lazy" src="https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=1024" alt="" class="wp-image-8008" width="177" height="177" srcset="https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=177 177w, https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=354 354w, https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=160 160w, https://raysinnema.blog/wp-content/uploads/2022/08/implementation.png?w=240 240w" sizes="auto, (max-width: 177px) 100v
2648w, 177px" /></figure> 2649</div> 2650 2651 2652<p class="wp-block-paragraph">By now the benefits of Hexagonal Architecture should be clear. Some developers, however, are put off by the need to create separate ports, especially for trivial things. Many would balk at designing their own logging interface, for example. Luckily, this is not an all-or-nothing decision. You can make a trade-off analysis per library.</p> 2653 2654 2655 2656<p class="wp-block-paragraph">With Hexagonal Architecture, code constantly needs to be passed the ports it uses. A <a rel="noreferrer noopener" href="https://en.wikipedia.org/wiki/Dependency_injection" target="_blank">dependency injection</a> framework can automate that.</p> 2657 2658 2659 2660<p class="wp-block-paragraph">It also helps to have a naming convention for the modules of your code, like packages in Java. </p> 2661 2662 2663 2664<p class="wp-block-paragraph">Here’s what we used on a recent Spring Boot application (*) I was involved with:</p> 2665 2666 2667 2668<ul class="wp-block-list"><li><code>application.services</code><br>The <code>@SpringBootAp
2668plication</code> class and related plumbing (like Spring Security configuration) to wire up and start the application.</li><li><code>domain.model</code><br>Types that represent fundamental concepts in the domain with associated behavior.</li><li><code>domain.services</code><br>Functionality that crosses domain model boundaries. It implements input ports and uses output ports.</li><li><code>ports.in</code><br>Input ports that offer abstractions of domain services to input mechanisms like <code>@Scheduled</code> jobs and <code>@Controller</code>s.</li><li><code>ports.out</code><br>Output ports that offer abstractions of technical services to the domain services.</li><li><code>infra</code><br>Infrastructure that implements output ports, IOW adapters. Packages in here represent either technology directly, like <code>infra.pubsub</code>, or indirectly for some functionality, like <code>infra.store.gcs</code>. The latter form allows competing implementations to live next to each other.</li><li><code>ui</code><br>Interface into the application, like its user and programmatic interfaces, and any scheduled jobs.</li></ul> 2669 2670 2671 2672<p class="wp-block-paragraph"><em>(*) All the examples in this post are from the same application. This doesn’t mean that Hexagonal Architecture is only suitable for Java, or even Spring, applications. It can be applied anywhere.</em></p> 2673 2674 2675 2676<p class="wp-block-paragraph">Note that this package structure is a form of <a rel="noreferrer noopener" href="https://dzone.com/articles/package-your-classes-feature" target="_blank">package by layer</a>. It therefore works best for microservices, where you’ve implicitly already done packaging by feature on the service level. If you have a monolith, it makes sense for top-level packages to be about the domains and sub-packages to be split out like above.</p> 2677 2678 2679 2680<p class="wp-block-paragraph">You only realize the benefits of Hexagonal Architecture if you have the discipline to adhere to it. You can use an <a rel="noreferrer noopener" href="https://smile.amazon.com/Building-Evolutionary-Architectures-Support-Constant-ebook-dp-B075RR1XVG/dp/B075RR1XVG/ref=mt_other?_encoding=UTF8&me=&qid=1659436498" target="_blank">architectural fitness function</a> to ensure that it’s followed. A tool like <a rel="noreferrer noopener" href="https://www.archunit.org/" target="_blank">ArchUnit</a> can automate such a fitness function, especially if you have a naming convention.</p> 2681 2682 2683 2684<p class="wp-block-paragraph"><em>What do you think? Does it sound like Hexagonal Architecture could improve your ability to keep tech debt low? Have you used it and <strong>not</strong> liked it? Please leave a comment below.</em></p> 2685 </div><!-- .entry-content --> 2686 2687 <footer class="entry-footer"> 2688 2689 <div class="entry-meta"> 2690 <span class="byline"> 2691 <a href="https://raysinnema.blog/author/sinnema313/" rel="author">Ray Sinnema</a> </span> 2692 <span class="cat-links"> 2693 <a href="https://raysinnema.blog/category/architecture/" rel="category tag">architecture</a> </span><!-- .cat-links --> 2694 2695 <span class="comments-link"><a href="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/#respond">Leave a comment</a></span><!-- .comments-link --> 2696 <span class="published-on"> 2697 <a href="https://raysinnema.blog/2022/08/03/hexagonal-architecture-helps-keep-tech-debt-low/" rel="bookmark"><time class="entry-date published" datetime="2022-08-03T13:00:00-06:00">2022-08-03</time><time class="updated" datetime="2022-08-02T21:34:23-06:00">2022-08-02</time></a> 2698 </span> 2699 2700 <span class="word-count">7 Minutes</span> </div><!-- .entry-meta --> 2701 </footer><!-- .entry-footer --> 2702</article><!-- #post-## --> 2703 2704 2705 2706 <nav class="navigation posts-navigation" aria-label="Posts"> 2707 <h2 class="screen-reader-text">Posts navigation</h2> 2708 <div class="nav-links"><div class="nav-previous"><a href="https://raysinnema.blog/author/sinnema313/page/2/" >Older posts</a></div></div> 2709 </nav> 2710 2711 </main><!-- #main --> 2712 </div><!-- #primary --> 2713 2714 2715<div id="secondary" class="widget-area" role="complementary"> 2716 <aside id="text-161921954" class="widget widget_text"><h1 class="widget-title">Subscribe</h1> <div class="textwidget"><table style="width:100%;"><tr><td style="width:50%;text-align:center;float:center;"><a href="http://feeds.feedburner.com/SecureSoftwareDevelopment" title="Subscribe to my feed" rel="alternate"><img src="https://i0.wp.com/www.feedburner.com/fb/images/pub/feed-icon32x32.png" alt="" style="border:0;" /></a></td><td style="text-align:center;float:center;"><a href="http://feedburner.google.com/fb/a/mailverify?uri=SecureSoftwareDevelopment&loc=en_US">
2716<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSnEZNs2QkPF0JROk7vD7bJd_ZsGbdToaHRHqzNs4RIt5XnJJC_" width="32" height="32" /></a></td></tr></table></div> 2717 </aside><aside id="search-3" class="widget widget_search"><form role="search" method="get" class="search-form" action="https://raysinnema.blog/"> 2718 <label> 2719 <span class="screen-reader-text">Search for:</span> 2720 <input type="search" class="search-field" placeholder="Search …" value="" name="s" /> 2721 </label> 2722 <input type="submit" class="search-submit" value="Search" /> 2723 </form></aside><aside id="text-161921951" class="widget widget_text"><h1 class="widget-title">Disclaimer</h1> <div class="textwidget"><em><font size="2">The opinions expressed here are my personal opinions. Content published here is not read or approved in advance by VelocityEHS and does not necessarily reflect the views and opinions of VelocityEHS nor does it constitute any official communication of VelocityEHS.</font></em> 2724 2725</div> 2726 </aside><aside id="top-posts-3" class="widget widget_top-posts"><h1 class="widget-title">Top Posts & Pages</h1><ul><li><a href="https://raysinnema.blog/2008/06/16/the-law-of-demeter/" class="bump-view" data-bump-view="tp">The Law of Demeter</a></li><li><a href="https://raysinnema.blog/2013/03/25/likely-candidates-for-frictionless-development-environments/" class="bump-view" data-bump-view="tp">Likely Candidates for Frictionless Development Environments</a></li><li><a href="https://raysinnema.blog/2009/05/27/kanban/" class="bump-view" data-bump-view="tp">Kanban</a></li><li><a href="https://raysinnema.blog/2011/12/05/practicing-tdd-using-the-roman-numerals-kata/" class="bump-view" data-bump-view="tp">Practicing TDD using the Roman Numerals kata</a></li><li><a href="https://raysinnema.blog/2012/09/03/a-classification-of-tests/" class="bump-view" data-bump-view="tp">A Classification of Tests</a></li></ul></aside><aside id="wp_tag_cloud-2" class="widget wp_widget_tag_cloud"><h1 class="widget-title"></h1><a href="https://raysinnema.blog/tag/agile/" class="tag-cloud-link tag-link-11287 tag-link-position-1" style="font-size: 15.333333333333pt;" aria-label="Agile (12 items)">Agile</a> 2727<a href="https://raysinnema.blog/tag/ant/" class="tag-cloud-link tag-link-56113 tag-link-position-2" style="font-size: 10.666666666667pt;" aria-label="Ant (7 items)">Ant</a> 2728<a href="https://raysinnema.blog/tag/automation/" class="tag-cloud-link tag-link-44070 tag-link-position-3" style="font-size: 9.5555555555556pt;" aria-label="automation (6 items)">automation</a> 2729<a href="https://raysinnema.blog/tag/cloud-computing-2/" class="tag-cloud-link tag-link-35123767 tag-link-position-4" style="font-size: 12.888888888889pt;" aria-label="cloud computing (9 items)">cloud computing</a> 2730<a href="https://raysinnema.blog/tag/design-pattern/" class="tag-cloud-link tag-link-36769 tag-link-position-5" style="font-size: 9.5555555555556pt;" aria-label="design pattern (6 items)">design pattern</a> 2731<a href="https://raysinnema.blog/tag/eclipse/" class="tag-cloud-link tag-link-18466 tag-link-position-6" style="font-size: 18.666666666667pt;" aria-label="Eclipse (17 items)">Eclipse</a> 2732<a href="https://raysinnema.blog/tag/factory/" class="tag-cloud-link tag-link-11189 tag-link-position-7" style="font-size: 8pt;" aria-label="factory (5 items)">factory</a> 2733<a href="https://raysinnema.blog/tag/fde/" class="tag-cloud-link tag-link-2585220 tag-link-position-8" style="font-size: 9.5555555555556pt;" aria-label="FDE (6 items)">FDE</a> 2734<a href="https://raysinnema.blog/tag/gradle/" class="tag-cloud-link tag-link-8644597 tag-link-position-9" style="font-size: 9.5555555555556pt;" aria-label="Gradle (6 items)">Gradle</a> 2735<a href="https://raysinnema.blog/tag/http/" class="tag-cloud-link tag-link-10518 tag-link-position-10" style="font-size: 11.777777777778pt;" aria-label="HTTP (8 items)">HTTP</a> 2736<a href="https://raysinnema.blog/tag/java/" class="tag-cloud-link tag-link-1017 tag-link-position-11" style="font-size: 15.333333333333pt;" aria-label="Java (12 items)">Java</a> 2737<a href="https://raysinnema.blog/tag/javafx/" class="tag-cloud-link tag-link-1078878 tag-link-position-12" style="font-size: 8pt;" aria-label="JavaFX (5 items)">JavaFX</a> 2738<a href="https://raysinnema.blog/tag/javascript/" class="tag-cloud-link tag-link-457 tag-link-position-13" style="font-size: 8pt;" aria-label="JavaScript (5 items)">JavaScript</a> 2739<a href="https://raysinnema.blog/tag/json/" class="tag-cloud-link tag-link-72721 tag-link-position-14" style="font-size: 13.777777777778pt;" aria-label="JSON (10 items)">JSON</a> 2740<a href="https://raysinnema.blog/tag/kata/" class="tag-cloud-link tag-link-133657 tag-link-position-15" style="font-size: 11.777777777778pt;" aria-label="kata (8 items)">kata</a> 2741<a href="https://raysinnema.blog/tag/mason/" class="tag-cloud-link tag-link-272996 tag-link-position-16" style="font-size: 9.5555555555556pt;" aria-label="Mason (6 items)">Mason</a> 2742<a href="https://raysinnema.blog/tag/media-type/" class="tag-cloud-link tag-link-139422 tag-link-position-17" style="font-size: 12.888888888889pt;" aria-label="media type (9 items)">media type</a> 2743<a href="https://raysinnema.blog/tag/osgi/" class="tag-cloud-link tag-link-5165 tag-link-position-18" style="font-size: 12.888888888889pt;" aria-label="OSGi (9 items)">OSGi</a> 2744<a href="https://raysinnema.blog/tag/performance/" class="tag-cloud-link tag-link-1930 tag-link-position-19" style="font-size: 8pt;" aria-label="performance (5 items)">performance</a> 2745<a href="https://raysinnema.blog/tag/plug-in/" class="tag-cloud-link tag-link-274272 tag-link-position-20" style="font-size: 8pt;" aria-label="plug-in (5 items)">plug-in</a> 2746<a href="https://raysinnema.blog/tag/refactoring/" class="tag-cloud-link tag-link-54712 tag-link-position-21" style="font-size: 13.777777777778pt;
2746" aria-label="refactoring (10 items)">refactoring</a> 2747<a href="https://raysinnema.blog/tag/rest/" class="tag-cloud-link tag-link-15314 tag-link-position-22" style="font-size: 20pt;" aria-label="REST (20 items)">REST</a> 2748<a href="https://raysinnema.blog/tag/roman-numerals/" class="tag-cloud-link tag-link-377955 tag-link-position-23" style="font-size: 9.5555555555556pt;" aria-label="Roman numerals (6 items)">Roman numerals</a> 2749<a href="https://raysinnema.blog/tag/sdl/" class="tag-cloud-link tag-link-138753 tag-link-position-24" style="font-size: 11.777777777778pt;" aria-label="SDL (8 items)">SDL</a> 2750<a href="https://raysinnema.blog/tag/security/" class="tag-cloud-link tag-link-801 tag-link-position-25" style="font-size: 12.888888888889pt;" aria-label="security (9 items)">security</a> 2751<a href="https://raysinnema.blog/tag/siren/" class="tag-cloud-link tag-link-179604 tag-link-position-26" style="font-size: 10.666666666667pt;" aria-label="Siren (7 items)">Siren</a> 2752<a href="https://raysinnema.blog/tag/tdd/" class="tag-cloud-link tag-link-76604 tag-link-position-27" style="font-size: 22pt;" aria-label="TDD (25 items)">TDD</a> 2753<a href="https://raysinnema.blog/tag/threat-modeling/" class="tag-cloud-link tag-link-4009294 tag-link-position-28" style="font-size: 9.5555555555556pt;" aria-label="threat modeling (6 items)">threat modeling</a> 2754<a href="https://raysinnema.blog/tag/xacml/" class="tag-cloud-link tag-link-1081110 tag-link-position-29" style="font-size: 20pt;" aria-label="XACML (20 items)">XACML</a> 2755<a href="https://raysinnema.blog/tag/xml/" class="tag-cloud-link tag-link-6852 tag-link-position-30" style="font-size: 15.333333333333pt;" aria-label="XML (12 items)">XML</a></aside></div><!-- #secondary --> 2756 2757 </div><!-- #content --> 2758 2759 <footer id="colophon" class="site-footer" role="contentinfo"> 2760 <div class="site-info"> 2761 <a href="https://wordpress.com/?ref=footer_blog" rel="nofollow">Blog at WordPress.com.</a> 2762 2763 </div><!-- .site-info --> 2764 </footer><!-- #colophon --> 2765 </div><!-- #content-wrapper --> 2766</div><!-- #page --> 2767 2768
2768<script type="text/javascript"> 2769 var infiniteScroll = {"settings":{"id":"main","ajaxurl":"https://raysinnema.blog/?infinity=scrolling","type":"scroll","wrapper":true,"wrapper_class":"infinite-wrap","footer":"page","click_handle":"1","text":"Older posts","totop":"Scroll back to top","currentday":"03.08.22","order":"DESC","scripts":[],"styles":[],"google_analytics":false,"offset":1,"history":{"host":"raysinnema.blog","path":"/author/sinnema313/page/%d/","use_trailing_slashes":true,"parameters":""},"query_args":{"author_name":"sinnema313","error":"","m":"","p":0,"post_parent":"","subpost":"","subpost_id":"","attachment":"","attachment_id":0,"name":"","pagename":"","page_id":0,"second":"","minute":"","hour":"","day":0,"monthnum":0,"year":0,"w":0,"category_name":"","tag":"","cat":"","tag_id":"","author":4041491,"feed":"","tb":"","paged":0,"meta_key":"","meta_value":"","preview":"","s":"","sentence":"","title":"","fields":"all","menu_order":"","embed":"","category__in":[],"category__not_in":[],"category__and":[],"post__in":[],"post__not_in":[],"post_name__in":[],"tag__in":[],"tag__not_in":[],"tag__and":[],"tag_slug__in":[],"tag_slug__and":[],"post_parent__in":[],"post_parent__not_in":[],"author__in":[],"author__not_in":[],"search_columns":[],"lazy_load_term_meta":false,"posts_per_page":10,"ignore_sticky_posts":false,"suppress_filters":false,"cache_results":true,"update_post_term_cache":true,"update_menu_item_cache":false,"update_post_meta_cache":true,"post_type":"","nopaging":false,"comments_per_page":"0","no_found_rows":false,"order":"DESC"},"query_before":"2026-09-19 22:20:59","last_post_date":"2022-08-03 13:00:00","body_class":"infinite-scroll neverending","loading_text":"Loading new page","stats":"blog=3802920\u0026v=wpcom\u0026tz=-6\u0026user_id=0\u0026arch_author=sinnema313\u0026arch_results=10\u0026subd=sinnema313\u0026x_pagetype=infinite"}}; 2770 </script>
2770 2771 <!-- -->
2772<script type="speculationrules"> 2773{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/files/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/pub/independent-publisher-2/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]} 2774</script>
vendor: 1 bytes, line 2774
2774
2775<script id="grofiles-cards-js" src="//0.gravatar.com/js/hovercards/hovercards.min.js?ver=202638d1133547e8a1f2801609f51cef12522abe7f6647d869e9fbc974fe356a37e613"></script>
vendor: 1 bytes, line 2775
2775
2776<script id="wpgroho-js-extra"> 2777var WPGroHo = {"my_hash":""}; 2778//# sourceURL=wpgroho-js-extra 2779</script>
vendor: 1 bytes, line 2779
2779
2780<script crossorigin='anonymous' type='text/javascript' src='/wp-content/mu-plugins/gravatar-hovercards/wpgroho.js?m=1610363240i'></script>
2780 2781 2782
2782<script> 2783 // Initialize and attach hovercards to all gravatars 2784 ( function() { 2785 function init() { 2786 if ( typeof Gravatar === 'undefined' ) { 2787 return; 2788 } 2789 2790 if ( typeof Gravatar.init !== 'function' ) { 2791 return; 2792 } 2793 2794 Gravatar.profile_cb = function ( hash, id ) { 2795 WPGroHo.syncProfileData( hash, id ); 2796 }; 2797 2798 Gravatar.my_hash = WPGroHo.my_hash; 2799 Gravatar.init( 2800 'body', 2801 '#wp-admin-bar-my-account', 2802 { 2803 i18n: { 2804 'Edit your profile →': 'Edit your profile →', 2805 'View profile →': 'View profile →', 2806 'Contact': 'Contact', 2807 'Send money': 'Send money', 2808 'Sorry, we are unable to load this Gravatar profile.': 'Sorry, we are unable to load this Gravatar profile.', 2809 'Gravatar not found.': 'Gravatar not found.', 2810 'Too Many Requests.': 'Too Many Requests.', 2811 'Internal Server Error.': 'Internal Server Error.', 2812 'Is this you?': 'Is this you?', 2813 'Claim your free profile.': 'Claim your free profile.', 2814 'Email': 'Email', 2815 'Home Phone': 'Home Phone', 2816 'Work Phone': 'Work Phone', 2817 'Cell Phone': 'Cell Phone', 2818 'Contact Form': 'Contact Form', 2819 'Calendar': 'Calendar', 2820 }, 2821 } 2822 ); 2823 } 2824 2825 if ( document.readyState !== 'loading' ) { 2826 init(); 2827 } else { 2828 document.addEventListener( 'DOMContentLoaded', init ); 2829 } 2830 } )(); 2831 </script>
2831 2832 2833 <div style="display:none"> 2834 </div> 2835 <div id="infinite-footer"> 2836 <div class="container"> 2837 <div class="blog-info"> 2838 <a id="infinity-blog-title" href="https://raysinnema.blog/" rel="home"> 2839 Ray on Software Architecture </a> 2840 </div> 2841 <div class="blog-credits"> 2842 <a href="https://wordpress.com/?ref=footer_blog" rel="nofollow">Blog at WordPress.com.</a> </div> 2843 </div> 2844 </div><!-- #infinite-footer --> 2845 <div id="actionbar" dir="ltr" style="display: none;" 2846 class="actnbr-pub-independent-publisher-2 actnbr-has-follow actnbr-has-actions"> 2847 <ul> 2848 <li class="actnbr-btn actnbr-hidden"> 2849 <a class="actnbr-action actnbr-actn-follow " href=""> 2850 <svg class="gridicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path clip-rule="evenodd" d="m4 4.5h12v6.5h1.5v-6.5-1.5h-1.5-12-1.5v1.5 10.5c0 1.1046.89543 2 2 2h7v-1.5h-7c-.27614 0-.5-.2239-.5-.5zm10.5 2h-9v1.5h9zm-5 3h-4v1.5h4zm3.5 1.5h-1v1h1zm-1-1.5h-1.5v1.5 1 1.5h1.5 1 1.5v-1.5-1-1.5h-1.5zm-2.5 2.5h-4v1.5h4zm6.5 1.25h1.5v2.25h2.25v1.5h-2.25v2.25h-1.5v-2.25h-2.25v-1.5h2.25z" fill-rule="evenodd"></path></svg> 2851 <span>Subscribe</span> 2852 </a> 2853 <a class="actnbr-action actnbr-actn-following no-display" href=""> 2854 <svg class="gridicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M16 4.5H4V15C4 15.2761 4.22386 15.5 4.5 15.5H11.5V17H4.5C3.39543 17 2.5 16.1046 2.5 15V3H17.5V12.5H16V4.5ZM5.5 6.5H14.5V8H5.5V6.5ZM5.5 9.5H9.5V11H5.5V9.5ZM12 11H13V12H12V11ZM10.5 9.5H14.5V13.5H10.5V9.5ZM5.5 12H9.5V13.5H5.5V12Z" fill="#008A20"></path><path d="M19.5303 15.0303L15.5 19.0605L12.9697 16.5303L14.0303 15.4697L15.5 16.9395L18.4697 13.9697L19.5303 15.0303Z" fill="#008A20"></path></svg> 2855 <span>Subscribed</span> 2856 </a> 2857 <div class="actnbr-popover tip tip-top-left actnbr-notice" id="follow-bubble"> 2858 <div class="tip-arrow"></div> 2859 <div class="tip-inner actnbr-follow-bubble"> 2860 <ul> 2861 <li class="actnbr-sitename"> 2862 <a href="https://raysinnema.blog"> 2863 <img loading='lazy' alt='' src='https://s2.wp.com/i/logo/wpcom-gray-white.png?m=1479929237i' srcset='https://s2.wp.com/i/logo/wpcom-gray-white.png 1x' class='avatar avatar-50' height='50' width='50' /> Ray on Software Architecture </a> 2864 </li> 2865 <div class="actnbr-message no-display"></div> 2866 <form method="post" action="https://subscribe.wordpress.com" accept-charset="utf-8" style="display: none;"> 2867 <div class="actnbr-follow-count">Join 45 other subscribers</div> 2868 <div> 2869 <input type="email" name="email" placeholder="Enter your email address" class="actnbr-email-field" aria-label="Enter your email address" /> 2870 </div> 2871 <input type="hidden" name="action" value="subscribe" /> 2872 <input type="hidden" name="blog_id" value="3802920" /> 2873 <input type="hidden" name="source" value="https://raysinnema.blog/author/sinnema313/" /> 2874 <input type="hidden" name="sub-type" value="actionbar-follow" /> 2875 <input type="hidden" id="_wpnonce" name="_wpnonce" value="fbe14665e2" /> <div class="actnbr-button-wrap"> 2876 <button type="submit" value="Sign me up"> 2877 Sign me up </button> 2878 </div> 2879 </form> 2880 <li class="actnbr-login-nudge"> 2881 <div> 2882 Already have a WordPress.com account? <a href="https://wordpress.com/log-in?redirect_to=https%3A%2F%2Fr-login.wordpress.com%2Fremote-login.php%3Faction%3Dlink%26back%3Dhttps%253A%252F%252Fraysinnema.blog%252F2026%252F01%252F12%252Fsolve-problems-with-systems-thinking%252F">Log in now.</a> </div> 2883 </li> 2884 </ul> 2885 </div> 2886 </div> 2887 </li> 2888 <li class="actnbr-ellipsis actnbr-hidden"> 2889 <svg class="gridicon gridicons-ellipsis" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M7 12c0 1.104-.896 2-2 2s-2-.896-2-2 .896-2 2-2 2 .896 2 2zm12-2c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2zm-7 0c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2z"/></g></svg> <div class="actnbr-popover tip tip-top-left actnbr-more"> 2890 <div class="tip-arrow"></div> 2891 <div class="tip-inner"> 2892 <ul> 2893 <li class="actnbr-sitename"> 2894 <a href="https://raysinnema.blog">
2895 <img loading='lazy' alt='' src='https://s2.wp.com/i/logo/wpcom-gray-white.png?m=1479929237i' srcset='https://s2.wp.com/i/logo/wpcom-gray-white.png 1x' class='avatar avatar-50' height='50' width='50' /> Ray on Software Architecture </a> 2896 </li> 2897 <li class="actnbr-folded-follow"> 2898 <a class="actnbr-action actnbr-actn-follow " href=""> 2899 <svg class="gridicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path clip-rule="evenodd" d="m4 4.5h12v6.5h1.5v-6.5-1.5h-1.5-12-1.5v1.5 10.5c0 1.1046.89543 2 2 2h7v-1.5h-7c-.27614 0-.5-.2239-.5-.5zm10.5 2h-9v1.5h9zm-5 3h-4v1.5h4zm3.5 1.5h-1v1h1zm-1-1.5h-1.5v1.5 1 1.5h1.5 1 1.5v-1.5-1-1.5h-1.5zm-2.5 2.5h-4v1.5h4zm6.5 1.25h1.5v2.25h2.25v1.5h-2.25v2.25h-1.5v-2.25h-2.25v-1.5h2.25z" fill-rule="evenodd"></path></svg> 2900 <span>Subscribe</span> 2901 </a> 2902 <a class="actnbr-action actnbr-actn-following no-display" href=""> 2903 <svg class="gridicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M16 4.5H4V15C4 15.2761 4.22386 15.5 4.5 15.5H11.5V17H4.5C3.39543 17 2.5 16.1046 2.5 15V3H17.5V12.5H16V4.5ZM5.5 6.5H14.5V8H5.5V6.5ZM5.5 9.5H9.5V11H5.5V9.5ZM12 11H13V12H12V11ZM10.5 9.5H14.5V13.5H10.5V9.5ZM5.5 12H9.5V13.5H5.5V12Z" fill="#008A20"></path><path d="M19.5303 15.0303L15.5 19.0605L12.9697 16.5303L14.0303 15.4697L15.5 16.9395L18.4697 13.9697L19.5303 15.0303Z" fill="#008A20"></path></svg> 2904 <span>Subscribed</span> 2905 </a> 2906 </li> 2907 <li class="actnbr-signup"><a href="https://wordpress.com/start/">Sign up</a></li> 2908 <li class="actnbr-login"><a href="https://wordpress.com/log-in?redirect_to=https%3A%2F%2Fr-login.wordpress.com%2Fremote-login.php%3Faction%3Dlink%26back%3Dhttps%253A%252F%252Fraysinnema.blog%252F2026%252F01%252F12%252Fsolve-problems-with-systems-thinking%252F">Log in</a></li> 2909 <li class="flb-report"> 2910 <a href="https://wordpress.com/abuse/?report_url=https://raysinnema.blog" target="_blank" rel="noopener noreferrer"> 2911 Report this content </a> 2912 </li> 2913 <li class="actnbr-reader"> 2914 <a href="https://wordpress.com/reader/feeds/176241278"> 2915 View site in Reader </a> 2916 </li> 2917 <li class="actnbr-subs"> 2918 <a href="https://subscribe.wordpress.com/">Manage subscriptions</a> 2919 </li> 2920 <li class="actnbr-fold"><a href="">Collapse this bar</a></li> 2921 </ul> 2922 </div> 2923 </div> 2924 </li> 2925 </ul> 2926 </div> 2927
2928<script> 2929window.addEventListener( "DOMContentLoaded", function( event ) { 2930 var link = document.createElement( "link" ); 2931 link.href = "/wp-content/mu-plugins/actionbar/actionbar.css?v=20250116"; 2932 link.type = "text/css"; 2933 link.rel = "stylesheet"; 2934 document.head.appendChild( link ); 2935 2936 var script = document.createElement( "script" ); 2937 script.src = "/wp-content/mu-plugins/actionbar/actionbar.js?v=20250204"; 2938 document.body.appendChild( script ); 2939} ); 2940</script>
2940 2941 2942 <div id="jp-carousel-loading-overlay" style="display: none;"> 2943 <div id="jp-carousel-loading-wrapper"> 2944 <span id="jp-carousel-library-loading"><svg class="jetpack-spinner" width="40" height="40" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><circle cx="50" cy="50" r="46" fill="none" stroke="#ddd" stroke-width="8"/><path d="M 50 4 A 46 46 0 0 1 96 50" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" dur="1.4s" from="0 50 50" to="360 50 50" repeatCount="indefinite"/></path></svg></span> 2945 </div> 2946 </div> 2947 <div class="jp-carousel-overlay jp-carousel-light" style="display: none;"> 2948 2949 <div class="jp-carousel-container jp-carousel-light"> 2950 <!-- The Carousel Swiper --> 2951 <div 2952 class="jp-carousel-wrap swiper jp-carousel-swiper-container jp-carousel-transitions" 2953 itemscope 2954 itemtype="https://schema.org/ImageGallery"> 2955 <div class="jp-carousel swiper-wrapper"></div> 2956 <div class="jp-swiper-button-prev swiper-button-prev"> 2957 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 2958 <mask id="maskPrev" mask-type="alpha" maskUnits="userSpaceOnUse" x="8" y="6" width="9" height="12"> 2959 <path d="M16.2072 16.59L11.6496 12L16.2072 7.41L14.8041 6L8.8335 12L14.8041 18L16.2072 16.59Z" fill="white"/> 2960 </mask> 2961 <g mask="url(#maskPrev)"> 2962 <rect x="0.579102" width="23.8823" height="24" fill="#FFFFFF"/> 2963 </g> 2964 </svg> 2965 </div> 2966 <div class="jp-swiper-button-next swiper-button-next"> 2967 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 2968 <mask id="maskNext" mask-type="alpha" maskUnits="userSpaceOnUse" x="8" y="6" width="8" height="12"> 2969 <path d="M8.59814 16.59L13.1557 12L8.59814 7.41L10.0012 6L15.9718 12L10.0012 18L8.59814 16.59Z" fill="white"/> 2970 </mask> 2971 <g mask="url(#maskNext)"> 2972 <rect x="0.34375" width="23.8822" height="24" fill="#FFFFFF"/> 2973 </g> 2974 </svg> 2975 </div> 2976 </div> 2977 <!-- The main close buton --> 2978 <div class="jp-carousel-close-hint"> 2979 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 2980 <mask id="maskClose" mask-type="alpha" maskUnits="userSpaceOnUse" x="5" y="5" width="15" height="14"> 2981 <path d="M19.3166 6.41L17.9135 5L12.3509 10.59L6.78834 5L5.38525 6.41L10.9478 12L5.38525 17.59L6.78834 19L12.3509 13.41L17.9135 19L19.3166 17.59L13.754 12L19.3166 6.41Z" fill="white"/> 2982 </mask> 2983 <g mask="url(#maskClose)"> 2984 <rect x="0.409668" width="23.8823" height="24" fill="#FFFFFF"/> 2985 </g> 2986 </svg> 2987 </div> 2988 <!-- Image info, comments and meta --> 2989 <div class="jp-carousel-info"> 2990 <div class="jp-carousel-info-footer"> 2991 <div class="jp-carousel-pagination-container"> 2992 <div class="jp-swiper-pagination swiper-pagination"></div> 2993 <div class="jp-carousel-pagination"></div> 2994 </div> 2995 <div class="jp-carousel-photo-title-container"> 2996 <div class="jp-carousel-photo-caption"></div> 2997 </div> 2998 <div class="jp-carousel-photo-icons-container"> 2999 <a href="#" class="jp-carousel-icon-btn jp-carousel-icon-info" aria-label="Toggle photo metadata visibility"> 3000 <span class="jp-carousel-icon"> 3001 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3002 <mask id="maskInfo" mask-type="alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="21" height="20"> 3003 <path fill-rule="evenodd" clip-rule="evenodd" d="M12.7537 2C7.26076 2 2.80273 6.48 2.80273 12C2.80273 17.52 7.26076 22 12.7537 22C18.2466 22 22.7046 17.52 22.7046 12C22.7046 6.48 18.2466 2 12.7537 2ZM11.7586 7V9H13.7488V7H11.7586ZM11.7586 11V17H13.7488V11H11.7586ZM4.79292 12C4.79292 16.41 8.36531 20 12.7537 20C17.142 20 20.7144 16.41 20.7144 12C20.7144 7.59 17.142 4 12.7537 4C8.36531 4 4.79292 7.59 4.79292 12Z" fill="white"/> 3004 </mask> 3005 <g mask="url(#maskInfo)"> 3006 <rect x="0.8125" width="23.8823" height="24" fill="#FFFFFF"/> 3007 </g> 3008 </svg> 3009 </span> 3010 </a> 3011 <a href="#" class="jp-carousel-icon-btn jp-carousel-icon-comments" aria-label="Toggle photo comments visibility"> 3012 <span class="jp-carousel-icon"> 3013 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3014 <mask id="maskComments" mask-type="alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="21" height="20"> 3015 <path fill-rule="evenodd" clip-rule="evenodd" d="M4.3271 2H20.2486C21.3432 2 22.2388 2.9 22.2388 4V16C22.2388 17.1 21.3432 18 20.2486 18H6.31729L2.33691 22V4C2.33691 2.9 3.2325 2 4.3271 2ZM6.31729 16H20.2486V4H4.3271V18L6.31729 16Z" fill="white"/> 3016 </mask> 3017 <g mask="url(#maskComments)"> 3018 <rect x="0.34668" width="23.8823" height="24" fill="#FFFFFF"/> 3019 </g> 3020 </svg> 3021 3022 <span class="jp-carousel-has-comments-indicator" aria-label="This image has comments."></span> 3023 </span> 3024 </a> 3025 </div>
3026 </div> 3027 <div class="jp-carousel-info-extra"> 3028 <div class="jp-carousel-info-content-wrapper"> 3029 <div class="jp-carousel-photo-title-container"> 3030 <div class="jp-carousel-photo-title"></div> 3031 </div> 3032 <div class="jp-carousel-comments-wrapper"> 3033 <div id="jp-carousel-comments-loading"> 3034 <span>Loading Comments...</span> 3035 </div> 3036 <div class="jp-carousel-comments"></div> 3037 <div id="jp-carousel-comment-form-container"> 3038 <span id="jp-carousel-comment-form-spinner"><svg class="jetpack-spinner" width="20" height="20" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><circle cx="50" cy="50" r="46" fill="none" stroke="#ddd" stroke-width="8"/><path d="M 50 4 A 46 46 0 0 1 96 50" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" dur="1.4s" from="0 50 50" to="360 50 50" repeatCount="indefinite"/></path></svg></span> 3039 <div id="jp-carousel-comment-post-results"></div> 3040 <div id="jp-carousel-comment-form-commenting-as"> 3041 <p id="jp-carousel-commenting-as"> 3042 You must be <a href="#" class="jp-carousel-comment-login">logged in</a> to post a comment. </p> 3043 </div> 3044 </div> 3045 </div> 3046 <div class="jp-carousel-image-meta"> 3047 <div class="jp-carousel-title-and-caption"> 3048 <div class="jp-carousel-photo-info"> 3049 <div class="jp-carousel-caption" itemprop="caption description"></div> 3050 </div> 3051 3052 <div class="jp-carousel-photo-description"></div> 3053 </div> 3054 <a class="jp-carousel-image-download" href="#" aria-label="Download image" target="_blank" style="display: none;"> 3055 <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3056 <mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="19" height="18"> 3057 <path fill-rule="evenodd" clip-rule="evenodd" d="M5.84615 5V19H19.7775V12H21.7677V19C21.7677 20.1 20.8721 21 19.7775 21H5.84615C4.74159 21 3.85596 20.1 3.85596 19V5C3.85596 3.9 4.74159 3 5.84615 3H12.8118V5H5.84615ZM14.802 5V3H21.7677V10H19.7775V6.41L9.99569 16.24L8.59261 14.83L18.3744 5H14.802Z" fill="white"/> 3058 </mask> 3059 <g mask="url(#mask0)"> 3060 <rect x="0.870605" width="23.8823" height="24" fill="#FFFFFF"/> 3061 </g> 3062 </svg> 3063 <span class="jp-carousel-download-text"></span> 3064 </a> 3065 <div class="jp-carousel-image-map" style="display: none;"></div> 3066 </div> 3067 </div> 3068 </div> 3069 </div> 3070 </div> 3071 3072 </div> 3073
3073<script crossorigin='anonymous' type='text/javascript' src='/_static/??-eJzTLy/QTc7PK0nNK9EvyClNz8wr1i+uzCtJrMjITM/IAeKS1CJMEWP94uSizIISoOIM5/yiVL2sYh19yo1yKiotzvBKLEukpnkBOYmZeUAD7XNtDU3NDYwMTCwtTLMA1p9f5A=='></script>
vendor: 1 bytes, line 3073
3073
3074<script type='text/javascript'> 3075 (function(){ 3076 var corecss = document.createElement('link'); 3077 var themecss = document.createElement('link'); 3078 var corecssurl = "/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shCore.css?ver=3.0.9b"; 3079 if ( corecss.setAttribute ) { 3080 corecss.setAttribute( "rel", "stylesheet" ); 3081 corecss.setAttribute( "type", "text/css" ); 3082 corecss.setAttribute( "href", corecssurl ); 3083 } else { 3084 corecss.rel = "stylesheet"; 3085 corecss.href = corecssurl; 3086 } 3087 document.head.appendChild( corecss ); 3088 var themecssurl = "/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shThemeDefault.css?m=1363304414i&ver=3.0.9b"; 3089 if ( themecss.setAttribute ) { 3090 themecss.setAttribute( "rel", "stylesheet" ); 3091 themecss.setAttribute( "type", "text/css" ); 3092 themecss.setAttribute( "href", themecssurl ); 3093 } else { 3094 themecss.rel = "stylesheet"; 3095 themecss.href = themecssurl; 3096 } 3097 document.head.appendChild( themecss ); 3098 })(); 3099 SyntaxHighlighter.config.strings.expandSource = '+ expand source'; 3100 SyntaxHighlighter.config.strings.help = '?'; 3101 SyntaxHighlighter.config.strings.alert = 'SyntaxHighlighter\n\n'; 3102 SyntaxHighlighter.config.strings.noBrush = 'Can\'t find brush for: '; 3103 SyntaxHighlighter.config.strings.brushNotHtmlScript = 'Brush wasn\'t configured for html-script option: '; 3104 SyntaxHighlighter.defaults['pad-line-numbers'] = false; 3105 SyntaxHighlighter.defaults['toolbar'] = false; 3106 SyntaxHighlighter.all(); 3107 3108 // Infinite scroll support 3109 document.addEventListener( 'is.post-load', function () { 3110 SyntaxHighlighter.highlight(); 3111 } ); 3112</script>
3112 3113 3114
3114<script type="text/javascript"> 3115 (function () { 3116 var wpcom_reblog = { 3117 source: 'toolbar', 3118 3119 toggle_reblog_box_flair: function (obj_id, post_id) { 3120 3121 // Go to site selector. This will redirect to their blog if they only have one. 3122 const postEndpoint = `https://wordpress.com/post`; 3123 3124 // Ideally we would use the permalink here, but fortunately this will be replaced with the 3125 // post permalink in the editor. 3126 const originalURL = `${ document.location.href }?page_id=${ post_id }`; 3127 3128 const url = 3129 postEndpoint + 3130 '?url=' + 3131 encodeURIComponent( originalURL ) + 3132 '&is_post_share=true' + 3133 '&v=5'; 3134 3135 const redirect = function () { 3136 if ( 3137 ! window.open( url, '_blank' ) 3138 ) { 3139 location.href = url; 3140 } 3141 }; 3142 3143 if ( /Firefox/.test( navigator.userAgent ) ) { 3144 setTimeout( redirect, 0 ); 3145 } else { 3146 redirect(); 3147 } 3148 }, 3149 }; 3150 3151 window.wpcom_reblog = wpcom_reblog; 3152 })(); 3153 </script>
vendor: 1 bytes, line 3153
3153
3154<script id="coblocks-loader-js-extra"> 3155var wpcom_coblocks_js = {"coblocks_masonry_js":"https://s2.wp.com/wp-content/plugins/coblocks/2.18.1-simple-rev.4/dist/js/coblocks-masonry.min.js?m=1681832297i","coblocks_lightbox_js":"https://s2.wp.com/wp-content/plugins/coblocks/2.18.1-simple-rev.4/dist/js/coblocks-lightbox.min.js?m=1681832297i","jquery_core_js":"/wp-includes/js/jquery/jquery.min.js","jquery_migrate_js":"/wp-includes/js/jquery/jquery-migrate.min.js","masonry_js":"/wp-includes/js/masonry.min.js","imagesloaded_js":"/wp-includes/js/imagesloaded.min.js"}; 3156var coblocksLigthboxData = {"closeLabel":"Close Gallery","leftLabel":"Previous","rightLabel":"Next"}; 3157//# sourceURL=coblocks-loader-js-extra 3158</script>
vendor: 1 bytes, line 3158
3158
3159<script id="jetpack-carousel-js-extra"> 3160var jetpackSwiperLibraryPath = {"url":"/wp-content/mu-plugins/jetpack-plugin/sun/_inc/blocks/swiper.js"}; 3161var jetpackCarouselStrings = {"widths":[370,700,1000,1200,1400,2000],"is_logged_in":"","lang":"en","ajaxurl":"https://raysinnema.blog/wp-admin/admin-ajax.php","nonce":"2e9c743d27","display_exif":"1","display_comments":"1","single_image_gallery":"1","single_image_gallery_media_file":"","background_color":"white","comment":"Comment","post_comment":"Post Comment","write_comment":"Write a Comment...","loading_comments":"Loading Comments...","image_label":"Open image in full-screen.","download_original":"View full size \u003Cspan class=\"photo-size\"\u003E{0}\u003Cspan class=\"photo-size-times\"\u003E\u00d7\u003C/span\u003E{1}\u003C/span\u003E","no_comment_text":"Please be sure to submit some text with your comment.","no_comment_email":"Please provide an email address to comment.","no_comment_author":"Please provide your name to comment.","comment_post_error":"Sorry, but there was an error posting your comment. Please try again later.","comment_approved":"Your comment was approved.","comment_unapproved":"Your comment is in moderation.","camera":"Camera","aperture":"Aperture","shutter_speed":"Shutter Speed","focal_length":"Focal Length","copyright":"Copyright","comment_registration":"1","require_name_email":"1","login_url":"https://sinnema313.wordpress.com/wp-login.php?redirect_to=https%3A%2F%2Fraysinnema.blog%2F2026%2F01%2F12%2Fsolve-problems-with-systems-thinking%2F","blog_id":"3802920","meta_data":["camera","aperture","shutter_speed","focal_length","copyright"],"stats_query_args":"blog=3802920&v=wpcom&tz=-6&user_id=0&arch_author=sinnema313&arch_results=10&subd=sinnema313","is_public":"1"}; 3162//# sourceURL=jetpack-carousel-js-extra 3163</script>
vendor: 1 bytes, line 3163
3163
3164<script crossorigin='anonymous' type='text/javascript' src='/_static/??-eJydkNFqwzAMRX9oipbQdXkZ+5ThOEqrRJaNZbfd3zeFZhtjD6MvgiuujuDgOYGPWkgLhgpJ6oHVcKaSnF/uGa0qfrB6HCrLiKwTKxcC8zmKbPmzCazNbE/4A7oRfRwk+sWwa9q+acE4JCHIdGp2OLIVnL9LINGNlP/ilSMFMkx1WN+OlGgdWmDNwnakDN0NpO7EB1c4PnhvCycQ1gWm6KvBxJcHQDdjqyJOBc7Jx/Ab8W/h3uVYjeSrti3uht7DW/vav3Tt7rnfz1f9dq1m'></script>
vendor: 1 bytes, line 3164
3164
3165<script data-wp-strategy="defer" defer id="tiled-gallery-js" src="/wp-content/mu-plugins/jetpack-plugin/sun/_inc/build/tiled-gallery/tiled-gallery/tiled-gallery.min.js?m=1755011788i&ver=16.3-a.2"></script>
vendor: 1 bytes, line 3165
3165
3166<script crossorigin='anonymous' type='text/javascript' src='/wp-content/mu-plugins/carousel-wpcom/carousel-wpcom.js?m=1761899756i'></script>
vendor: 1 bytes, line 3166
3166
3167<script id="wp-emoji-settings" type="application/json"> 3168{"baseUrl":"https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/","ext":".png","svgUrl":"https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/svg/","svgExt":".svg","source":{"concatemoji":"/wp-includes/js/wp-emoji-release.min.js?m=1783090810i&ver=7.1.1-alpha-63587"}} 3169</script>
vendor: 1 bytes, line 3169
3169
3170<script type="module"> 3171/*! This file is auto-generated */ 3172var e="script#wp-emoji-settings",t=document.querySelector(e);if(!(t instanceof HTMLScriptElement))throw new Error("Element missing: "+e);const r=JSON.parse(t.text),s=(window._wpemojiSettings=r,"wpEmojiSettingsSupports"),o=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(s,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const r=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===r[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,r){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!r(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,r){let a;const s=(a="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),o=(s.textBaseline="top",s.font="600 32px Arial",{});return e.forEach(e=>{o[e]=t(s,e,n,r)}),o}function a(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}r.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(s));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(o),u.toString(),c.toString(),p.toString()].join(",")+"));",r=new Blob([e],{type:"text/javascript"});const a=new Worker(URL.createObjectURL(r),{name:"wpTestEmojiSupports"});return void(a.onmessage=e=>{i(n=e.data),a.terminate(),t(n)})}catch(e){}i(n=f(o,u,c,p))}t(n)}).then(e=>{for(const n in e)r.supports[n]=e[n],r.supports.everything=r.supports.everything&&r.supports[n],"flag"!==n&&(r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&r.supports[n]);var t;r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&!r.supports.flag,r.supports.everything||((t=r.source||{}).concatemoji?a(t.concatemoji):t.wpemoji&&t.twemoji&&(a(t.twemoji),a(t.wpemoji)))}); 3173//# sourceURL=/wp-includes/js/wp-emoji-loader.min.js 3174</script>
3174 3175
3175<script type="text/javascript"> 3176 (function() { 3177 var extend = function(out) { 3178 out = out || {}; 3179 3180 for (var i = 1; i < arguments.length; i++) { 3181 if (!arguments[i]) 3182 continue; 3183 3184 for (var key in arguments[i]) { 3185 if (arguments[i].hasOwnProperty(key)) 3186 out[key] = arguments[i][key]; 3187 } 3188 } 3189 3190 return out; 3191 }; 3192 extend( window.infiniteScroll.settings.scripts, ["jetpack-mu-wpcom-settings","rlt-proxy","wpcom-actionbar-placeholder","grofiles-cards","wpgroho","syntaxhighlighter-core","syntaxhighlighter-brush-java","syntaxhighlighter-brush-plain","the-neverending-homepage","coblocks-loader","independent-publisher-2-navigation","independent-publisher-2-skip-link-focus-fix","independent-publisher-2-wpcom-functions","jetpack-carousel","tiled-gallery","carousel-wpcom"] ); 3193 extend( window.infiniteScroll.settings.styles, ["the-neverending-homepage","coblocks-frontend","wpcom-core-compat-playlist-styles","wpcom-text-widget-styles","wpcom-bbpress2-staff-css","genericons","independent-publisher-2-style","
3193independent-publisher-2-block-style","independent-publisher-2-wpcom-style","reblogging","geo-location-flair","a8c-global-print","h4-global","wp-img-auto-sizes-contain","wp-emoji-styles","wp-block-library","classic-theme-styles","wp-block-styles-placeholder","coblocks-inline-format","wp-global-styles-placeholder","jetpack-global-styles-frontend-style","wp-block-list-item","wp-block-list-item-theme","wp-block-heading","wp-block-heading-theme","wp-block-image","wp-block-image-theme","wp-block-list","wp-block-list-theme","wp-block-paragraph","wp-block-paragraph-theme","global-styles","jetpack-swiper-library","jetpack-carousel","jetpack-top-posts-widget","tiled-gallery","core-block-supports-duotone"] ); 3194 })(); 3195 </script>
3195 3196 <span id="infinite-aria" aria-live="polite"></span> 3197
3197<script src="//stats.wp.com/w.js?69" defer></script>
3197
3197<script type="text/javascript"> 3198_tkq = window._tkq || []; 3199_stq = window._stq || []; 3200_tkq.push(['storeContext', {'blog_id':'3802920','blog_tz':'-6','user_lang':'en','blog_lang':'en','user_id':'0'}]); 3201 // Prevent sending pageview tracking from WP-Admin pages. 3202 _stq.push(['view', {'blog':'3802920','v':'wpcom','tz':'-6','user_id':'0','arch_author':'sinnema313','arch_results':'10','subd':'sinnema313'}]); 3203 _stq.push(['extra', {'crypt':'UE5tW3cvZGRTd1VXTHx0Q0pyYT89K3QrZXh+YlZCT3lNLEN2NzQ2Njh8QTJiL1BzZVh+MTlwZTc3TkdTOFRnU0tVUE1VQj1TaW03RjJMN204RzI2ejFUaD9lbXxMLWYvVXBoZWxUPzZNR0Z+fGtWa2xLZGRVZTNSVHpBTkxYczdjYlBtT0RvX0EzOWNSSE0lJmg2azJ6fmhrM1JmSStOOUNlNTlSczEmM0hEQX58RD14QU9oV0lEMlVBc0FLdl1UOC5LVSssLG1ZMyt3PVE4RC1kT356QkQxSitwM0tINCVucg=='}]); 3204_stq.push([ 'clickTrackerInit', '3802920', '0' ]); 3205</script>
3205 3206<noscript><img src="https://pixel.wp.com/b.gif?v=noscript" style="height:1px;width:1px;overflow:hidden;position:absolute;bottom:1px;" alt="" /></noscript> 3207<meta id="bilmur" property="bilmur:data" content="" data-provider="wordpress.com" data-service="simple" data-site-tz="America/Denver" data-custom-props="{"logged_in":"0","wptheme":"pub\/independent-publisher-2","wptheme_is_block":"0"}" > 3208
3208<script defer src="/wp-content/js/bilmur.min.js?i=19&m=202638"></script>
3208 3209</body> 3210</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.