PageSourceSearch

https://raysinnema.blog/author/sinnema313/

html raysinnema.blog collected 2026-10-09 23:10:07 UTC 245,173 bytes, 3,210 lines download raw bytes

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 &#8211; 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 &raquo; Feed" href="https://raysinnema.blog/feed/" />
167<link rel="alternate" type="application/rss+xml" title="Ray on Software Architecture &raquo; Comments Feed" href="https://raysinnema.blog/comments/feed/" />
168<link rel="alternate" type="application/rss+xml" title="Ray on Software Architecture &raquo; 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&amp;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&#038;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&amp;h=67 160w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=240&amp;h=100 240w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=768&amp;h=320 768w, https://raysinnema.blog/wp-content/uploads/2025/05/cropped-erasmusbrug-1.jpg?w=1024&amp;
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&nbsp;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;VectorMine | Dreamstime.com&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;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&amp;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&amp;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&#8217;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&#8217;t like working outside business hours, we can&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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>&#8211;</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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;s name contains &#8220;loop&#8221;: most systems contain loops.</p>
592
593
594
595<p class="wp-block-paragraph">It&#8217;s hard to tell from a simple diagram like this how conflicting effects combine. The example CLD doesn&#8217;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 &#8220;best practices.&#8221; 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&#8217;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&amp;dib=eyJ2IjoiMSJ9.xG56AEYJVPwm2PrfYOJ_HFqCMQqS0miAViG4DEVTqywuWcjAnsHKNvZtNvm75F_dH9ez6TcawfDlZpsh0wBjbozNHQ5iTjV741qg3xCAPdMFWi1rdTawd5ENRIprSA2fkAyLcqUAE2AjO63Qye4sIiqs-GQyHA4E2VtLrHmUfWC-wg4UtCD7EKEawP7Jwou6R7b5XhLhIsHqtG037tIRlSi9f024EWN2T5KBhW0SqCM.8ugYDT5PVHraYdkTsYENOSEtZ9w00ZagCh3iZ1hE6Gg&amp;dib_tag=se&amp;keywords=donella+meadows&amp;qid=1768091285&amp;sprefix=done%2Caps%2C210&amp;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&amp;dib=eyJ2IjoiMSJ9.UxUsh767EBqycyNVxerlN5a7jdVwgsET3rCXycEh8CQjDzz9TvbSP2bW5W2U-ZrNx3v4Wfw81VMLKjHKttfHZKKFLF2-sRjkMnygAR08AiCLZ1zSDbQgpPLhNgC-zmfqLLfgxa2hWQwyLh7CVlXu5C4cL8BcO69vD67B-8TM9ZnraGLs9Nas4p-RE9mRxDLm33p-RkD_NI5LohlYCao8fVFznHcBh-aeiYOPRkefMrY.yxaB8RGb9nds-bHewFLuDabX64ixou58Y6arLGlcLs4&amp;dib_tag=se&amp;keywords=learning+systems+thinking&amp;qid=1768091349&amp;sprefix=learning+systems+thinking+%2Caps%2C166&amp;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&nbsp;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>&#8220;architecture is relationship therapy for software systems.&#8221;</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&#8217;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 &amp; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=95 160w, https://raysinnema.blog/wp-content/uploads/2025/04/ddd-linter.png?w=240&amp;
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 &amp; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=160 110w, https://raysinnema.blog/wp-content/uploads/2025/04/event-storming.png?w=164&amp;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&#8217;s properties interact with the system&#8217;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 &#8211; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=143 160w, https://raysinnema.blog/wp-content/uploads/2025/04/facilitating-mindset-shift.png?w=240&amp;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&#8217;s easy to spot errors in hindsight, but it&#8217;s much more difficult to encourage insights. We like to attribute the &#8220;root cause&#8221; to human error, so we have someone to blame. It&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=44 160w, https://raysinnema.blog/wp-content/uploads/2025/04/screenshot-2025-04-23-at-7.04.10e280afam.png?w=240&amp;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&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=160 135w, https://raysinnema.blog/wp-content/uploads/2025/04/eric-evans.png?w=203&amp;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 &amp; 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&amp;dib=eyJ2IjoiMSJ9.z3yeBZuTfcjY0_hLBOp8ruD2TUB2SB6Bbxpe_YpgNaoyztZAns_1mRJsYZrTZTdyII4mcwVjpoDdsUQmnlqPLUEaZ7uNMkdAO-UPdaDngFdH19mIo0FXfxgbac-h7UCkJQ1bczpftB7Xffh5MnBLxN2N0AyJdtZsCa_TrWHhnW4dVV4YQ9c2UWs7XMPoGac4DWPIlAxvyLqG8PfJ3UCOutE5lPVvK1FVZL_67RAWUOQ.PWrP-4sXXTyvK0fxSFzjz90xWhlEXLAJUgddgkkrVao&amp;dib_tag=se&amp;keywords=hexagonal+architecture&amp;qid=1742768309&amp;
821sprefix=hexagonal+ar%2Caps%2C165&amp;sr=8-1">hexagonal architecture</a>, aka the Ports &amp; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;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 &amp; SQS, all in the span of 6 years. These migrations were relatively painless because we used the Ports &amp; 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&amp;dib=eyJ2IjoiMSJ9.IjcD2WKpMl-96TikVHSpho0j0cajWEBku8PZ3uFevIS80cDYvKDA90OW6jeTsk91V8grKgjXjNjhM5OUUoFdTLEJ3SXfpk5IoAJdtURoPZNXvMGYwSsbYg82zgcJPel5y_sAYr4FedkWr7ISCOUXXWf4vLPfI4ZJBP8LOyM4BKobOQ3c0hJCWB_UvurrNCFnI3wdHwWSSNvKmLVN2TtSlOiq9PaAL-IZ0fvpHUi_llY.gq-Np7jd9YqRcH9yVUKRpjQ3QZLWmsvjcQvxyG-7dqc&amp;dib_tag=se&amp;keywords=naming+things+book&amp;qid=1742770782&amp;sprefix=naming+thin%2Caps%2C157&amp;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&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;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&#8217;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&#8217;s not impossible. Let&#8217;s try this with the <a href="https://codingdojo.org/kata/WordWrap/">WordWrap kata</a>, where we have to ensure lines aren&#8217;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&#8217;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 = &#039;\n&#039;;
1070
1071    public static String wrap(String text, int length) {
1072        if (text == null) {
1073            return &quot;&quot;;
1074        }
1075        if (text.length() &lt;= 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 &quot;&quot;;
1098        }
1099        if (text.length() &lt;= length) {
1100            return text;
1101        }
1102        var index = text.lastIndexOf(&#039; &#039;, length);
1103        if (index &lt; 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 &quot;&quot;;
1128        }
1129        if (text.length() &lt;= length) {
1130            return text;
1131        }
1132        var index = text.lastIndexOf(&#039; &#039;, length);
1133        if (index &lt; 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 &quot;&quot;;
1150        }
1151        if (text.length() &lt;= length) {
1152            return text;
1153        }
1154        var index = text.lastIndexOf(&#039; &#039;, length);
1155        var skip = 1;
1156        if (index &lt; 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&#8217;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&#8217;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 &lt; 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 &lt; numCharsInBook; i++) {
1196            result.append(randomChar());
1197        }
1198        return result.toString();
1199    }
1200
1201    private char randomChar() {
1202        if (random.nextFloat() &lt; 1.0 / (1 + AVG_NUM_CHARS_PER_WORD)) {
1203            return &#039; &#039;;
1204        }
1205        return (char) (random.nextInt(26) + &#039;a&#039;);
1206    }
1207</pre></div>
1208
1209
1210<p class="wp-block-paragraph">Normally, you&#8217;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&#8217;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&#8217;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&#8217;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&#8217;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&#8217;t focused on performance. We shouldn&#8217;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&#8217;t good enough, things are different. Let&#8217;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&#8217;t want to use <code>substring()</code> and add two <code>String</code>s together, since that involves copying characters. So let&#8217;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 &quot;&quot;;
1254        }
1255        var result = new StringBuilder(text);
1256        if (result.length() &gt; 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 &quot;&quot;;
1271        }
1272        var capacity = text.length() + text.length() / length;
1273        var result = new StringBuilder(capacity);
1274        result.append(text);
1275        if (result.length() &gt; 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&#8217;re going to need this, so I&#8217;m cool with it.</p>
1284
1285
1286
1287<p class="wp-block-paragraph">Next let&#8217;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 &quot;&quot;;
1294        }
1295        var result = new StringBuilder(text.length() + text.length() / length);
1296        result.append(text);
1297        if (result.length() &gt; length) {
1298            var spaceIndex = text.lastIndexOf(&#039; &#039;, length);
1299            if (spaceIndex &lt; 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 &quot;&quot;;
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 &lt; result.length()) {
1323            var spaceIndex = result.lastIndexOf(&quot; &quot;, columnEnd);
1324            if (spaceIndex &lt; 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&#8217;t we do it &#8220;right&#8221; from the start? Like I said earlier, we didn&#8217;t because we didn&#8217;t know that our implementation wasn&#8217;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&#8217;ve written our tests in a different order, tackling the test for performance earlier in the process. That would&#8217;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&#8217;ll accept a little bit of rework, knowing that I&#8217;ll win back that time and more in all the cases where the &#8220;right&#8221; solution is also fast enough and I don&#8217;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 &#8220;sprint&#8221; 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 &#8220;to run or go at top speed especially for a short distance&#8221;. Most software development doesn&#8217;t go just &#8220;a short distance&#8221;. 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&#8217;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&#8217;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&#8217;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&#8217;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&#8217;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 &#8220;sprint&#8221; 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 &#8220;sprint&#8221;. Turning to Merriam-Webster again, we find that <a href="https://www.merriam-webster.com/dictionary/iteration">iteration</a> is &#8220;a procedure in which repetition of a sequence of operations yields results successively closer to a desired result.&#8221; That sounds about right.</p>
1409
1410
1411
1412<p class="wp-block-paragraph"><strong>Exercise for the reader</strong>: What&#8217;s wrong with the phrase &#8220;best practices?&#8221; (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&nbsp;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&#8217;ll look at is to calculate a string that represents the Roman representation of a number. We&#8217;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&#8217;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 &#8211; 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&#8217;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&#8217;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, () -&gt; 
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&#8217;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&#8217;t change the code, so that&#8217;s not a smart choice. Tests for items 3-5 on the list all depend on the symbols in 2, so that&#8217;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&#8217;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, () -&gt;
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 &lt;= 0) {
1574            throw new InexpressibleException();
1575        }
1576        return &quot;I&quot;;
1577    }
1578</pre></div>
1579
1580
1581<p class="wp-block-paragraph">With the lower bound in place, let&#8217;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, () -&gt;
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 &lt;= 0 || value &gt;= 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&#8217;t look great, so let&#8217;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 &quot;I&quot;;
1615    }
1616
1617    private static boolean isExpressible(int value) {
1618        return MIN_EXPRESSIBLE &lt;= value &amp;&amp; value &lt;= 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&#8217;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 = &quot;I&quot;;
1644        if (value &gt; 1) {
1645            result += &quot;I&quot;;
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 = &quot;I&quot;;
1673        while (value &gt; 1) {
1674            result += &quot;I&quot;;
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&#8217;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 = &quot;&quot;;
1691        while (value &gt;= 1) {
1692            result += &quot;I&quot;;
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&#8217;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 = &quot;&quot;;
1709        var numeral = new Numeral(&quot;I&quot;, 1);
1710        while (value &gt;= 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&#8217;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 = &quot;&quot;;
1735        var numeral = new Numeral(&quot;I&quot;, 1);
1736        while (value &gt;= 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 = &quot;&quot;;
1751        var progress = 0;
1752        var numeral = new Numeral(&quot;I&quot;, 1);
1753        while (value - progress &gt;= 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(&quot;&quot;, 0);
1768        var numeral = new Numeral(&quot;I&quot;, 1);
1769        while (value - result.value() &gt;= 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&#8217;ll add processing of other numerals later. In order to prepare for that, let&#8217;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&#8217;s because this is a <code>static</code> method, and thus not able to use fields. Let&#8217;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(&quot;&quot;, 0);
1799        var numeral = new Numeral(&quot;I&quot;, 1);
1800        while (value - result.value() &gt;= 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(&quot;&quot;, 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(&quot;I&quot;, 1);
1828        while (target - current.value() &gt;= 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(&quot;I&quot;, 1);
1842        add(numeral);
1843        return current.text();
1844    }
1845
1846    private void add(Numeral numeral) {
1847        while (target - current.value() &gt;= 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() &gt;= 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&#8217;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&#8217;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&#8217;t pretty. There is duplication in the values, since <code>"IV" == "V" - "I" == 5 - 1 == 4</code>. Let&#8217;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(&quot;I&quot;, 1);
2034        var v = new Numeral(&quot;V&quot;, 5);
2035        var x = new Numeral(&quot;X&quot;, 10);
2036        var l = new Numeral(&quot;L&quot;, 50);
2037        var c = new Numeral(&quot;C&quot;, 100);
2038        var d = new Numeral(&quot;D&quot;, 500);
2039        var m = new Numeral(&quot;M&quot;, 1000);
2040        var numerals = new TreeSet&lt;&gt;(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&lt;Numeral&gt; 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&#8217;re still not done, though. The way we add the pattern of subtractives isn&#8217;t random; there&#8217;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(&quot;I&quot;, 1);
2070        var v = new Numeral(&quot;V&quot;, 5);
2071        var x = new Numeral(&quot;X&quot;, 10);
2072        var l = new Numeral(&quot;L&quot;, 50);
2073        var c = new Numeral(&quot;C&quot;, 100);
2074        var d = new Numeral(&quot;D&quot;, 500);
2075        var m = new Numeral(&quot;M&quot;, 1000);
2076        var baseNumerals = List.of(i, v, x, l, c, d, m);
2077        var numerals = new TreeSet&lt;&gt;(comparing(Numeral::value).reversed());
2078        for (var index = 0; index &lt; 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&lt;Numeral&gt; BASE_NUMERALS = List.of(
2095            new Numeral(&quot;I&quot;, 1), 
2096            new Numeral(&quot;V&quot;, 5), 
2097            new Numeral(&quot;X&quot;, 10), 
2098            new Numeral(&quot;L&quot;, 50), 
2099            new Numeral(&quot;C&quot;, 100), 
2100            new Numeral(&quot;D&quot;, 500), 
2101            new Numeral(&quot;M&quot;, 1000));
2102
2103    private String complete() {
2104        var numerals = new TreeSet&lt;&gt;(comparing(Numeral::value).reversed());
2105        for (var index = 0; index &lt; 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&lt;Numeral&gt; numerals() {
2127        var result = new TreeSet&lt;&gt;(comparing(Numeral::value).reversed());
2128        for (var index = 0; index &lt; 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&#8217;re done.</p>
2140
2141
2142
2143<p class="wp-block-paragraph">Here&#8217;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&lt;Numeral&gt; BASE_NUMERALS = List.of(
2157            new Numeral(&quot;I&quot;, 1),
2158            new Numeral(&quot;V&quot;, 5),
2159            new Numeral(&quot;X&quot;, 10),
2160            new Numeral(&quot;L&quot;, 50),
2161            new Numeral(&quot;C&quot;, 100),
2162            new Numeral(&quot;D&quot;, 500),
2163            new Numeral(&quot;M&quot;, 1000));
2164
2165    private final int target;
2166    private Numeral current = new Numeral(&quot;&quot;, 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 &lt;= value &amp;&amp; value &lt;= MAX_EXPRESSIBLE;
2181    }
2182
2183    private String complete() {
2184        numerals().forEach(this::add);
2185        return current.text();
2186    }
2187
2188    private Collection&lt;Numeral&gt; numerals() {
2189        var result = new TreeSet&lt;&gt;(comparing(Numeral::value).reversed());
2190        for (var index = 0; index &lt; 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&lt;Numeral&gt; 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() &gt;= 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&nbsp;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&#8217;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&#8217;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&#8217;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&#8217;ve seen don&#8217;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&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=240&amp;h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=768&amp;h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/personal-branding.png?w=1024&amp;
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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=240&amp;h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=768&amp;h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/stakeholders.png?w=1024&amp;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&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=160 160w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=240&amp;h=240 240w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=768&amp;h=768 768w, https://raysinnema.blog/wp-content/uploads/2023/09/chat.png?w=1024&amp;
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&#8217;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&#8217;s not there, they need to contact the team&#8217;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&#8217;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&#8217;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&#8217;t you?). However, that&#8217;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&#8217;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&nbsp;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&#8217;d only apply the ACL pattern, then you&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&amp;h=91 160w, https://raysinnema.blog/wp-content/uploads/2023/08/anti-corruption-microservice.png?w=240&amp;
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&#8217;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&#8217;t need to support all the features of the external system, just the ones used by the application&#8217;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&nbsp;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&#8217;ve noticed that when people say something can&#8217;t be done, it usually means that they don&#8217;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 &#8220;
2540deficiencies in internal quality that make it harder than it would ideally be to modify and extend the system further&#8221;. 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&#8217;d like to focus on <strong>changes in perception</strong>. That might seem odd, but bear with me. Let&#8217;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&#8217;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&#8217;t change, our definition of &#8220;internal quality&#8221; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;ve picked a language that values stability, like Java, then you&#8217;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&#8217;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&#8217;d probably need to formally schedule that work. It&#8217;
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&#8217;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&#8217;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&#8217;t a silver bullet here, so be careful what you&#8217;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 &amp; 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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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 &#8220;downward&#8221;. 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&#8217;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&#8217;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&#8217;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&#8217;s documentation. (Unless they&#8217;re studying the adapter&#8217;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&#8217;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="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" 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&#8217;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&#8217;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&#8217;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&amp;me=&amp;qid=1659436498" target="_blank">architectural fitness function</a> to ensure that it&#8217;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&amp;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 &hellip;" 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 &amp; 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&amp;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&#038;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="{&quot;logged_in&quot;:&quot;0&quot;,&quot;wptheme&quot;:&quot;pub\/independent-publisher-2&quot;,&quot;wptheme_is_block&quot;:&quot;0&quot;}"   >
3208		
3208<script defer src="/wp-content/js/bilmur.min.js?i=19&amp;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.