Files
nexus/sreweekly/articles/251/04-why-i-ve-been-merging-microservices-back-into-the-monolith-at-invision.html
2026-09-12 17:23:01 +08:00

2194 lines
123 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
Why I&#x27;ve Been Merging Microservices Back Into The Monolith At InVision
</title>
<meta name="Description" content="Ben&#x20;Nadel&#x20;explains&#x20;why&#x20;his&#x20;team&#x20;has&#x20;been&#x20;hard&#x20;at&#x20;work&#x20;merging&#x20;&quot;microservices&quot;&#x20;back&#x20;into&#x20;the&#x20;&quot;monolith&quot;.&#x20;His&#x20;team&#x20;is&#x20;not&#x20;anti-microservices.&#x20;And,&#x20;in&#x20;fact,&#x20;both&#x20;the&#x20;expanding&#x20;and&#x20;the&#x20;contracting&#x20;of&#x20;services&#x20;have&#x20;been&#x20;driven&#x20;by&#x20;the&#x20;same&#x20;underlying&#x20;point-of-friction." />
<link rel="canonical" href="https&#x3a;&#x2f;&#x2f;www.bennadel.com&#x2f;blog&#x2f;3944-why-ive-been-merging-microservices-back-into-the-monolith-at-invision.htm" />
<link rel="shortcut icon" type="image/png" href="https://bennadel-cdn.com/favicon.png"></link>
<link
rel="alternate"
type="application/rss+xml"
title="Ben Nadel's ColdFusion, JavaScript, User Experience (UX), and Web Development RSS"
href="/rss">
</link>
<style type="text/css" media="(min-width: 600px)">
/* nunito-sans-300 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: normal;
font-weight: 300;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-300.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-300.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* nunito-sans-300italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: italic;
font-weight: 300;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-300italic.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-300italic.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* nunito-sans-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-regular.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-regular.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* nunito-sans-italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: italic;
font-weight: 400;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-italic.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-italic.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* nunito-sans-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: normal;
font-weight: 700;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-700.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-700.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* nunito-sans-700italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "Nunito Sans";
font-style: italic;
font-weight: 700;
src: url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-700italic.woff2" ) format( "woff2" ), /* Chrome 36+, Opera 23+, Firefox 39+ */
url( "https://bennadel-cdn.com/static/fonts/nunito-sans-v15-latin-700italic.woff" ) format( "woff" ); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
</style>
<link rel="preconnect" href="https://bennadel-cdn.com" />
<meta name="verify-v1" content="4BMHbbVgHMewa8Orm/PNkp0/RtWXuI4Fh/IKPkE2y7A=" />
<meta name="google-site-verification" content="vR-ZlPTSF4uAlM0vCQPsoVsVSwslsq0tSftkSrGGQ-w" />
<meta name="google-site-verification" content="N-CzvyRbou_KRXcxhqvoDP3rZnw-fgRw5evSEp7T65s" />
<script type="application/ld+json">
[{"@context":"https://schema.org","@type":"NewsArticle","headline":"Why I&#x27;ve Been Merging Microservices Back Into The Monolith At InVision","image":["https://bennadel-cdn.com/resources/uploads/2020/thanos-inserting-the-last-inifinity-stone.gif","https://bennadel-cdn.com/resources/uploads/2020/merging-microservices-effort-is-not-free.png"],"datePublished":"2020-12-21T07:27:15Z","dateModified":"2020-12-21T07:27:15Z","author":[{"@type":"Person","name":"Ben Nadel","jobTitle":"Principal Engineer","url":"https://www.bennadel.com/about/about-ben-nadel.htm"}]}]
</script>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="&#x40;BenNadel" />
<meta name="twitter:creator" content="@BenNadel" />
<meta name="twitter:title" content="Why&#x20;I&#x27;ve&#x20;Been&#x20;Merging&#x20;Microservices&#x20;Back&#x20;Into&#x20;The&#x20;Monolith&#x20;At&#x20;InVision" />
<meta property="og:title" content="Why&#x20;I&#x27;ve&#x20;Been&#x20;Merging&#x20;Microservices&#x20;Back&#x20;Into&#x20;The&#x20;Monolith&#x20;At&#x20;InVision" />
<meta name="twitter:description" content="Ben&#x20;Nadel&#x20;explains&#x20;why&#x20;his&#x20;team&#x20;has&#x20;been&#x20;hard&#x20;at&#x20;work&#x20;merging&#x20;&quot;microservices&quot;&#x20;back&#x20;into&#x20;the&#x20;&quot;monolith&quot;.&#x20;His&#x20;team&#x20;is&#x20;not&#x20;anti-microservices.&#x20;And,&#x20;in&#x20;fact,&#x20;both&#x20;the&#x20;expanding&#x20;and&#x20;the&#x20;contracting&#x20;of&#x20;serv" />
<meta property="og:description" content="Ben&#x20;Nadel&#x20;explains&#x20;why&#x20;his&#x20;team&#x20;has&#x20;been&#x20;hard&#x20;at&#x20;work&#x20;merging&#x20;&quot;microservices&quot;&#x20;back&#x20;into&#x20;the&#x20;&quot;monolith&quot;.&#x20;His&#x20;team&#x20;is&#x20;not&#x20;anti-microservices.&#x20;And,&#x20;in&#x20;fact,&#x20;both&#x20;the&#x20;expanding&#x20;and&#x20;the&#x20;contracting&#x20;of&#x20;serv" />
<meta property="og:url" content="https&#x3a;&#x2f;&#x2f;www.bennadel.com&#x2f;blog&#x2f;3944-why-ive-been-merging-microservices-back-into-the-monolith-at-invision.htm" />
<meta property="og:type" content="article" />
<meta name="twitter:image" content="https&#x3a;&#x2f;&#x2f;bennadel-cdn.com&#x2f;resources&#x2f;uploads&#x2f;2020&#x2f;thanos-inserting-the-last-inifinity-stone.gif" />
<meta property="og:image" content="https&#x3a;&#x2f;&#x2f;bennadel-cdn.com&#x2f;resources&#x2f;uploads&#x2f;2020&#x2f;thanos-inserting-the-last-inifinity-stone.gif" />
<meta name="twitter:image:alt" content="Thanos&#x20;returning&#x20;the&#x20;last&#x20;stone&#x20;to&#x20;the&#x20;Infinity&#x20;Gauntlet" />
<meta property="og:image:alt" content="Thanos&#x20;returning&#x20;the&#x20;last&#x20;stone&#x20;to&#x20;the&#x20;Infinity&#x20;Gauntlet" />
<script type="text/javascript" nonce="E6yu4&#x2f;&#x2f;sBBvJEXLdNHUXgg&#x3d;&#x3d;">
!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Bugsnag=e()}(function(){var r=["navigation","request","process","log","user","state","error","manual"],g=function(e,t,n){for(var r=n,a=0,o=e.length;a<o;a++)r=t(r,e[a],a,e);return r},h=function(e,a){return g(e,function(e,t,n,r){return a(t,n,r)?e.concat(t):e},[])},l=function(e,a){return g(e,function(e,t,n,r){return!0===e||t===a},!1)},i=function(e){return"[object Array]"===Object.prototype.toString.call(e)},s=!{toString:null}.propertyIsEnumerable("toString"),f=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],o=function(e){var t,n=[];for(t in e)Object.prototype.hasOwnProperty.call(e,t)&&n.push(t);if(!s)return n;for(var r=0,a=f.length;r<a;r++)Object.prototype.hasOwnProperty.call(e,f[r])&&n.push(f[r]);return n},t=function(t,n){return void 0===t&&(t=1),void 0===n&&(n=Infinity),function(e){return"number"==typeof e&&parseInt(""+e,10)===e&&t<=e&&e<=n}},a=function(e){return"function"==typeof e||i(e)&&h(e,function(e){return"function"==typeof e}).length===e.length},m=function(e){return"string"==typeof e&&!!e.length},v={},y=function(){return{unhandledExceptions:!0,unhandledRejections:!0}},u=(v.schema={apiKey:{defaultValue:function(){return null},message:"is required",validate:m},appVersion:{defaultValue:function(){return undefined},message:"should be a string",validate:function(e){return e===undefined||m(e)}},appType:{defaultValue:function(){return undefined},message:"should be a string",validate:function(e){return e===undefined||m(e)}},autoDetectErrors:{defaultValue:function(){return!0},message:"should be true|false",validate:function(e){return!0===e||!1===e}},enabledErrorTypes:{defaultValue:y,message:"should be an object containing the flags { unhandledExceptions:true|false, unhandledRejections:true|false }",allowPartialObject:!0,validate:function(t){if("object"!=typeof t||!t)return!1;var e=o(t),n=o(y());return!(h(e,function(e){return l(n,e)}).length<e.length)&&!(0<h(o(t),function(e){return"boolean"!=typeof t[e]}).length)}},onError:{defaultValue:function(){return[]},message:"should be a function or array of functions",validate:a},onSession:{defaultValue:function(){return[]},message:"should be a function or array of functions",validate:a},onBreadcrumb:{defaultValue:function(){return[]},message:"should be a function or array of functions",validate:a},endpoints:{defaultValue:function(){return{notify:"https://notify.bugsnag.com",sessions:"https://sessions.bugsnag.com"}},message:"should be an object containing endpoint URLs { notify, sessions }",validate:function(e){return e&&"object"==typeof e&&m(e.notify)&&m(e.sessions)&&0===h(o(e),function(e){return!l(["notify","sessions"],e)}).length}},autoTrackSessions:{defaultValue:function(e){return!0},message:"should be true|false",validate:function(e){return!0===e||!1===e}},enabledReleaseStages:{defaultValue:function(){return null},message:"should be an array of strings",validate:function(e){return null===e||i(e)&&h(e,function(e){return"string"==typeof e}).length===e.length}},releaseStage:{defaultValue:function(){return"production"},message:"should be a string",validate:function(e){return"string"==typeof e&&e.length}},maxBreadcrumbs:{defaultValue:function(){return 25},message:"should be a number ≤100",validate:function(e){return t(0,100)(e)}},enabledBreadcrumbTypes:{defaultValue:function(){return r},message:"should be null or a list of available breadcrumb types ("+r.join(",")+")",validate:function(e){return null===e||i(e)&&g(e,function(e,t){return!1===e?e:l(r,t)},!0)}},context:{defaultValue:function(){return undefined},message:"should be a string",validate:function(e){return e===undefined||"string"==typeof e}},user:{defaultValue:function(){return{}},message:"should be an object with { id, email, name } properties",validate:function(e){return null===e||e&&g(o(e),function(e,t){return e&&l(["id","email","name"],t)},!0)}},metadata:{defaultValue:function(){return{}},message:"should be an object",validate:function(e){return"object"==typeof e&&null!==e}},logger:{defaultValue:function(){return undefined},message:"should be null or an object with methods { debug, info, warn, error }",validate:function(n){return!n||g(["debug","info","warn","error"],function(e,t){return e&&"function"==typeof n[t]},!0)}},redactedKeys:{defaultValue:function(){return["password"]},message:"should be an array of strings|regexes",validate:function(e){return i(e)&&e.length===h(e,function(e){return"string"==typeof e||e&&"function"==typeof e.test}).length}},plugins:{defaultValue:function(){return[]},message:"should be an array of plugin objects",validate:function(e){return i(e)&&e.length===h(e,function(e){return e&&"object"==typeof e&&"function"==typeof e.load}).length}},featureFlags:{defaultValue:function(){return[]},message:'should be an array of objects that have a "name" property',validate:function(e){return i(e)&&e.length===h(e,function(e){return e&&"object"==typeof e&&"string"==typeof e.name}).length}}},function(e){for(var t=1;t<arguments.length;t++){var n,r=arguments[t];for(n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e}),p=function(e,a){return g(e,function(e,t,n,r){return e.concat(a(t,n,r))},[])};function b(){return(b=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n,r=arguments[t];for(n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e}).apply(this,arguments)}var a=v.schema,a={releaseStage:u({},a.releaseStage,{defaultValue:function(){return/^localhost(:\d+)?$/.test(window.location.host)?"development":"production"}}),appType:b({},a.appType,{defaultValue:function(){return"browser"}}),logger:u({},a.logger,{defaultValue:function(){return"undefined"!=typeof console&&"function"==typeof console.debug?_():undefined}})},_=function(){var n={},r=console.log;return p(["debug","info","warn","error"],function(e){var t=console[e];n[e]=("function"==typeof t?t:r).bind(console,"[bugsnag]")}),n},S=function(){function e(e,t,n,r){void 0===r&&(r=new Date),this.type=n,this.message=e,this.metadata=t,this.timestamp=r}return e.prototype.toJSON=function(){return{type:this.type,name:this.message,timestamp:this.timestamp,metaData:this.metadata}},e}(),w={},E=(!function(e,t){"use strict";"object"==typeof w?w=t():e.StackFrame=t()}(this,function(){"use strict";function n(e){return!isNaN(parseFloat(e))&&isFinite(e)}function r(e){return e.charAt(0).toUpperCase()+e.substring(1)}function l(e){return function(){return this[e]}}var e=["isConstructor","isEval","isNative","isToplevel"],t=["columnNumber","lineNumber"],a=["fileName","functionName","source"],o=e.concat(t,a,["args"]);function i(e){if(e instanceof Object)for(var t=0;t<o.length;t++)e.hasOwnProperty(o[t])&&e[o[t]]!==undefined&&this["set"+r(o[t])](e[o[t]])}i.prototype={getArgs:function(){return this.args},setArgs:function(e){if("[object Array]"!==Object.prototype.toString.call(e))throw new TypeError("Args must be an Array");this.args=e},getEvalOrigin:function(){return this.evalOrigin},setEvalOrigin:function(e){if(e instanceof i)this.evalOrigin=e;else{if(!(e instanceof Object))throw new TypeError("Eval Origin must be an Object or StackFrame");this.evalOrigin=new i(e)}},toString:function(){return(this.getFunctionName()||"{anonymous}")+("("+(this.getArgs()||[]).join(",")+")")+(this.getFileName()?"@"+this.getFileName():"")+(n(this.getLineNumber())?":"+this.getLineNumber():"")+(n(this.getColumnNumber())?":"+this.getColumnNumber():"")}};for(var s=0;s<e.length;s++)i.prototype["get"+r(e[s])]=l(e[s]),i.prototype["set"+r(e[s])]=function(t){return function(e){this[t]=Boolean(e)}}(e[s]);for(var u=0;u<t.length;u++)i.prototype["get"+r(t[u])]=l(t[u]),i.prototype["set"+r(t[u])]=function(t){return function(e){if(!n(e))throw new TypeError(t+" must be a Number");this[t]=Number(e)}}(t[u]);for(var c=0;c<a.length;c++)i.prototype["get"+r(a[c])]=l(a[c]),i.prototype["set"+r(a[c])]=function(t){return function(e){this[t]=String(e)}}(a[c]);return i}),{}),O=(!function(e,t){"use strict";"object"==typeof E?E=t(w):e.ErrorStackParser=t(e.StackFrame)}(this,function(s){"use strict";var t=/(^|@)\S+\:\d+/,n=/^\s*at .*(\S+\:\d+|\(native\))/m,r=/^(eval@)?(\[native code\])?$/;return{parse:function(e){if("undefined"!=typeof e.stacktrace||"undefined"!=typeof e["opera#sourceloc"])return this.parseOpera(e);if(e.stack&&e.stack.match(n))return this.parseV8OrIE(e);if(e.stack)return this.parseFFOrSafari(e);throw new Error("Cannot parse given Error object")},extractLocation:function(e){if(-1===e.indexOf(":"))return[e];e=/(.+?)(?:\:(\d+))?(?:\:(\d+))?$/.exec(e.replace(/[\(\)]/g,""));return[e[1],e[2]||undefined,e[3]||undefined]},parseV8OrIE:function(e){return e.stack.split("\n").filter(function(e){return!!e.match(n)},this).map(function(e){var t=(e=-1<e.indexOf("(eval ")?e.replace(/eval code/g,"eval").replace(/(\(eval at [^\()]*)|(\)\,.*$)/g,""):e).replace(/^\s+/,"").replace(/\(eval code/g,"("),n=t.match(/ (\((.+):(\d+):(\d+)\)$)/),t=(t=n?t.replace(n[0],""):t).split(/\s+/).slice(1),n=this.extractLocation(n?n[1]:t.pop()),t=t.join(" ")||undefined,r=-1<["eval","<anonymous>"].indexOf(n[0])?undefined:n[0];return new s({functionName:t,fileName:r,lineNumber:n[1],columnNumber:n[2],source:e})},this)},parseFFOrSafari:function(e){return e.stack.split("\n").filter(function(e){return!e.match(r)},this).map(function(e){var t,n;return-1===(e=-1<e.indexOf(" > eval")?e.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g,":$1"):e).indexOf("@")&&-1===e.indexOf(":")?new s({functionName:e}):(t=(t=e.match(n=/((.*".+"[^@]*)?[^@]*)(?:@)/))&&t[1]?t[1]:undefined,n=this.extractLocation(e.replace(n,"")),new s({functionName:t,fileName:n[0],lineNumber:n[1],columnNumber:n[2],source:e}))},this)},parseOpera:function(e){return!e.stacktrace||-1<e.message.indexOf("\n")&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(e){for(var t=/Line (\d+).*script (?:in )?(\S+)/i,n=e.message.split("\n"),r=[],a=2,o=n.length;a<o;a+=2){var i=t.exec(n[a]);i&&r.push(new s({fileName:i[2],lineNumber:i[1],source:n[a]}))}return r},parseOpera10:function(e){for(var t=/Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i,n=e.stacktrace.split("\n"),r=[],a=0,o=n.length;a<o;a+=2){var i=t.exec(n[a]);i&&r.push(new s({functionName:i[3]||undefined,fileName:i[2],lineNumber:i[1],source:n[a]}))}return r},parseOpera11:function(e){return e.stack.split("\n").filter(function(e){return!!e.match(t)&&!e.match(/^Error created at/)},this).map(function(e){var t,n=e.split("@"),r=this.extractLocation(n.pop()),n=n.shift()||"",a=n.replace(/<anonymous function(: (\w+))?>/,"$2").replace(/\([^\)]*\)/g,"")||undefined,n=(t=n.match(/\(([^\)]*)\)/)?n.replace(/^[^\(]+\(([^\)]*)\)$/,"$1"):t)===undefined||"[arguments not available]"===t?undefined:t.split(",");return new s({functionName:a,args:n,fileName:r[0],lineNumber:r[1],columnNumber:r[2],source:e})},this)}}}),E),j=function(e,t,n,r){var a=r&&r.redactedKeys?r.redactedKeys:[],r=r&&r.redactedPaths?r.redactedPaths:[];return JSON.stringify(B(e,a,r),t,n)},N=20,x=25e3,k=8,T="...";function L(e){return"[Throws: "+(e?e.message:"?")+"]"}function R(e,t){try{return e[t]}catch(n){return L(n)}}function B(e,d,f){var g=[],h=0;return function p(e,t){function n(){return t.length>k&&x<h}if(h++,t.length>N)return T;if(n())return T;if(null===e||"object"!=typeof e)return e;if(function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return 1}(g,e))return"[Circular]";if(g.push(e),"function"==typeof e.toJSON)try{h--;var r=p(e.toJSON(),t);return g.pop(),r}catch(u){return L(u)}if((r=e)instanceof Error||/^\[object (Error|(Dom)?Exception)\]$/.test(Object.prototype.toString.call(r)))return h--,r=p({name:e.name,message:e.message},t),g.pop(),r;if("[object Array]"===Object.prototype.toString.call(e)){for(var a=[],o=0,l=e.length;o<l;o++){if(n()){a.push(T);break}a.push(p(e[o],t.concat("[]")))}return g.pop(),a}var i={};try{for(var s in e)if(Object.prototype.hasOwnProperty.call(e,s))if(function(e,t){for(var n=0,r=e.length;n<r;n++)if(0===t.indexOf(e[n]))return 1}(f,t.join("."))&&function(e,t){for(var n=0,r=e.length;n<r;n++){if("string"==typeof e[n]&&e[n].toLowerCase()===t.toLowerCase())return 1;if(e[n]&&"function"==typeof e[n].test&&e[n].test(t))return 1}}(d,s))i[s]="[REDACTED]";else{if(n()){i[s]=T;break}i[s]=p(R(e,s),t.concat(s))}}catch(c){}return g.pop(),i}(e,[])}function q(e,t,n,r){var a;"string"==typeof n&&(r===undefined?r=null:null!==r&&"string"!=typeof r&&(r=j(r)),"number"!=typeof(a=t[n])?(e.push({name:n,variant:r}),t[n]=e.length-1):e[a]={name:n,variant:r})}var M={add:q,clear:function(e,t,n){var r=t[n];"number"==typeof r&&(e[r]=null,delete t[n])},merge:function(e,t,n){if(i(t)){for(var r=0;r<t.length;++r){var a=t[r];null!==a&&"object"==typeof a&&q(e,n,a.name,a.variant)}return e}},toEventApi:function(e){return p(h(e,Boolean),function(e){var t=e.name,e=e.variant,t={featureFlag:t};return"string"==typeof e&&(t.variant=e),t})}},I=function(e){return!(!e||!e.stack&&!e.stacktrace&&!e["opera#sourceloc"]||"string"!=typeof(e.stack||e.stacktrace||e["opera#sourceloc"])||e.stack===e.name+": "+e.message)};var A=function(e){switch(Object.prototype.toString.call(e)){case"[object Error]":case"[object Exception]":case"[object DOMException]":return!0;default:return e instanceof Error}},C=function(e,t,n){"string"==typeof t&&(n?e[t]&&delete e[t][n]:delete e[t])},D=function(e,t,n,r){var a,o;if(t){if(null===n)return C(e,t);"object"==typeof n&&(o=n),"string"==typeof n&&((a={})[n]=r,o=a),o&&(e[t]||(e[t]={}),e[t]=u({},e[t],o))}},F=function(e,t,n){return"string"!=typeof t?undefined:n?e[t]?e[t][n]:undefined:e[t]},P=C,V={},d=(!function(e,t){"use strict";"object"==typeof V?V=t(w):e.StackGenerator=t(e.StackFrame)}(this,function(s){return{backtrace:function(e){for(var t=[],n=10,r=("object"==typeof e&&"number"==typeof e.maxStackSize&&(n=e.maxStackSize),arguments.callee);r&&t.length<n&&r.arguments;){for(var a=new Array(r.arguments.length),o=0;o<a.length;++o)a[o]=r.arguments[o];/function(?:\s+([\w$]+))+\s*\(/.test(r.toString())?t.push(new s({functionName:RegExp.$1||undefined,args:a})):t.push(new s({args:a}));try{r=r.caller}catch(i){break}}return t}}}),function(){function o(e,t,n,r,a){void 0===n&&(n=[]),void 0===r&&(r=$()),this.apiKey=undefined,this.context=undefined,this.groupingHash=undefined,this.originalError=a,this._handledState=r,this.severity=this._handledState.severity,this.unhandled=this._handledState.unhandled,this.app={},this.device={},this.request={},this.breadcrumbs=[],this.threads=[],this._metadata={},this._features=[],this._featuresIndex={},this._user={},this._session=undefined,this.errors=[X(e,t,o.__type,n)]}var e=o.prototype;return e.addMetadata=function(e,t,n){return D(this._metadata,e,t,n)},e.getMetadata=function(e,t){return F(this._metadata,e,t)},e.clearMetadata=function(e,t){return P(this._metadata,e,t)},e.addFeatureFlag=function(e,t){M.add(this._features,this._featuresIndex,e,t=void 0===t?null:t)},e.addFeatureFlags=function(e){M.merge(this._features,e,this._featuresIndex)},e.getFeatureFlags=function(){return M.toEventApi(this._features)},e.clearFeatureFlag=function(e){M.clear(this._features,this._featuresIndex,e)},e.clearFeatureFlags=function(){this._features=[],this._featuresIndex={}},e.getUser=function(){return this._user},e.setUser=function(e,t,n){this._user={id:e,email:t,name:n}},e.toJSON=function(){return{payloadVersion:"4",exceptions:p(this.errors,function(e){return u({},e,{message:e.errorMessage})}),severity:this.severity,unhandled:this._handledState.unhandled,severityReason:this._handledState.severityReason,app:this.app,device:this.device,request:this.request,breadcrumbs:this.breadcrumbs,context:this.context,groupingHash:this.groupingHash,metaData:this._metadata,user:this._user,session:this._session,featureFlags:this.getFeatureFlags()}},o}()),H=function(e){e={file:e.fileName,method:K(e.functionName),lineNumber:e.lineNumber,columnNumber:e.columnNumber,code:undefined,inProject:undefined};return-1<e.lineNumber&&!e.file&&!e.method&&(e.file="global code"),e},K=function(e){return/^global code$/i.test(e)?"global code":e},$=function(){return{unhandled:!1,severity:"warning",severityReason:{type:"handledException"}}},U=function(e){return"string"==typeof e?e:""};function X(e,t,n,r){return{errorClass:U(e),errorMessage:U(t),type:n,stacktrace:g(r,function(e,t){t=H(t);try{return"{}"===JSON.stringify(t)?e:e.concat(t)}catch(n){return e}},[])}}d.getStacktrace=function(e,t,n){if(I(e))return O.parse(e).slice(t);try{return h(V.backtrace(),function(e){return-1===(e.functionName||"").indexOf("StackGenerator$$")}).slice(1+n)}catch(r){return[]}},d.create=function(e,t,n,r,a,o){void 0===a&&(a=0);var t=W(e,t,r,o),o=t[0],t=t[1];try{var i=d.getStacktrace(o,0<t?1+t+a:0,1+a),s=new d(o.name,o.message,i,n,e)}catch(u){s=new d(o.name,o.message,[],n,e)}return"InvalidError"===o.name&&s.addMetadata(""+r,"non-error parameter",z(e)),o.cause&&(t=function c(e){return e.cause?[e].concat(c(e.cause)):[e]}(o).slice(1),a=p(t,function(e){var t=A(e)&&I(e)?O.parse(e):[],n=W(e,!0,"error cause")[0];return"InvalidError"===n.name&&s.addMetadata("error cause",z(e)),X(n.name,n.message,d.__type,t)}),(i=s.errors).push.apply(i,a)),s};var J,z=function(e){return null===e?"null":e===undefined?"undefined":e},W=function(e,t,n,r){var a,o=0,i=function(e){var t="error cause"===n?"was":"received",e=(r&&r.warn(n+" "+t+' a non-error: "'+e+'"'),new Error(n+" "+t+' a non-error. See "'+n+'" tab for more detail.'));return e.name="InvalidError",e};if(t)switch(typeof e){case"string":case"number":case"boolean":a=new Error(String(e)),o+=1;break;case"function":a=i("function"),o+=2;break;case"object":null!==e&&A(e)?a=e:null!==e&&G(e)?((a=new Error(e.message||e.errorMessage)).name=e.name||e.errorClass,o+=1):(a=i(null===e?"null":"unsupported object"),o+=2);break;default:a=i("nothing"),o+=2}else A(e)?a=e:(a=i(typeof e),o+=2);if(!I(a))try{throw a}catch(s){I(s)&&(a=s,o=1)}return[a,o]},G=(d.__type="browserjs",function(e){return!("string"!=typeof e.name&&"string"!=typeof e.errorClass||"string"!=typeof e.message&&"string"!=typeof e.errorMessage)}),Q=d,Y=function(e,t,n){var r=0,a=function(){if(r>=e.length)return n(null,!0);t(e[r],function(e,t){return e?n(e):!1===t?n(null,!1):(r++,void a())})};a()},Z=function(e,a,o,t){Y(e,function(e,n){if("function"!=typeof e)return n(null);try{var t;if(2!==e.length)return(t=e(a))&&"function"==typeof t.then?t.then(function(e){return setTimeout(function(){return n(null,e)})},function(e){setTimeout(function(){return o(e),n(null,!0)})}):n(null,t);e(a,function(e,t){if(e)return o(e),n(null);n(null,t)})}catch(r){o(r),n(null)}},t)},ee=function(e,t,n,r){for(var a=!1,o=e.slice();!a&&o.length;)try{a=!1===o.pop()(t)}catch(i){r.error("Error occurred in "+n+" callback, continuing anyway…"),r.error(i)}return a},te=function(e,t){e="000000000"+e;return e.substr(e.length-t)},ne="object"==typeof window?window:self,re=0;for(J in ne)Object.hasOwnProperty.call(ne,J)&&re++;var e=navigator.mimeTypes?navigator.mimeTypes.length:0,ae=te((e+navigator.userAgent.length).toString(36)+re.toString(36),4),oe=function(){return ae},ie=0,se=4,ue=36,ce=Math.pow(ue,se);function le(){return te((Math.random()*ce<<0).toString(ue),se)}function de(){return"c"+(new Date).getTime().toString(ue)+te((ie=ie<ce?ie:0,(++ie-1).toString(ue)),se)+oe()+(le()+le())}de.fingerprint=oe;var fe,ge,he=de,pe=function(){function e(){this.id=he(),this.startedAt=new Date,this._handled=0,this._unhandled=0,this._user={},this.app={},this.device={}}var t=e.prototype;return t.getUser=function(){return this._user},t.setUser=function(e,t,n){this._user={id:e,email:t,name:n}},t.toJSON=function(){return{id:this.id,startedAt:this.startedAt,events:{handled:this._handled,unhandled:this._unhandled}}},t._track=function(e){this[e._handledState.unhandled?"_unhandled":"_handled"]+=1},e}(),me=M.add,ve=M.clear,ye=M.merge,c=function(){},be=function(t,n){return new Error("Invalid configuration\n"+p(o(t),function(e){return" - "+e+" "+t[e]+", got "+_e(n[e])}).join("\n\n"))},_e=function(e){switch(typeof e){case"string":case"number":case"object":return JSON.stringify(e);default:return String(e)}},Se=function(){function s(e,t,n,r){var a=this,o=(void 0===t&&(t=v.schema),void 0===n&&(n=[]),this._notifier=r,this._config={},this._schema=t,this._delivery={sendSession:c,sendEvent:c},this._logger={debug:c,info:c,warn:c,error:c},this._plugins={},this._breadcrumbs=[],this._session=null,this._metadata={},this._featuresIndex={},this._features=[],this._context=undefined,this._user={},this._cbs={e:[],s:[],sp:[],b:[]},this.Client=s,this.Event=Q,this.Breadcrumb=S,this.Session=pe,this._config=this._configure(e,n),p(n.concat(this._config.plugins),function(e){e&&a._loadPlugin(e)}),this._depth=1,this),i=this.notify;this.notify=function(){return i.apply(o,arguments)}}var e=s.prototype;return e.addMetadata=function(e,t,n){return D(this._metadata,e,t,n)},e.getMetadata=function(e,t){return F(this._metadata,e,t)},e.clearMetadata=function(e,t){return P(this._metadata,e,t)},e.addFeatureFlag=function(e,t){me(this._features,this._featuresIndex,e,t=void 0===t?null:t)},e.addFeatureFlags=function(e){ye(this._features,e,this._featuresIndex)},e.clearFeatureFlag=function(e){ve(this._features,this._featuresIndex,e)},e.clearFeatureFlags=function(){this._features=[],this._featuresIndex={}},e.getContext=function(){return this._context},e.setContext=function(e){this._context=e},e._configure=function(r,e){var a=g(e,function(e,t){return t&&t.configSchema?u({},e,t.configSchema):e},this._schema),e=g(o(a),function(e,t){var n=a[t].defaultValue(r[t]);return r[t]!==undefined?a[t].validate(r[t])?a[t].allowPartialObject?e.config[t]=u(n,r[t]):e.config[t]=r[t]:(e.errors[t]=a[t].message,e.config[t]=n):e.config[t]=n,e},{errors:{},config:{}}),t=e.errors,e=e.config;if(a.apiKey){if(!e.apiKey)throw new Error("No Bugsnag API Key set");/^[0-9a-f]{32}$/i.test(e.apiKey)||(t.apiKey="should be a string of 32 hexadecimal characters")}return this._metadata=u({},e.metadata),ye(this._features,e.featureFlags,this._featuresIndex),this._user=u({},e.user),this._context=e.context,e.logger&&(this._logger=e.logger),e.onError&&(this._cbs.e=this._cbs.e.concat(e.onError)),e.onBreadcrumb&&(this._cbs.b=this._cbs.b.concat(e.onBreadcrumb)),e.onSession&&(this._cbs.s=this._cbs.s.concat(e.onSession)),o(t).length&&this._logger.warn(be(t,r)),e},e.getUser=function(){return this._user},e.setUser=function(e,t,n){this._user={id:e,email:t,name:n}},e._loadPlugin=function(e){var t=e.load(this);return e.name&&(this._plugins["~"+e.name+"~"]=t),this},e.getPlugin=function(e){return this._plugins["~"+e+"~"]},e._setDelivery=function(e){this._delivery=e(this)},e.startSession=function(){var e=new pe;return e.app.releaseStage=this._config.releaseStage,e.app.version=this._config.appVersion,e.app.type=this._config.appType,e._user=u({},this._user),ee(this._cbs.s,e,"onSession",this._logger)?(this._logger.debug("Session not started due to onSession callback"),this):this._sessionDelegate.startSession(this,e)},e.addOnError=function(e,t){this._cbs.e[(t=void 0===t?!1:t)?"unshift":"push"](e)},e.removeOnError=function(t){this._cbs.e=h(this._cbs.e,function(e){return e!==t})},e._addOnSessionPayload=function(e){this._cbs.sp.push(e)},e.addOnSession=function(e){this._cbs.s.push(e)},e.removeOnSession=function(t){this._cbs.s=h(this._cbs.s,function(e){return e!==t})},e.addOnBreadcrumb=function(e,t){this._cbs.b[(t=void 0===t?!1:t)?"unshift":"push"](e)},e.removeOnBreadcrumb=function(t){this._cbs.b=h(this._cbs.b,function(e){return e!==t})},e.pauseSession=function(){return this._sessionDelegate.pauseSession(this)},e.resumeSession=function(){return this._sessionDelegate.resumeSession(this)},e.leaveBreadcrumb=function(e,t,n){e="string"==typeof e?e:"",n="string"==typeof n&&l(r,n)?n:"manual",t="object"==typeof t&&null!==t?t:{},e&&(e=new S(e,t,n),ee(this._cbs.b,e,"onBreadcrumb",this._logger)?this._logger.debug("Breadcrumb not attached due to onBreadcrumb callback"):(this._breadcrumbs.push(e),this._breadcrumbs.length>this._config.maxBreadcrumbs&&(this._breadcrumbs=this._breadcrumbs.slice(this._breadcrumbs.length-this._config.maxBreadcrumbs))))},e._isBreadcrumbTypeEnabled=function(e){var t=this._config.enabledBreadcrumbTypes;return null===t||l(t,e)},e.notify=function(e,t,n){void 0===n&&(n=c);e=Q.create(e,!0,undefined,"notify()",this._depth+1,this._logger);this._notify(e,t,n)},e._notify=function(n,e,r){var a=this;if(void 0===r&&(r=c),n.app=u({},n.app,{releaseStage:this._config.releaseStage,version:this._config.appVersion,type:this._config.appType}),n.context=n.context||this._context,n._metadata=u({},n._metadata,this._metadata),n._user=u({},n._user,this._user),n.breadcrumbs=this._breadcrumbs.slice(),ye(n._features,this._features,n._featuresIndex),null!==this._config.enabledReleaseStages&&!l(this._config.enabledReleaseStages,this._config.releaseStage))return this._logger.warn("Event not sent due to releaseStage/enabledReleaseStages configuration"),r(null,n);var o=n.severity,i=function(e){a._logger.error("Error occurred in onError callback, continuing anyway…"),a._logger.error(e)},e=[].concat(this._cbs.e).concat(e);Z(e,n,i,function(e,t){if(e&&i(e),!t)return a._logger.debug("Event not sent due to onError callback"),r(null,n);a._isBreadcrumbTypeEnabled("error")&&s.prototype.leaveBreadcrumb.call(a,n.errors[0].errorClass,{errorClass:n.errors[0].errorClass,errorMessage:n.errors[0].errorMessage,severity:n.severity},"error"),o!==n.severity&&(n._handledState.severityReason={type:"userCallbackSetSeverity"}),n.unhandled!==n._handledState.unhandled&&(n._handledState.severityReason.unhandledOverridden=!0,n._handledState.unhandled=n.unhandled),a._session&&(a._session._track(n),n._session=a._session),a._delivery.sendEvent({apiKey:n.apiKey||a._config.apiKey,notifier:a._notifier,events:[n]},function(e){return r(e,n)})})},s}(),we={},Ee=["events.[].metaData","events.[].breadcrumbs.[].metaData","events.[].request"],Oe=(we.event=function(e,t){var n=j(e,null,null,{redactedPaths:Ee,redactedKeys:t});return 1e6<n.length&&(e.events[0]._metadata={notifier:"WARNING!\nSerialized payload was "+n.length/1e6+"MB (limit = 1MB)\nmetadata was removed"},n=j(e,null,null,{redactedPaths:Ee,redactedKeys:t})),n},we.session=function(e,t){return j(e,null,null)},function(e,t,n,r){var a=JSON.parse(JSON.stringify(new Date));return je(e.endpoints[t],r.location.protocol)+"?apiKey="+encodeURIComponent(e.apiKey)+"&payloadVersion="+n+"&sentAt="+encodeURIComponent(a)}),je=(fe=function(o,i){return void 0===i&&(i=window),{sendEvent:function(e,t){void 0===t&&(t=function(){});var n=Oe(o._config,"notify","4",i),r=we.event(e,o._config.redactedKeys),a=new i.XDomainRequest;a.onload=function(){t(null)},a.onerror=function(){var e=new Error("Event failed to send");o._logger.error("Event failed to send…",e),1e6<r.length&&o._logger.warn("Event oversized ("+(r.length/1e6).toFixed(2)+" MB)"),t(e)},a.open("POST",n),setTimeout(function(){try{a.send(r)}catch(e){o._logger.error(e),t(e)}},0)},sendSession:function(t,n){void 0===n&&(n=function(){});var e=Oe(o._config,"sessions","1",i),r=new i.XDomainRequest;r.onload=function(){n(null)},r.open("POST",e),setTimeout(function(){try{r.send(we.session(t,o._config.redactedKeys))}catch(e){o._logger.error(e),n(e)}},0)}}})._matchPageProtocol=function(e,t){return"http:"===t?e.replace(/^https:/,"http:"):e},Ne=function(i,s){return void 0===s&&(s=window),{sendEvent:function(e,t){void 0===t&&(t=function(){});try{var n=i._config.endpoints.notify,r=new s.XMLHttpRequest,a=we.event(e,i._config.redactedKeys);r.onreadystatechange=function(){var e;r.readyState===s.XMLHttpRequest.DONE&&((0===(e=r.status)||400<=e)&&(i._logger.error("Event failed to send…"),1e6<a.length&&i._logger.warn("Event oversized ("+(a.length/1e6).toFixed(2)+" MB)")),t(null))},r.open("POST",n),r.setRequestHeader("Content-Type","application/json"),r.setRequestHeader("Bugsnag-Api-Key",e.apiKey||i._config.apiKey),r.setRequestHeader("Bugsnag-Payload-Version","4"),r.setRequestHeader("Bugsnag-Sent-At",(new Date).toISOString()),r.send(a)}catch(o){i._logger.error(o)}},sendSession:function(e,t){void 0===t&&(t=function(){});try{var n=i._config.endpoints.sessions,r=new s.XMLHttpRequest;r.onreadystatechange=function(){r.readyState===s.XMLHttpRequest.DONE&&t(null)},r.open("POST",n),r.setRequestHeader("Content-Type","application/json"),r.setRequestHeader("Bugsnag-Api-Key",i._config.apiKey),r.setRequestHeader("Bugsnag-Payload-Version","1"),r.setRequestHeader("Bugsnag-Sent-At",(new Date).toISOString()),r.send(we.session(e,i._config.redactedKeys))}catch(a){i._logger.error(a)}}}},xe=new Date,ke=function(){xe=new Date},Te={name:"appDuration",load:function(e){return e.addOnError(function(e){var t=new Date;e.app.duration=t-xe},!0),{reset:ke}}},Le=function(e,t){e="000000000"+e;return e.substr(e.length-t)},Re="object"==typeof window?window:self,Be=0;for(ge in Re)Object.hasOwnProperty.call(Re,ge)&&Be++;var e=navigator.mimeTypes?navigator.mimeTypes.length:0,qe=Le((e+navigator.userAgent.length).toString(36)+Be.toString(36),4),Me=function(){return qe},Ie=0,Ae=4,Ce=36,De=Math.pow(Ce,Ae);function Fe(){return Le((Math.random()*De<<0).toString(Ce),Ae)}function Pe(){return"c"+(new Date).getTime().toString(Ce)+Le((Ie=Ie<De?Ie:0,(++Ie-1).toString(Ce)),Ae)+Me()+(Fe()+Fe())}Pe.fingerprint=Me;var Ve=Pe,He="bugsnag-anonymous-id",Ke=function(e){try{var t=e.localStorage,n=t.getItem(He);return n&&/^c[a-z0-9]{20,32}$/.test(n)?n:(n=Ve(),t.setItem(He,n),n)}catch(r){}},$e=function(e){var t=e.getUser();t&&t.id||e.setUser(e.device.id)},Ue={load:function(e){e._sessionDelegate=Xe}},Xe={startSession:function(e,t){return e._session=t,(e._pausedSession=null)===e._config.enabledReleaseStages||l(e._config.enabledReleaseStages,e._config.releaseStage)?e._delivery.sendSession({notifier:e._notifier,device:t.device,app:t.app,sessions:[{id:t.id,startedAt:t.startedAt,user:t._user}]}):e._logger.warn("Session not sent due to releaseStage/enabledReleaseStages configuration"),e},resumeSession:function(e){return e._session?e:e._pausedSession?(e._session=e._pausedSession,e._pausedSession=null,e):e.startSession()},pauseSession:function(e){e._pausedSession=e._session,e._session=null}},Je={load:function(e){e._config.collectUserIp||e.addOnError(function(e){e._user&&"undefined"==typeof e._user.id&&delete e._user.id,e._user=u({id:"[REDACTED]"},e._user),e.request=u({clientIp:"[REDACTED]"},e.request)})},configSchema:{collectUserIp:{defaultValue:function(){return!0},message:"should be true|false",validate:function(e){return!0===e||!1===e}}}},ze={load:function(o){!/^(local-)?dev(elopment)?$/.test(o._config.releaseStage)&&o._isBreadcrumbTypeEnabled("log")&&p(We,function(r){var a=console[r];console[r]=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];o.leaveBreadcrumb("Console output",g(t,function(e,t,n){var r="[Unknown value]";try{r=String(t)}catch(a){}if("[object Object]"===r)try{r=JSON.stringify(t)}catch(a){}return e["["+n+"]"]=r,e},{severity:0===r.indexOf("group")?"log":r}),"log"),a.apply(console,t)},console[r]._restore=function(){console[r]=a}})}},We=h(["log","debug","info","warn","error"],function(e){return"undefined"!=typeof console&&"function"==typeof console[e]}),Ge=200,Qe=5e5,Ye=function(d,f){return void 0===d&&(d=document),void 0===f&&(f=window),{load:function(e){var a,o,i,s,t,n,u,c,l;function r(o,i,s){return void 0===s&&(s=!1),function(){var e=[].slice.call(arguments);try{var t,n=i(e),r=n.get();if(s&&o.apply(this,e),"function"!=typeof r)return o.apply(this,e);r.__trace__||(t=c(),r.__trace__=function(){u(t),l(function(){u(null)},0);var e=r.apply(this,arguments);return u(null),e},r.__trace__.__trace__=r.__trace__),n.replace(r.__trace__)}catch(a){}if(o.apply)return o.apply(this,e);switch(e.length){case 1:return o(e[0]);case 2:return o(e[0],e[1]);default:return o()}}}e._config.trackInlineScripts&&(a=f.location.href,s="",o=!!d.attachEvent?"complete"===d.readyState:"loading"!==d.readyState,s=(i=function(){return d.documentElement.outerHTML})(),t=d.onreadystatechange,d.onreadystatechange=function(){"interactive"===d.readyState&&(s=i(),o=!0);try{t.apply(this,arguments)}catch(e){}},n=null,u=function(e){n=e},c=function(){var e,t=d.currentScript||n;return t=t||o?t:(e=d.scripts||d.getElementsByTagName("script"))[e.length-1]},e.addOnError(function(e){e.errors[0].stacktrace=h(e.errors[0].stacktrace,function(e){return!/__trace__$/.test(e.method)});var t,r,n=e.errors[0].stacktrace[0];n&&n.file&&n.file.replace(/#.*$/,"")!==a.replace(/#.*$/,"")||(t=c())&&(t=t.innerHTML,e.addMetadata("script","content",t.length<=Qe?t:t.substr(0,Qe)),n&&n.lineNumber&&(n.code=(t=n.lineNumber,o&&s||(s=i()),e=["\x3c!-- DOC START --\x3e"].concat(s.split("\n")),t-=1,r=Math.max(t-3,0),t=Math.min(3+t,e.length),g(e.slice(r,t),function(e,t,n){return e[r+1+n]=t.length<=Ge?t:t.substr(0,Ge),e},{}))))},!0),l=p(["setTimeout","setInterval","setImmediate","requestAnimationFrame"],function(e){return Ze(f,e,function(e){return r(e,function(t){return{get:function(){return t[0]},replace:function(e){t[0]=e}}})})})[0],p(["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"],function(e){f[e]&&f[e].prototype&&Object.prototype.hasOwnProperty.call(f[e].prototype,"addEventListener")&&(Ze(f[e].prototype,"addEventListener",function(e){return r(e,et)}),Ze(f[e].prototype,"removeEventListener",function(e){return r(e,et,!0)}))}))},configSchema:{trackInlineScripts:{validate:function(e){return!0===e||!1===e},defaultValue:function(){return!0},message:"should be true|false"}}}};function Ze(e,t,n){var r=e[t];if(!r)return r;n=n(r);return e[t]=n,r}function et(t){var n=!!t[1]&&"function"==typeof t[1].handleEvent;return{get:function(){return n?t[1].handleEvent:t[1]},replace:function(e){n?t[1].handleEvent=e:t[1]=e}}}var tt=function(i){return void 0===i&&(i=window),{load:function(a){"addEventListener"in i&&a._isBreadcrumbTypeEnabled("user")&&i.addEventListener("click",function(e){var t,n;try{t=nt(e.target),n=function o(e,t){var n=[e.tagName];e.id&&n.push("#"+e.id);e.className&&e.className.length&&n.push("."+e.className.split(" ").join("."));if(!t.document.querySelectorAll||!Array.prototype.indexOf)return n.join("");try{if(1===t.document.querySelectorAll(n.join("")).length)return n.join("")}catch(a){return n.join("")}{var r;1<e.parentNode.childNodes.length&&(r=Array.prototype.indexOf.call(e.parentNode.childNodes,e)+1,n.push(":nth-child("+r+")"))}if(1===t.document.querySelectorAll(n.join("")).length)return n.join("");if(e.parentNode)return o(e.parentNode,t)+" > "+n.join("");return n.join("")}(e.target,i)}catch(r){n=t="[hidden]",a._logger.error("Cross domain error when tracking click event. See docs: https://tinyurl.com/yy3rn63z")}a.leaveBreadcrumb("UI click",{targetText:t,targetSelector:n},"user")},!0)}}},nt=function(e){var t=(t=(t=e.textContent||e.innerText||"")||"submit"!==e.type&&"button"!==e.type?t:e.value).replace(/^\s+|\s+$/g,"");return e=140,(t=t)&&t.length<=e?t:t.slice(0,e-"(...)".length)+"(...)"};var rt=function(n){void 0===n&&(n=window);var e={load:function(t){var e;"addEventListener"in n&&t._isBreadcrumbTypeEnabled("navigation")&&(n.addEventListener("pagehide",(e=function(e){return function(){return t.leaveBreadcrumb(e,{},"navigation")}})("Page hidden"),!0),n.addEventListener("pageshow",e("Page shown"),!0),n.addEventListener("load",e("Page loaded"),!0),n.document.addEventListener("DOMContentLoaded",e("DOMContentLoaded"),!0),n.addEventListener("load",function(){return n.addEventListener("popstate",e("Navigated back"),!0)}),n.addEventListener("hashchange",function(e){e=e.oldURL?{from:at(e.oldURL,n),to:at(e.newURL,n),state:st(n)}:{to:at(n.location.href,n)};t.leaveBreadcrumb("Hash changed",e,"navigation")},!0),n.history.replaceState&&it(t,n.history,"replaceState",n),n.history.pushState&&it(t,n.history,"pushState",n))}};return e},at=function(e,t){t=t.document.createElement("A");return t.href=e,""+t.pathname+t.search+t.hash},ot=function(e,t,n,r){var a=at(e.location.href,e);return{title:n,state:t,prevState:st(e),to:r||a,from:a}},it=function(r,a,o,i){var s=a[o];a[o]=function(e,t,n){r.leaveBreadcrumb("History "+o,ot(i,e,t,n),"navigation"),"function"==typeof r.resetEventCount&&r.resetEventCount(),r._config.autoTrackSessions&&r.startSession(),s.apply(a,[e,t].concat(n!==undefined?n:[]))}},st=function(e){try{return e.history.state}catch(t){}},ut="request",ct=function(e,t){void 0===e&&(e=[]),void 0===t&&(t=window);var n={load:function(i){var s,n,o,u,c;i._isBreadcrumbTypeEnabled("request")&&(s=[i._config.endpoints.notify,i._config.endpoints.sessions].concat(e),"addEventListener"in t.XMLHttpRequest.prototype&&(n=t.XMLHttpRequest.prototype.open,t.XMLHttpRequest.prototype.open=function(r,a){var o=this,e=function(){var e,t;e=r,(t=a)!==undefined?"string"==typeof t&&l(s,t.replace(/\?.*$/,""))||i.leaveBreadcrumb("XMLHttpRequest error",{request:e+" "+t},ut):i._logger.warn("The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.")},t=function(){var e,t,n;e=r,t=a,n=o.status,t!==undefined?"string"==typeof t&&l(s,t.replace(/\?.*$/,""))||(e={status:n,request:e+" "+t},400<=n?i.leaveBreadcrumb("XMLHttpRequest failed",e,ut):i.leaveBreadcrumb("XMLHttpRequest succeeded",e,ut)):i._logger.warn("The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.")};this.addEventListener("load",t),this.addEventListener("error",e),n.apply(this,arguments)}),"fetch"in t&&!t.fetch.polyfill&&(o=t.fetch,t.fetch=function(){var r,e=arguments,t=arguments[0],n=arguments[1],a=null;return t&&"object"==typeof t?(a=t.url,n&&"method"in n?r=n.method:t&&"method"in t&&(r=t.method)):(a=t,n&&"method"in n&&(r=n.method)),r===undefined&&(r="GET"),new Promise(function(t,n){o.apply(void 0,e).then(function(e){u(e,r,a),t(e)})["catch"](function(e){c(r,a),n(e)})})}),u=function(e,t,n){t={status:e.status,request:t+" "+n};400<=e.status?i.leaveBreadcrumb("fetch() failed",t,ut):i.leaveBreadcrumb("fetch() succeeded",t,ut)},c=function(e,t){i.leaveBreadcrumb("fetch() error",{request:e+" "+t},ut)})}};return n},lt={load:function(t){var n=0;t.addOnError(function(e){if(n>=t._config.maxEvents)return t._logger.warn("Cancelling event send due to maxEvents per session limit of "+t._config.maxEvents+" being reached"),!1;n++}),t.resetEventCount=function(){n=0}},configSchema:{maxEvents:{defaultValue:function(){return 10},message:"should be a positive integer ≤100",validate:function(e){return t(1,100)(e)}}}},dt={},ft=(dt={load:function(e){e.addOnError(function(e){e=g(e.errors,function(e,t){return e.concat(t.stacktrace)},[]);p(e,function(e){e.file=ft(e.file)})})}})._strip=function(e){return"string"==typeof e?e.replace(/\?.*$/,"").replace(/#.*$/,""):e},gt=function(e,d){return void 0===e&&(e=window),void 0===d&&(d="window onerror"),{load:function(c){var l;c._config.autoDetectErrors&&c._config.enabledErrorTypes.unhandledExceptions&&(l=e.onerror,e.onerror=function(e,t,n,r,a){var o,i,s,u;0===n&&/Script error\.?/.test(e)?c._logger.warn("Ignoring cross-domain or eval script error. See docs: https://tinyurl.com/yy3rn63z"):(o={severity:"error",unhandled:!0,severityReason:{type:"unhandledException"}},a?(u=c.Event.create(a,!0,o,d,1),ht(u.errors[0].stacktrace,t,n,r)):"object"!=typeof e||null===e||t&&"string"==typeof t||n||r||a?(u=c.Event.create(e,!0,o,d,1),ht(u.errors[0].stacktrace,t,n,r)):(i=e.type?"Event: "+e.type:"Error",s=e.message||e.detail||"",(u=c.Event.create({name:i,message:s},!0,o,d,1)).originalError=e,u.addMetadata(d,{event:e,extraParameters:t})),c._notify(u)),"function"==typeof l&&l.apply(this,arguments)})}}},ht=function(e,t,n,r){e[0]||e.push({});e=e[0];e.file||"string"!=typeof t||(e.file=t),!e.lineNumber&&pt(n)&&(e.lineNumber=n),e.columnNumber||(pt(r)?e.columnNumber=r:window.event&&pt(window.event.errorCharacter)&&(e.columnNumber=window.event.errorCharacter))},pt=function(e){return"number"==typeof e&&"NaN"!==String.call(e)},mt=function(t){return function(e){e.file!==t.toString()&&e.method&&(e.method=e.method.replace(/^\s+/,""))}},e={},vt=u({},v.schema,a),n={_client:null,createClient:function(e){e=(e="string"==typeof e?{apiKey:e}:e)||{};var t,n,r,a,o,i=[Te,(void 0===a&&(a=navigator),void 0===o&&(o=window),{load:function(t){var n={locale:a.browserLanguage||a.systemLanguage||a.userLanguage||a.language,userAgent:a.userAgent};o&&o.screen&&o.screen.orientation&&o.screen.orientation.type?n.orientation=o.screen.orientation.type:o&&o.document&&(n.orientation=o.document.documentElement.clientWidth>o.document.documentElement.clientHeight?"landscape":"portrait"),t._config.generateAnonymousId&&(n.id=Ke(o)),t.addOnSession(function(e){e.device=u({},e.device,n),t._config.collectUserIp||$e(e)}),t.addOnError(function(e){e.device=u({},e.device,n,{time:new Date}),t._config.collectUserIp||$e(e)},!0)},configSchema:{generateAnonymousId:{validate:function(e){return!0===e||!1===e},defaultValue:function(){return!0},message:"should be true|false"}}}),(void 0===r&&(r=window),{load:function(e){e.addOnError(function(e){e.context===undefined&&(e.context=r.location.pathname)},!0)}}),(void 0===n&&(n=window),{load:function(e){e.addOnError(function(e){e.request&&e.request.url||(e.request=u({},e.request,{url:n.location.href}))},!0)}}),lt,Ue,Je,dt,gt(),(void 0===t&&(t=window),{load:function(a){var n;a._config.autoDetectErrors&&a._config.enabledErrorTypes.unhandledRejections&&(n=function(e){var t=e.reason,n=!1;try{e.detail&&e.detail.reason&&(t=e.detail.reason,n=!0)}catch(r){}e=a.Event.create(t,!1,{severity:"error",unhandled:!0,severityReason:{type:"unhandledPromiseRejection"}},"unhandledrejection handler",1,a._logger);n&&p(e.errors[0].stacktrace,mt(t)),a._notify(e,function(e){var t;A(e.originalError)&&!e.originalError.stack&&e.addMetadata("unhandledRejection handler",((t={})[Object.prototype.toString.call(e.originalError)]={name:e.originalError.name,message:e.originalError.message,code:e.originalError.code},t))})},"addEventListener"in t?t.addEventListener("unhandledrejection",n):t.onunhandledrejection=function(e,t){n({detail:{reason:e,promise:t}})},0)}}),rt(),tt(),ct(),ze,Ye()],e=new Se(e,vt,i,{name:"Bugsnag JavaScript",version:"7.19.0",url:"https://github.com/bugsnag/bugsnag-js"});return e._setDelivery(window.XDomainRequest?fe:Ne),e._logger.debug("Loaded!"),e.leaveBreadcrumb("Bugsnag loaded",{},"state"),e._config.autoTrackSessions?e.startSession():e},start:function(e){return n._client?n._client._logger.warn("Bugsnag.start() was called more than once. Ignoring."):n._client=n.createClient(e),n._client},isStarted:function(){return null!=n._client}};return p(["resetEventCount"].concat(o(Se.prototype)),function(t){/^_/.test(t)||(n[t]=function(){if(!n._client)return console.log("Bugsnag."+t+"() was called before Bugsnag.start()");n._client._depth+=1;var e=n._client[t].apply(n._client,arguments);return--n._client._depth,e})}),(e=n).Client=Se,e.Event=Q,e.Session=pe,e.Breadcrumb=S,e["default"]=n,e});
//# sourceMappingURL=https://d2wy8f7a9ursnm.cloudfront.net/v7/bugsnag.min.js.map
</script>
<script type="text/javascript" nonce="E6yu4&#x2f;&#x2f;sBBvJEXLdNHUXgg&#x3d;&#x3d;">
Bugsnag && Bugsnag.start({
apiKey: "61a78eaae6bdd5b2d62b9529bf058818"
});
</script>
<script type="text/javascript" nonce="E6yu4&#x2f;&#x2f;sBBvJEXLdNHUXgg&#x3d;&#x3d;">
(function() {
if ( ! window.matchMedia ) {
return;
}
var prefersTheme = window.matchMedia( "( prefers-color-scheme: dark )" ).matches
? "dark"
: "light"
;
// CAUTION: Some browsers are throwing an error when accessing the local
// storage even when the API exists. Some browsers appear to throw an error
// even during the CHECK for EXISTENCE!
try {
var evaluatedTheme = ( localStorage.getItem( "theme" ) || prefersTheme );
} catch ( error ) {
var evaluatedTheme = prefersTheme;
console.warn( "Local storage access error", error );
}
document.documentElement.setAttribute( "data-theme", evaluatedTheme );
})();
</script>
<meta name="turbo-cache-control" content="no-preview" />
<link
rel="preload"
href="https://bennadel-cdn.com/images/header/photos/paul_james.jpg?v=2023.07.04.11.24"
as="image"
media="(min-width: 1024px)"
/>
<link nonce="E6yu4&#x2f;&#x2f;sBBvJEXLdNHUXgg&#x3d;&#x3d;" rel="stylesheet" href="https://bennadel-cdn.com/static/main/site/site.f1eb23ab.css" />
<script nonce="E6yu4&#x2f;&#x2f;sBBvJEXLdNHUXgg&#x3d;&#x3d;" type="module" src="https://bennadel-cdn.com/static/main/site/site.70279dff.js"></script>
</head>
<body id="body" class="standard">
<a href="#main-content" data-turbo="false" class="skip-to-main">
Skip to main content
</a>
<div
id="primary-navigation"
data-controller="nav click-outside"
data-action="click-outside:click->nav#close"
data-nav-open-class="open"
class="m-sl-nav-bar ">
<div class="m-sl-nav-bar__wrapper">
<a href="/" class="m-sl-nav-bar__logo">
Ben Nadel . com
</a>
<button data-action="nav#toggleMenu" class="ds-text-button m-sl-nav-bar__toggle">
Navigation
</button>
<nav data-nav-target="menu" class="m-sl-nav-bar__nav">
<ul class="m-sl-nav-bar__items">
<li class="m-sl-nav-bar__item">
<a
href="/"
title="Ben Nadel's Blog on modern web application development."
class="m-sl-nav-bar__link">
Home
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/activity/recent-activity.htm"
title="Recent activity on Ben Nadel's blog."
class="m-sl-nav-bar__link">
Activity
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/blog/tags/35-podcast-blog-entries.htm"
title="Ben Nadel's podcast, Working Code."
class="m-sl-nav-bar__link">
Podcast
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/books/feature-flags/overview.htm"
title="Ben Nadel's book on Feature Flags: Transform Your Product Development Workflow."
class="m-sl-nav-bar__link">
My Book
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/about/about-ben-nadel.htm"
title="Learn more about Ben Nadel, Co-founder of InVision App, Inc."
class="m-sl-nav-bar__link">
About Me
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/people/who-rock-my-world.htm"
title="The amazing people who rock Ben Nadel's world on the daily!"
class="m-sl-nav-bar__link">
People
</a>
</li>
<li class="m-sl-nav-bar__item">
<a
href="/contact/contact-ben-nadel.htm"
title="Contact Ben Nadel with questions about web development."
class="m-sl-nav-bar__link">
Contact
</a>
</li>
<li
aria-hidden="true"
data-controller="nav-theme"
class="m-sl-nav-bar__item m-sl-nav-bar__themer">
<button
data-theme="light"
data-action="nav-theme#setTheme"
tabindex="-1"
class="m-sl-nav-bar__theme">
Light UI
</button>
<button
data-theme="dark"
data-action="nav-theme#setTheme"
tabindex="-1"
class="m-sl-nav-bar__theme">
Dark UI
</button>
</li>
</ul>
</nav>
</div>
</div>
<header class="l-site-header" >
<figure
id="site-photo-figure"
data-controller="site-photos"
data-action="mouseenter->site-photos#preloadPhotos"
class="site-photo">
<a href="/people/494-paul-james.htm" class="site-photo__hero-link">
<picture>
<source
srcset="https://bennadel-cdn.com/images/header/photos/paul_james.jpg?v=2023.07.04.11.24"
media="(min-width: 1024px)"
/>
<img
data-rel="494"
fetchpriority="high"
loading="eager"
alt="Ben&#x20;Nadel&#x20;at&#x20;cf.Objective&#x28;&#x29;&#x20;2011&#x20;&#x28;Minneapolis,&#x20;MN&#x29;&#x20;with&#x3a;&#x20;Paul&#x20;James"
class="site-photo__hero-image"
/>
</picture>
</a>
<figcaption class="site-photo__caption">
<span class="site-photo__caption__description">
Ben Nadel at cf.Objective&#x28;&#x29; 2011 &#x28;Minneapolis, MN&#x29; with&#x3a;
</span>
<span class="site-photo__caption__item">
<a href="http&#x3a;&#x2f;&#x2f;www.linkedin.com&#x2f;pub&#x2f;paul-james&#x2f;b&#x2f;0&#x2f;279" target="_blank" rel="noopener">Paul James</a>
</span>
</figcaption>
<div class="site-photo__nav">
<a
href="/blog/3944-why-ive-been-merging-microservices-back-into-the-monolith-at-invision.htm?site-photo=493"
data-action="focus->site-photos#preloadPhotos"
data-site-photos-target="prevLink"
data-image-src="https://bennadel-cdn.com/images/header/photos/haley_groves.jpg?v=2023.07.04.11.24"
rel="nofollow"
class="site-photo__prev prev">
&larr; <span>Previous Photo</span>
</a>
<a
href="/blog/3944-why-ive-been-merging-microservices-back-into-the-monolith-at-invision.htm?site-photo=495"
data-action="focus->site-photos#preloadPhotos"
data-site-photos-target="nextLink"
data-image-src="https://bennadel-cdn.com/images/header/photos/alec_irwin.jpg?v=2023.07.04.11.24"
rel="nofollow"
class="site-photo__next next">
<span>Next Photo</span> &rarr;
</a>
</div>
</figure>
</header>
<div id="site-content" class="l-site-body">
<div class="l-site-body__content content">
<main id="main-content">
<article
id="blog-post"
data-controller="blog-post"
data-id="3944"
data-gist-id="">
<header class="m-post-header">
<h1
data-controller="blog-title"
data-action="dblclick->blog-title#highlight"
class="m-post-header__title">
Why I&#x27;ve Been Merging Microservices Back Into The Monolith At InVision
</h1>
<div class="m-post-header__author">
By <a href="/about/about-ben-nadel.htm" rel="author">Ben Nadel</a>
</div>
<div class="m-post-header__meta">
Published
<time datetime="2020-12-21">2020-12-21</time>
in
<a href="/blog/tags/1-coldfusion-blog-entries.htm" rel="tag" class="tag">ColdFusion</a>,
<a href="/blog/tags/10-work-blog-entries.htm" rel="tag" class="tag">Work</a>
&mdash;
<a href="#comments">Comments</a> (30)
</div>
</header>
<p>If you follow me on Twitter, you may notice that every <a href="https://twitter.com/BenNadel/status/1097596366321303552">now (1)</a> <a href="https://twitter.com/BenNadel/status/1321530362909044737">and (2)</a> <a href="https://twitter.com/BenNadel/status/1338904547159379970">then (3)</a> I post a celebratory tweet about merging one of our microservices <strong>back into the monolith</strong> at <a href="/invision/co-founder.htm?redirect=https%3A%2F%2Fwww.invisionapp.com%2F%3Fsource%3Dbennadel.com" title="InVision is the digital product design platform used to make the world's best customer experiences.">InVision</a>. My tweets are usually accompanied by a Thanos GIF in which Thanos is returning the last Infinity Stone to the Infinity Gauntlet. I find this GIF quite fitting as the reuniting of the stones gives Thanos immense power; much in the same way that <em>reuniting the microservices</em> give <em>me and my team</em> power. I've been asked several times as to why it is that I am killing-off my microservices. So, I wanted to share a bit more insight about this particular journey in the world of web application development.</p>
<div class="m-image-tile"><img src="https://bennadel-cdn.com/resources/uploads/2020/thanos-inserting-the-last-inifinity-stone.gif" width="700" height="285" alt="Thanos returning the last stone to the Infinity Gauntlet" loading="lazy">
</div>
<h3><a aria-hidden="true" tabindex="-1" id="i-am-not-anti-microservices" href="#i-am-not-anti-microservices" title="Link directly to this section: I Am Not &quot;Anti-Microservices&quot;" class="m-title-anchor"></a>I Am Not "Anti-Microservices"</h3>
<p>To be very clear, I wanted to start this post off by stating unequivocally that I am <strong>not anti-microservices</strong>. My merging of services back into the monolith is not some crusade to get microservices out of my life. This quest is intended to <strong>"right size" the monolith</strong>. What I am doing is <em>solving a pain-point</em> for my team. If it weren't reducing friction, I wouldn't spend <strong>so much time (and opportunity cost)</strong> lifting, shifting, and refactoring old code.</p>
<div class="m-image-tile"><img src="https://bennadel-cdn.com/resources/uploads/2020/merging-microservices-effort-is-not-free.png" width="650" height="347" alt="Tweet highlight: 3 weeks and about 40 JIRA tickets worth of effort." loading="lazy">
</div>
<p>Every time I do this, I run the risk of introducing new bugs and breaking the user experience. Merging microservices back into the monolith, while sometimes exhilarating, it <em>always terrifying</em>; and, represents a <em>Master Class</em> in planning, risk reduction, and testing. Again, if it weren't worth doing, I wouldn't be doing it.</p>
<h3><a aria-hidden="true" tabindex="-1" id="microservices-solve-both-technical-and-people-problems" href="#microservices-solve-both-technical-and-people-problems" title="Link directly to this section: Microservices Solve Both Technical and People Problems" class="m-title-anchor"></a>Microservices Solve Both Technical <em>and</em> People Problems</h3>
<p>In order to understand why I am destroying some microservices, it's important to understand why microservices get created in the first place. Microservices solve two types of problems: <strong>Technical problems</strong> and <strong>People problems</strong>.</p>
<p>A <strong>Technical problem</strong> is one in which an aspect of the application is putting an undue burden on the infrastructure; which, in turn, is likely causing a poor user experience (UX). For example, image processing requires a lot of CPU. If this CPU load becomes too great, it could start starving the rest of the application of processing resources. This could affect system latency. And, if it gets bad enough, it could start affecting <em>system availability</em>.</p>
<p>A <strong>People problem</strong>, on the other hand, has little to do with the application at all and everything to do with how your team is organized. The more people you have working in any given part of the application, the slower and more error-prone development and deployment becomes. For example, if you have 30 engineers all competing to "Continuously Deploy" (CD) the same service, you're going to get a lot of queuing; which means, a lot of engineers that <em>could otherwise be shipping product</em> are actually sitting around waiting for their turn to deploy.</p>
<h3><a aria-hidden="true" tabindex="-1" id="early-invision-microservices-mostly-solved-people-problems" href="#early-invision-microservices-mostly-solved-people-problems" title="Link directly to this section: Early InVision Microservices Mostly Solved &quot;People&quot; Problems" class="m-title-anchor"></a>Early InVision Microservices Mostly Solved "People" Problems</h3>
<p><a href="/invision/co-founder.htm?redirect=https%3A%2F%2Fwww.invisionapp.com%2F%3Fsource%3Dbennadel.com" title="InVision is the digital product design platform used to make the world's best customer experiences.">InVision</a> has been a monolithic system since its onset 8-years ago when <strong>3 engineers</strong> were working on it. As the company began to grow and gain traction, the <em>number of systems</em> barely increased while the <em>size of the engineering team</em> began to grow rapidly. In a few years, we had <em>dozens</em> of engineers - both back-end and front-end - all working on the same codebase and all deploying to the same service queue.</p>
<p>As I mentioned above, having a lot of people all working in the same place can become very problematic. Not only were the various teams all competing for the same deployment resources, it meant that every time an "Incident" was declared, several teams' code had to get rolled-back; and, <em>no team could deploy</em> while an incident was being managed. As you can imagine, this was causing a <em>lot of friction</em> across the organization, both for the engineering team and for the product team.</p>
<p>And so, "microservices" were born to solve the <strong>"People problem"</strong>. A select group of engineers started drawing boundaries around parts of the application that they felt corresponded to <em>team boundaries</em>. This was done so that teams could work more independently, deploy independently, and ship more product. Early InVision microservices had <em>almost nothing to do</em> with solving technical problems.</p>
<h3><a aria-hidden="true" tabindex="-1" id="conways-law-is-good-if-your-boundaries-are-good" href="#conways-law-is-good-if-your-boundaries-are-good" title="Link directly to this section: Conway's Law Is Good If Your Boundaries Are Good" class="m-title-anchor"></a>Conway's Law Is Good If Your Boundaries Are Good</h3>
<p>If you work with microservices, you've undoubtedly heard of <a href="https://en.wikipedia.org/wiki/Conway%27s_law">"Conway's Law"</a>, introduced by <a href="https://www.melconway.com/">Melvin Conway</a> in 1967. It states:</p>
<blockquote>
<p>Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.</p>
</blockquote>
<p>This law is often illustrated with a "compiler" example:</p>
<blockquote>
<p>If you have four groups working on a compiler, you'll get a 4-pass compiler.</p>
</blockquote>
<p>The idea here being that the solution is "optimized" around team structures (and team communication overhead) and not necessarily designed to solve any particular technical or performance issues.</p>
<p>In the world <em>before microservices</em>, Conway's Law was generally discussed in a negative light. As in, Conway's Law represented poor planning and organization of your application. But, in a <em>post-microservices</em> world, Conway's Law is given much more latitude. Because, as it turns out, if you can break your system up into a set of <strong>independent services</strong> with <strong>cohesive boundaries</strong>, you can ship more product with fewer bugs because you've created teams that are much more focused working on set of services that entail a narrower set of responsibilities.</p>
<p>Of course, the <em>benefits</em> of Conway's Law depend heavily on <em>where you draw boundaries</em>; and, how those boundaries <strong>evolve over time</strong>. And this is where me and my team - the <strong>Rainbow Team</strong> - come into the picture.</p>
<p>Over the years, <a href="/invision/co-founder.htm?redirect=https%3A%2F%2Fwww.invisionapp.com%2F%3Fsource%3Dbennadel.com" title="InVision is the digital product design platform used to make the world's best customer experiences.">InVision</a> has had to evolve from both an organizational and an infrastructure standpoint. What this means is that, <em>under the hood</em>, there is an older "legacy" platform and a growing "modern" platform. As more of our teams migrate to the "modern" platform, the services for which those teams were responsible need to get handed-off to the remaining "legacy" teams.</p>
<p>Today - in 2020 - <strong>my team is the legacy team</strong>. My team has slowly but steadily become responsible for more and more services. Which means: fewer people but more repositories, more programming languages, more databases, more monitoring dashboards, more error logs, and more late-night pages.</p>
<p>In short, all the <em>benefits</em> of Conway's Law for the organization have become <strong>liabilities</strong> over time for my "legacy" team. And so, we've been trying to "right size" our domain of responsibility, bringing balance back to Conway's Law. Or, in other words, we're trying to alter <strong>our service boundaries</strong> to match <strong>our team boundary</strong>. Which means, <strong>merging microservices <em>back</em> into the monolith</strong>.</p>
<h3><a aria-hidden="true" tabindex="-1" id="microservices-are-not-micro-they-are-right-sized" href="#microservices-are-not-micro-they-are-right-sized" title="Link directly to this section: Microservices Are Not &quot;Micro&quot;, They Are &quot;Right Sized&quot;" class="m-title-anchor"></a>Microservices Are Not "Micro", They Are "Right Sized"</h3>
<p>Perhaps the worst thing that's ever happened to the microservices architecture is the term, "micro". Micro is a <em>meaningless</em> but heavily loaded term that's practically dripping with historical connotations and human bias. A far more helpful term would have been, "right sized". Microservices were never intended to be "small services", they were intended to be "right sized services."</p>
<p>"Micro" is apropos of nothing; it means nothing; it entails nothing. "Right sized", on the other hand, entails that a service has been <strong>appropriately designed</strong> to meet its requirements: it is responsible for the "right amount" of functionality. And, what's "right" is not a static notion - it is dependent on the team, its skill-set, the state of the organization, the calculated return-on-investment (ROI), the cost of ownership, and the moment in time in which that service is operating.</p>
<p>For my team, "right sized" means fewer repositories, fewer deployment queues, fewer languages, and fewer operational dashboards. For my <em>rather small</em> team, "right sized" is more about "People" than it is about "Technology". So, in the same way that <a href="/invision/co-founder.htm?redirect=https%3A%2F%2Fwww.invisionapp.com%2F%3Fsource%3Dbennadel.com" title="InVision is the digital product design platform used to make the world's best customer experiences.">InVision</a> originally introduced microservices to solve "People problems", my team is now destroying those very same microservices in order to solve "People problems".</p>
<p>The gesture is the same, the manifestation is different.</p>
<p>I am extremely proud of my team and our efforts on the legacy platform. We are small band of warriors; but we accomplish quite a lot with what we have. I attribute this success to our deep knowledge of the legacy platform; our <strong>aggressive pragmatism</strong>; and, our continued efforts to design a system that <em>speaks to our abilities</em> rather than an attempt to expand our abilities to match our system demands. That might sound narrow-minded; but, it is the only approach that is tenable <em>for our team and its resources in this moment in time</em>.</p>
<h3><a aria-hidden="true" tabindex="-1" id="epilogue-most-technology-doesnt-have-to-scale-independently" href="#epilogue-most-technology-doesnt-have-to-scale-independently" title="Link directly to this section: Epilogue: Most Technology Doesn't Have to &quot;Scale Independently&quot;" class="m-title-anchor"></a>Epilogue: Most Technology Doesn't Have to "Scale Independently"</h3>
<p>One of the arguments in favor of creating independent services is the idea that those services can then "scale independently". Meaning, you can be more targeted in how you provision servers and databases to meet service demands. So, rather than creating massive services to scale only a portion of the functionality, you can leave some services small while <em>independently</em> scaling-up other services.</p>
<p>Of all the reasons as to why independent services are a "Good Thing", this one gets used very often but is, in my (very limited) opinion, usually irrelevant. Unless a piece of functionality is <strong>CPU bound</strong> or <strong>IO bound</strong> or <strong>Memory bound</strong>, independent scalability is probably not the <em>"ility"</em> you have to worry about. Much of the time, your servers are <em>waiting for things to do</em>; adding "more HTTP route handlers" to an application is not going to suddenly drain it of all of its resources.</p>
<p>If I could go back and <strong>redo our early microservice attempts</strong>, I would 100% start by focusing on all the "CPU bound" functionality first: image processing and resizing, thumbnail generation, PDF exporting, PDF importing, file versioning with <code>rdiff</code>, ZIP archive generation. I would have broken teams out along those boundaries, and have them create "pure" services that dealt with nothing but Inputs and Outputs (ie, no "integration databases", no "shared file systems") such that every other service could consume them while maintaining loose-coupling.</p>
<p>I'm not saying this would have solved all our problems - after all, we had more "people" problems than we did "technology" problems; but, it would have solved some more of the "right" problems, which may have made life a bit easier in the long-run.</p>
<h3><a aria-hidden="true" tabindex="-1" id="epilogue-microservices-also-have-a-dollars-and-cents-cost" href="#epilogue-microservices-also-have-a-dollars-and-cents-cost" title="Link directly to this section: Epilogue: Microservices Also Have a Dollars-And-Cents Cost" class="m-title-anchor"></a>Epilogue: Microservices <em>Also</em> Have a Dollars-And-Cents Cost</h3>
<p>Service don't run in the abstract: they run on servers and talk to databases and report metrics and generate log entries. All of that has a very real dollars-and-cents cost. So while your "lambda function" doesn't cost you money when you're not using it, your "microservices" most certainly do. Especially when you consider the redundancy that you need to maintain in order to create a "highly available" system.</p>
<p>My team's merging of microservices back into the monolith has had an actual impact on the bottom-line of the business (in a good way). It's not massive - we're only talking about a few small services; but, it's not zero either. So, in additional to all of the "People" benefits we get from merging the systems together, we <em>also get</em> a dollars-and-cents benefit as well.</p>
<p class="m-go-link">
<a
href="https://bennadel.com/3944"
target="_blank"
rel="nofollow"
class="m-go-link__link">
<strong class="m-go-link__header">
Short link:
</strong>
<span class="m-go-link__text">
https://bennadel.com/3944
</span>
</a>
</p>
<div aria-hidden="true" class="ds-hrule">
<br />
</div>
<!-- BEGIN: Related Posts. -->
<aside data-controller="blog-related-posts" class="m-related-posts">
<h2 class="m-related-posts__title">
You Might Also Enjoy Some of My Other Posts
</h2>
<ul
data-blog-related-posts-target="list"
class="m-related-posts__list m-related-posts__list--truncated">
<li class="m-related-posts__item">
<a
href="/blog/4713-dave-farley-on-what-makes-high-quality-code.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Dave Farley On What Makes High Quality Code</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4693-code-isnt-magical-its-just-a-series-of-commands.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Code Isn't Magical, It's Just A Series Of Commands</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4495-layla-porter-on-modular-monolith-boundaries.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Layla Porter On Modular Monolith Boundaries</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4452-john-galls-law-on-building-complex-systems.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>John Gall's Law On Building Complex Systems</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4139-adam-lenda-always-choose-consistently-bad-over-inconsistently-good.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Adam Lenda: Always Choose Consistently Bad Over Inconsistently Good</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4020-a-peek-into-the-interstitial-cost-of-microservices.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>A Peek Into The Interstitial Cost Of Microservices</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/4014-the-coldfusion-monolith-allows-my-team-to-move-fast-and-deploy-hella-often.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>The ColdFusion Monolith Allows My Team To Move Fast And Deploy Hella Often</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3733-monolith-to-microservices-evolutionary-patterns-to-transform-your-monolith-by-sam-newman.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Monolith To Microservices: Evolutionary Patterns To Transform Your Monolith By Sam Newman</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3504-microservices-patterns-with-examples-in-java-by-chris-richardson.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Microservices Patterns: With Examples In Java By Chris Richardson</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3470-net-microservices-architecture-for-containerized-net-applications.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>.NET Microservices: Architecture For Containerized .NET Applications</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3434-designing-distributed-systems-patterns-and-paradigms-for-scalable-reliable-services-by-brendan-burns.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Designing Distributed Systems: Patterns And Paradigms For Scalable, Reliable Services By Brendan Burns</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3423-enterprise-integration-patterns-designing-building-and-deploying-messaging-solutions-by-gregor-hohpe-and-bobby-woolf.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Enterprise Integration Patterns: Designing, Building, And Deploying Messaging Solutions By Gregor Hohpe And Bobby Woolf</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3222-production-ready-microservices-building-standardized-systems-across-an-engineering-organization-by-susan-j-fowler.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Production-Ready Microservices: Building Standardized Systems Across An Engineering Organization By Susan J. Fowler</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3162-release-it-design-and-deploy-production-ready-software-by-michael-t-nygard.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Release It! Design And Deploy Production-Ready Software By Michael T. Nygard</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3158-scalability-rules-50-principles-for-scaling-web-sites-by-martin-l-abbott-and-michael-t-fisher.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Scalability Rules - 50 Principles For Scaling Web Sites By Martin L. Abbott And Michael T. Fisher</a>
</li>
<li class="m-related-posts__item">
<a
href="/blog/3154-building-microservices-designing-fine-grained-systems-by-sam-newman.htm"
data-blog-related-posts-target="link"
class="m-related-posts__link"
>Building Microservices (Designing Fine-Grained Systems) By Sam Newman</a>
</li>
</ul>
<div
aria-hidden="true"
data-blog-related-posts-target="actions"
class="m-related-posts__actions">
<button
data-action="blog-related-posts#expand"
class="ds-text-button m-related-posts__action">
Show all related posts
</button>
</div>
</aside>
<!-- END: Related Posts. -->
<div aria-hidden="true" class="ds-hrule">
<br />
</div>
<!-- BEGIN: Comments. -->
<section id="comments" data-controller="prism" class="m-comments">
<h2>
Reader Comments
</h2>
<!-- BEGIN: Comment. -->
<article
id="comments_52468"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14259/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14259-gerald-venzl.htm" rel="author" class="author">Gerald Venzl</a>
<a href="#comments_52468" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 10:41 AM</time>
</a>
<div class="count">
2 Comments
</div>
</header>
<div class="content">
<p>Great read, and so spot on!</p>
<p>Too often we hear at conferences or read on the web that the "new way of developing is breaking up your monolith into microservices" without talking about the potential downsides we will have to deal with by doing so.</p>
<p>It's not about refactoring your code to keep ourselves busy, it's about the right tools and methods for the right job. You have captured this very elegantly in this post and also put some reality into it by sharing why you are doing what you are doing at InVision.</p>
<p>Thank you!</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52469"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52469" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 11:43 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Gerald,</p>
<p>Thank you for the kind words. It's always tough with the <em>new shiny thing</em>! Developers generally want to play with the cool new tools. And, given the fact that the average job in this industry is only <em>2-years</em>, we don't all get the benefit of having to live with choices and see what impact they have over time. I count myself as quite lucky to be working on the same application for over 8-years now as it gives me a perspective that not everybody gets to have.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52470"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/942/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/942-brad-wood.htm" rel="author" class="author">Brad Wood</a>
<a href="#comments_52470" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 1:00 PM</time>
</a>
<div class="count">
45 Comments
</div>
</header>
<div class="content">
<p>@ben This is a <em>fantastic</em> post and you've learned some great insights that can only come with real world experience!</p>
<p>Firstly, great application of Conway's Law. I was actually trying to remember the name of that law just a couple weeks back and I couldn't recall it to save my life. Your post instantly reminded me and it's a very true observation as well.</p>
<p>Secondly, I always dislike naming a team the "legacy" team. I think it's a bit demoralizing to those on it and implies that code is somehow not as good or useful to the org. If you're using best practices, source control, tests where possible, the CI/CD, consider calling it the "core team" or "foundation team" ;)</p>
<p>Lastly, you've make a great observation that there can be good value in keeping parts of the application together to reduce the footprint of repos, builds, and tests. Shameless ColdBox MVC plug incoming. This is where the power of a modular architecture comes in. It's great to break up a large app into a modular setup where you have a nice separation of M, V, and C into bite sized chunks with clear lines of responsibility while still living in the same repo and part of the same base app. The apps we build often times have 5-10 core modules where we break out functions to keep a nice organisation and always leave open the possibility to break some off into a separate app in the future, but without the overhead of completely separate microservices. I think this is a great "middle ground" as you've showed.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52471"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52471" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 1:45 PM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Brad,</p>
<p>Thank you for the kind words. We actually joke on our team that we should be the <strong>legendary</strong> team, not the <em>legacy</em> one :D</p>
<p>I think part of our problem is that the <em>internal boundaries</em> within the monolith are not great. To you point, when you keep it really modular inside the service boundaries, it's easier for more teams to participate because they don't necessary have to understand "all the things" in order to get work done. I wouldn't say that we have a big ball of mud; but, we also don't have a really clean separation of concerns. I mean, "new code" is better than the old code; but, we do have <em>loads</em> of old code at this point.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52472"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/229/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/229-tom-k.htm" rel="author" class="author">Tom K</a>
<a href="#comments_52472" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 2:39 PM</time>
</a>
<div class="count">
10 Comments
</div>
</header>
<div class="content">
<p>Great insight Ben, thank you :)</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52473"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52473" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 3:10 PM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Tom,</p>
<p>My pleasure - I'm glad it landed well :)</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52474"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14260/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14260-stevef.htm" rel="author" class="author">stevef</a>
<a href="#comments_52474" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 6:55 PM</time>
</a>
<div class="count">
2 Comments
</div>
</header>
<div class="content">
<p>Ben, nice read however the bit I dont understand is why should a 'legacy' team meam fewer people but taking on more services/infra etc. Surely the issue here is a budget/organizational one not a boundary one<br> What happens when your team scales back up as it should for the amount of responsibility your back to the old problem.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52475"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14261/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14261-konrad-polek.htm" rel="author" class="author">Konrad Polek</a>
<a href="#comments_52475" rel="bookmark" class="date">
<time datetime="2020-12-21">Dec 21, 2020 at 11:21 PM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>I personally experienced maintenance burden that comes with microservices. I was splitting monolith when both team and product were small. We didn't solve "People" or "Technical" problem at all - just exploring a buzzword. Maybe, if I could read Your post then, we could be spared hours of pointless work.<br> Thank You, great read.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52476"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52476" rel="bookmark" class="date">
<time datetime="2020-12-22">Dec 22, 2020 at 7:17 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Konrad,</p>
<p>It's so hard! We like <em>new things</em>. I remember when I was first starting out and I learned a <em>little bit</em> about "Object Oriented Programming" (OOP). And then, without really any experience, I decided to do a new <strong>work</strong> project using a lot of OOP. Well, when all was said and down, the project timeline was like 3-times overdue! It was a pile of very confusing, very convoluted code. I had no idea what I was doing. Thankfully my boss at the time was very forgiving.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52477"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52477" rel="bookmark" class="date">
<time datetime="2020-12-22">Dec 22, 2020 at 7:19 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Steve,</p>
<p>"Why does a smaller team take on more responsibilities?" -- my team asks ourselves that question every single day 🤣 I believe that was a bit more of an internal "political" question more than anything else. The company didn't want to "burden" all the newer teams with having to be distracted by the legacy code; so, slowly, they started giving more and more of it to us in order to free up the "future" of the product.</p>
<p>It's counter-intuitive, no doubt. But, such is life.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52478"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/942/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/942-brad-wood.htm" rel="author" class="author">Brad Wood</a>
<a href="#comments_52478" rel="bookmark" class="date">
<time datetime="2020-12-22">Dec 22, 2020 at 9:47 AM</time>
</a>
<div class="count">
45 Comments
</div>
</header>
<div class="content">
<p>@Ben,</p>
<p>Your comment on your very first experience with OOPS reminds me of the <strong>Pilot System</strong> as discussed in Fred Brooks' book, <em>The Mythical Man Month</em>.</p>
<p><a href="https://en.wikipedia.org/wiki/The_Mythical_Man-Month#The_pilot_system" rel="nofollow">https://en.wikipedia.org/wiki/The_Mythical_Man-Month#The_pilot_system</a></p>
<blockquote>
<p>When designing a new kind of system, a team will design a throw-away system (whether it intends to or not). This system acts as a "pilot plan" that reveals techniques that will subsequently cause a complete redesign of the system. This second, smarter system should be the one delivered to the customer, since delivery of the pilot system would cause nothing but agony to the customer, and possibly ruin the system's reputation and maybe even the company.</p>
</blockquote>
<p>It's a good reminder that whenever you learn something new, you'll basically hate your first go at it and want to re-do it. So, if you can, create a test implementation first that you expect to throw away. I certainly hated most of my first attempt to build OOP in CF :)</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52479"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52479" rel="bookmark" class="date">
<time datetime="2020-12-22">Dec 22, 2020 at 9:57 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Brad,</p>
<p>100% true! And, the saddest thing is, even now, about 15-years later, I still basically don't know much about <em>real</em> object-oriented programming. My use of "Objects" extends mostly to using "Singletons" (though not in the strict sense) to encapsulate glorified procedural scripts and split up some "Data access" components from "Business logic" components.</p>
<p>The closest I've ever come to feeling good about my OOP is when building some utility libraries that have swappable behaviors.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52485"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14262/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14262-levin-zhang.htm" rel="author" class="author">Levin Zhang</a>
<a href="#comments_52485" rel="bookmark" class="date">
<time datetime="2020-12-29">Dec 29, 2020 at 7:45 PM</time>
</a>
<div class="count">
2 Comments
</div>
</header>
<div class="content">
<p>Hi Ben<br> I am the editor of InfoQ China(<a href="https://www.infoq.cn/" rel="nofollow">https://www.infoq.cn/</a>) which focuses on software development. We like this article and plan to translate it into Chinese. Before we translate it into Chinese and publish it on our website, I want to ask for your permission first! This translation version is provided for informational purposes only, and will not be used for any commercial purpose. In exchange, we will put the English title and link at the end of Chinese article. If our readers want to read more about this, he/she can click back to your website.<br> Thanks a lot, hope to get your help. Any more question, please let me know.</p>
<p>Thanks,<br> Levin</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52486"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52486" rel="bookmark" class="date">
<time datetime="2020-12-30">Dec 30, 2020 at 6:06 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Levin,</p>
<p>Absolutely - I'm a huge fan of InfoQ - it would be an honor to be part of your series of articles. Thank you so much :D</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52490"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14263/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14263-mike-mcgonagle.htm" rel="author" class="author">Mike McGonagle</a>
<a href="#comments_52490" rel="bookmark" class="date">
<time datetime="2021-01-01">Jan 1, 2021 at 8:26 AM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>Ben, great post. I especially like the concept of "right-sized" vs "micro". Right-sized is also imprecise, but carries a more directional connotation than "micro".</p>
<p>Mike</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52495"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52495" rel="bookmark" class="date">
<time datetime="2021-01-03">Jan 3, 2021 at 5:06 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Mike,</p>
<p>I'm glad you enjoyed the post :D</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52498"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14259/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14259-gerald-venzl.htm" rel="author" class="author">Gerald Venzl</a>
<a href="#comments_52498" rel="bookmark" class="date">
<time datetime="2021-01-05">Jan 5, 2021 at 10:58 PM</time>
</a>
<div class="count">
2 Comments
</div>
</header>
<div class="content">
<p>@Ben,</p>
<blockquote>
<p>And, given the fact that the average job in this industry is only 2-years, we don't all get the benefit of having to live with choices and see what impact they have over time.</p>
</blockquote>
<p>You are spot on there. This is one of the reasons why I think it is so important to hear from fellow developers and the lessons they have learned the hard way so that we don't keep repeating the same mistakes (unintentionally).</p>
<p>Thanks again for the great post and for taking the time to write it!</p>
<p>Cheers,</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52499"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52499" rel="bookmark" class="date">
<time datetime="2021-01-06">Jan 6, 2021 at 8:14 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Gerald,</p>
<p>My pleasure, good sir. Thanks for reading :D Learning lessons for others is <em>so hard</em>! I feel like we all have to be personally burned before we can truly understand things. The problem is, sometimes that cost is <em>really high</em>.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52505"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14265/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14265-francis.htm" rel="author" class="author">francis</a>
<a href="#comments_52505" rel="bookmark" class="date">
<time datetime="2021-01-10">Jan 10, 2021 at 9:51 PM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>So Ben what stops your team becoming the dumping ground for things-that-should-have-been-refactored and all that other crufty stuff that exists?</p>
<p>I understand the organisation is optimising for speed ("teams on the modern platform getting rid of legacy"). Would I be right in guessing that those teams don't want to be "slowed down" maintaining and refactoring, "legacy code"?</p>
<p>PS: I love the article and the concept of right-sized micro-services, I'm just wondering if the modern teams are owning their own technical debt or just putting it into your legendary -graveyard-, oops, backlog.</p>
<p>francis</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52506"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14266/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14266-antiphp.htm" rel="author" class="author">antiphp</a>
<a href="#comments_52506" rel="bookmark" class="date">
<time datetime="2021-01-11">Jan 11, 2021 at 7:42 AM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>Thanks for sharing this, it's a good read.</p>
<p>Where I would not agree is where you talk about right-sized services. A microservice should be - IMHO - very encapsulating but also small. If not it's just a normal application or what you would call a right-sized service, no?</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52507"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14262/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14262-levin-zhang.htm" rel="author" class="author">Levin Zhang</a>
<a href="#comments_52507" rel="bookmark" class="date">
<time datetime="2021-01-12">Jan 12, 2021 at 5:36 AM</time>
</a>
<div class="count">
2 Comments
</div>
</header>
<div class="content">
<p>@Ben,</p>
<p>This is the Chinese link of this article: <a href="https://www.infoq.cn/article/o6kcqCSGBTmeTbOP4wG1" rel="nofollow">https://www.infoq.cn/article/o6kcqCSGBTmeTbOP4wG1</a><br> Thank you again for your help.</p>
<p>Levin</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52508"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52508" rel="bookmark" class="date">
<time datetime="2021-01-12">Jan 12, 2021 at 6:13 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Levin,</p>
<p>So cool! This is very exciting for me :D</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52509"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52509" rel="bookmark" class="date">
<time datetime="2021-01-12">Jan 12, 2021 at 6:23 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Antiphp,</p>
<p>It's an interesting question. And, I think perhaps this gets to the heart of the "intent" vs. the "implementation" of a microservice. For me, one the ways I think about a microservice is from a "Build vs. Buy" point-of-view. Meaning, every team has a service that they could <strong>build internally</strong>; or, one that they could choose to <strong>buy (as a third-party service)</strong>. The decision usually (should) come down to whether or not the given service is a <em>differentiator</em> for the business itself.</p>
<p>Take, for example, managing WebSocket connections. At work, we use Pusher as our 3rd-party WebSocket provider since we're not really in the "business of managing WebSockets". The Pusher API is super simple (at least, the way we use it). It's basically one API end-point that allows us to push JSON payloads that then get pushed-out to all subscribed clients / channels.</p>
<p>Now, you could look at Pusher as a "microservice" from the consumer's point of view. It provides a very cohesive set of functionality with a very minimal API. You could <em>imagine</em> that if we were to build this type of service internally, it would be its own service.</p>
<p>However, under the hood, it's probably a <em>massive infrastructure</em> with loads of dependencies and complexities and scaling issues and data storage concerns and whatnot. And, it probably has its own set of microservices that we - as consumers - never have to know about.</p>
<p>Of course, did Pusher always operate at the same level of complexity? Probably not. I would hazard a guess that in the early days, when they had very few customers, they only had <em>enough complexity</em> to get the job done. Then, over time, as they needed to get more complex, and handle more scale, the internal implementation started to evolve.</p>
<p>However, from my (the consumer) point-of-view, nothing ever changed. I've still just been hitting that one API to push JSON to users. To me, the Pusher vendor has always been a "service" that I call because it manages the functionality that I need.</p>
<p>And, this is how I think about "right sized" services. There really is no "monolith" or "microservice"; there is simple a service that does "A Thing"; and, hopefully does it well.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52510"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52510" rel="bookmark" class="date">
<time datetime="2021-01-12">Jan 12, 2021 at 6:25 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Francis,</p>
<p>What stops my team from becoming the dumping ground for old services?</p>
<p>Unfortunately, very little it seems 😭 . We've had to take over services that we know very little about. I think we even own one or two Golang services and literally no one on my team is proficient in Golang.</p>
<p>We've been able to push back on <em>some attempts</em> by basically throwing a tantrum that another team has the audacity to foist responsibility onto us. But, it's been hit-and-miss as to how much that works.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52517"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52517" rel="bookmark" class="date">
<time datetime="2021-01-13">Jan 13, 2021 at 8:12 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@All,</p>
<p>Me and some people just had a more in-depth discussion about this topic on the Working Code podcast:</p>
<p><a href="/blog/3963-working-code-podcast-episode-005-monoliths-vs-microservices.htm" rel="nofollow" title="Read article: Working Code Podcast - Episode 005: Monoliths vs. Microservices">www.bennadel.com/blog/3963-working-code-podcast-episode-005-monoliths-vs-microservices.htm</a></p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52564"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14274/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14274-dmitry.htm" rel="author" class="author">Dmitry</a>
<a href="#comments_52564" rel="bookmark" class="date">
<time datetime="2021-02-03">Feb 3, 2021 at 9:02 PM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>@Ben,</p>
<p>hi and thank you for your article! Just to let you know: there is a Russian translation of this article as well. You can find it here: <a href="https://habr.com/ru/company/flant/blog/540406/" rel="nofollow">https://habr.com/ru/company/flant/blog/540406/</a></p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52681"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52681" rel="bookmark" class="date">
<time datetime="2021-03-23">Mar 23, 2021 at 5:08 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Dmitry,</p>
<p>Oh, that's awesome! Thank you :D</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_52692"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_52692" rel="bookmark" class="date">
<time datetime="2021-04-03">Apr 3, 2021 at 7:33 AM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@All,</p>
<p>If anyone is curious, I just merged another Go service back into our ColdFusion monolith:</p>
<p><a href="/blog/4020-a-peek-into-the-interstitial-cost-of-microservices.htm" rel="nofollow" title="Read article: A Peek Into The Interstitial Cost Of Microservices">www.bennadel.com/blog/4020-a-peek-into-the-interstitial-cost-of-microservices.htm</a></p>
<p>This recent subsumption was very interesting (to me) because it was very small in scope: two Go routes which map directly onto two ColdFusion routes. As such, it was really easy to isolate and see the performance impact.</p>
<p>That said, I now wonder if the big change in performance relates to the overhead of the <code>CFHTTP</code> call that Brad Wood was recently talking about on the Modernize or Die podcast.</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_54100"
class="comment ">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/14609/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/14609-pravin-yadav.htm" rel="author" class="author">Pravin Yadav</a>
<a href="#comments_54100" rel="bookmark" class="date">
<time datetime="2023-01-16">Jan 16, 2023 at 2:34 PM</time>
</a>
<div class="count">
1 Comments
</div>
</header>
<div class="content">
<p>Spot on. I am quite late here but this blog summarizes a lot of resources I read on the internet today on this topic. Explained all the stuff in great detail. The GIF of Thanos is quite impressive and relevant.</p>
<p>Thanks, Ben Nadel</p>
</div>
</article>
<!-- END: Comment. -->
<!-- BEGIN: Comment. -->
<article
id="comments_54101"
class="comment ben">
<header class="meta">
<img
src="https://bennadel-cdn.com/dynamic-images/avatars/1/2.jpg"
alt=""
loading="lazy"
class="avatar"
/>
<a href="/members/1-ben-nadel.htm" rel="author" class="author">Ben Nadel</a>
<a href="#comments_54101" rel="bookmark" class="date">
<time datetime="2023-01-16">Jan 16, 2023 at 2:37 PM</time>
</a>
<div class="count">
16,267 Comments
</div>
</header>
<div class="content">
<p>@Pravin,</p>
<p>It's such a strange time. I see a lot of people <em>walking back</em> the idea of microservices. Even most of the big advocates are pushing that you should stick with a "modular monolith" until you <em>really really need</em> microservices.</p>
<p>But, at the same time, there's a cohort of people who, I feel, are doubling-down on the microservices <em>philosophy</em>, pushing more logic into Edge functions and breaking UIs up into "islands". It will be curious to see what the next few years look like.</p>
</div>
</article>
<!-- END: Comment. -->
<div class="comments-disabled">
<div class="reason">
<span>Oh my chickens, this post is old!</span>
</div>
<div class="follow-up">
<a href="https://www.linkedin.com/in/BenNadel" title="Ben Nadel's LinkedIn Account">
<em>Hit me up on LinkedIn</em> if you want to discuss it further.
</a>
</div>
</div>
</section>
<!-- END: Comments. -->
</article>
</main>
</div>
<div class="l-site-body__aside">
<figure class="m-love">
<blockquote class="m-love__copy">
I believe in <strong>love</strong>. I believe in <strong>compassion</strong>. I believe in <strong>human rights</strong>. I believe that we can afford to give more of these gifts to the world around us because it <em>costs us nothing</em> to be decent and kind and understanding. And, I want you to know that when you land on this site, <strong>you are accepted for who you are</strong>, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
</blockquote>
<figcaption class="m-love__caption">
&mdash;
<a
href="/about/about-ben-nadel.htm"
title="Learn more about Ben Nadel, Co-founder of InVision App, Inc."
class="m-love__author"
>Ben Nadel</a>
</figcaption>
</figure>
<figure class="m-xByte">
<figcaption>
Managed <a href="https://www.xbytecloud.com/coldfusion/hosting/coldfusion-cloud-hosting?source=bennadel.com" rel="sponsored" target="_blank">ColdFusion hosting</a> services provided by:
</figcaption>
<a href="https://www.xbytecloud.com/?source=bennadel.com" rel="sponsored" target="_blank">
<img
src="https://bennadel-cdn.com/images/global/xbyte-cloud.png"
alt="xByte Cloud Logo"
loading="lazy"
/>
</a>
</figure>
</div>
<br clear="both" />
</div>
<footer class="l-site-footer">
<small>
<span class="copyright">
Ben Nadel &copy; 2026.
All content is the property of Ben Nadel.
For use of code <a href="/blog/license.htm">see license</a>.
</span>
<a href="#primary-navigation" class="back-to-top">
Back to Top
</a>
</small>
<section class="epilogue">
<div class="mini-resume">
<div class="avatar">
<img
src="https://bennadel-cdn.com/images/global/ben-nadel-avatar-v2.jpg"
title="Ben Nadel is co-founder of InVision App, Inc. and a passionate web-applications developer."
alt="Ben Nadel's avatar."
loading="lazy"
/>
</div>
<div class="title">
About Ben Nadel
</div>
<div class="bio">
I am the co-founder and a principal engineer at InVision App, Inc &mdash; the world's leading online whiteboard and productivity platform powering the future of work. I also rock out in JavaScript and ColdFusion 24x7 and I dream about chained Promises resolving asynchronously.
</div>
<div class="links">
<div class="links__title">
Social Links
</div>
<ul class="links__items">
<li class="links__item">
<a
href="https://github.com/BenNadel"
title="Follow Ben Nadel on GitHub."
class="links__link">
GitHub
</a>
</li>
<li class="links__item">
<a
href="https://www.linkedin.com/in/BenNadel"
title="Follow Ben Nadel on LinkedIn."
class="links__link">
LinkedIn
</a>
</li>
<li class="links__item">
<a
href="https://www.facebook.com/BenNadel"
title="Follow Ben Nadel on Facebook."
class="links__link">
Facebook
</a>
</li>
</ul>
</div>
<div class="links">
<div class="links__title">
Site Links
</div>
<ul class="links__items">
<li class="links__item">
<a
href="/"
title="Ben Nadel's Blog on modern web application development"
class="links__link">
Home
</a>
</li>
<li class="links__item">
<a
href="/activity/recent-activity.htm"
title="Recent activity on Ben Nadel's blog."
class="links__link">
Activity
</a>
</li>
<li class="links__item">
<a
href="/blog/tags/35-podcast-blog-entries.htm"
title="Ben Nadel's podcast, Working Code."
class="links__link">
Podcast
</a>
</li>
<li class="links__item">
<a
href="/projects/overview.htm"
title="Ben Nadel's open source web development projects."
class="links__link">
Projects
</a>
</li>
<li class="links__item">
<a
href="/about/about-ben-nadel.htm"
title="Learn more about Ben Nadel, Co-founder of InVision App, Inc."
class="links__link">
About Me
</a>
</li>
<li class="links__item">
<a
href="/contact/contact-ben-nadel.htm"
title="Contact Ben Nadel with questions about web development."
class="links__link">
Contact Me
</a>
</li>
<li class="links__item">
<a
href="/people/who-rock-my-world.htm"
title="The amazing people who rock Ben Nadel's world on the daily!"
class="links__link">
People
</a>
</li>
<li class="links__item">
<a
href="/utils/"
data-turbo="false"
title="Small utilities that I created for myself to aide in web development."
class="links__link">
Utilities
</a>
</li>
<li class="links__item">
<a
href="/invision/co-founder.htm"
title="Ben Nadel is the co-founder of InVision App, Inc."
class="links__link">
InVision
</a>
</li>
<li class="links__item">
<a
href="/rss"
title="Subscribe to the BenNadel.com RSS feed for daily updates."
class="links__link">
RSS Feed
</a>
</li>
</ul>
</div>
<div class="links">
<div class="links__title">
My Other Projects
</div>
<ul class="links__items">
<li class="links__item">
<a
href="https://www.bigsexypoems.com/"
title="An online poetry authoring application that helps you unlock your inner BigSexy with syllable counts, rhymes schemes, and synonyms!"
class="links__link">
Big Sexy Poems
</a>
</li>
<li class="links__item">
<a
href="https://www.digdeepfitness.com/"
title="Fitness tracking made simple."
class="links__link">
Dig Deep Fitness
</a>
</li>
<li class="links__item">
<a
href="https://www.incident-commander.com/"
title="A realtime communications tool to aide in incident management and remediation."
class="links__link">
Incident Commander
</a>
</li>
<li class="links__item">
<a
href="/books/feature-flags/overview.htm"
title="Feature Flags: Transform Your Product Development Workflow (a book)"
class="links__link">
Feature Flags (Book)
</a>
</li>
</ul>
</div>
</div>
</section>
</footer>
<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" nonce="E6yu4//sBBvJEXLdNHUXgg==" data-cf-beacon='{"version":"2024.11.0","token":"5345bed8fbe4482e8ac86ce8037e3e9c","r":1,"spa":2}' crossorigin="anonymous"></script>
</body>
</html>