<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>David Boike&#39;s Blog</title>
  
  <link href="/feed.xml" rel="self"/>
  
  <link href="https://www.davidboike.dev/"/>
  <updated>2025-04-10T20:53:46.350Z</updated>
  <id>https://www.davidboike.dev/</id>
  
  <author>
    <name>David Boike</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Using a SmartThings sensor in &quot;garage door&quot; mode with Home Assistant</title>
    <link href="https://www.davidboike.dev/2020/12/smartthings-sensor-garage-door-mode-with-home-assistant/"/>
    <id>https://www.davidboike.dev/2020/12/smartthings-sensor-garage-door-mode-with-home-assistant/</id>
    <updated>2020-12-23T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>I started my home automation journey with SmartThings but it didn’t take long to feel constrained by the things it couldn’t do, so I switched to <a href="https://www.home-assistant.io/" target="_blank" rel="noopener">Home Assistant</a>.</p><p>One thing SmartThings did well was their <a href="https://www.samsung.com/us/smart-home/smartthings/sensors/samsung-smartthings-multipurpose-sensor-2018-gp-u999sjvlaaa/" target="_blank" rel="noopener">door/window/multipurpose sensor</a>. (I purchased the few I have <a href="https://www.amazon.com/gp/product/B07F956F3B/" target="_blank" rel="noopener">from Amazon</a> but while I’m writing this they’re unavailable there—not sure why.)</p><p>These sensors are a standard magnetic reed switch (the little part is just a magnet) paired with a temperature sensor and accelerometer. One thing you could do with the SmartThings software was chuck the magnet part and use the sensor in “garage door” mode. You affix the sensor to the inside of the garage door and then SmartThings interprets the tilting of the sensor from vertical to horizontal as the door raises as open/closed instead of the magnetic switch.</p><p>In this post I’ll show how to get the same thing with Home Assistant to see the status of a garage door, mailbox, etc.</p><a id="more"></a><h2 id="The-SmartThings-sensor"><a href="#The-SmartThings-sensor" class="headerlink" title="The SmartThings sensor"></a>The SmartThings sensor</h2><p>When you pair one of these sensors with Home Assistant you get four associated entities:</p><ul><li>A <code>binary_sensor</code> for the accelerometer</li><li>A power sensor for the battery level</li><li>A temperature sensor</li><li>A <code>binary_sensor</code> named <code>ias_zone</code></li></ul><p>I have no idea what <code>ias_zone</code> means but what it really means the magnetic switch. So when you’re using the sensor without the magnet on something like a garage door, this is <em>always</em> going to be <code>open</code>. So the first thing I would do after pairing this device is to disable the <code>ias_zone</code> entity, since it’s useless.</p><p>Nowhere in the state or attributes for these entities is anything related to the position or orientation of the sensor:</p><p><img src="/images/2020/smartthings-sensor/entities.png" alt="Entity states and attributes"></p><p>However, this information is conveyed through events.</p><h2 id="zha-event"><a href="#zha-event" class="headerlink" title="zha_event"></a>zha_event</h2><p>The sensor will broadcast x-axis, y-axis, and z-axis values as separate events. I don’t know exactly how these values are defined, but it really doesn’t matter. All we need to do is install the device in the desired location and then measure.</p><p>First, let’s see the events in action:</p><ol><li>Go to <strong>Developer Tools</strong> &gt; <strong>Events</strong>.</li><li>Under <strong>Listen to events</strong> enter <code>zha_event</code> and click <strong>Start Listening</strong>.</li><li>Raise or lower the garage door.</li><li>Watch a few events come in.</li><li>Click <strong>Stop Listening</strong>.</li></ol><p>Here’s an example event from my garage door:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"event_type"</span>: <span class="string">"zha_event"</span>,</span><br><span class="line">    <span class="attr">"data"</span>: &#123;</span><br><span class="line">        <span class="attr">"device_ieee"</span>: <span class="string">"28:6d:97:00:01:0a:a5:7a"</span>,</span><br><span class="line">        <span class="attr">"unique_id"</span>: <span class="string">"28:6d:97:00:01:0a:a5:7a:1:0xfc02"</span>,</span><br><span class="line">        <span class="attr">"device_id"</span>: <span class="string">"cd84799cc2948d8febdd5f87d12245e7"</span>,</span><br><span class="line">        <span class="attr">"endpoint_id"</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">"cluster_id"</span>: <span class="number">64514</span>,</span><br><span class="line">        <span class="attr">"command"</span>: <span class="string">"attribute_updated"</span>,</span><br><span class="line">        <span class="attr">"args"</span>: &#123;</span><br><span class="line">            <span class="attr">"attribute_id"</span>: <span class="number">18</span>,</span><br><span class="line">            <span class="attr">"attribute_name"</span>: <span class="string">"x_axis"</span>,</span><br><span class="line">            <span class="attr">"value"</span>: <span class="number">50</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"origin"</span>: <span class="string">"LOCAL"</span>,</span><br><span class="line">    <span class="attr">"time_fired"</span>: <span class="string">"2020-12-23T03:33:56.688618+00:00"</span>,</span><br><span class="line">    <span class="attr">"context"</span>: &#123;</span><br><span class="line">        <span class="attr">"id"</span>: <span class="string">"91f5d475b1a27f3738ac474c87e70b2b"</span>,</span><br><span class="line">        <span class="attr">"parent_id"</span>: <span class="literal">null</span>,</span><br><span class="line">        <span class="attr">"user_id"</span>: <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The important parts are:</p><ul><li>One of the ids, either <code>device_ieee</code>, <code>unique_id</code>, or <code>device_id</code>. I prefer <code>device_ieee</code> because the same value is easily visible on the device info page.</li><li>The <code>data.args.attribute_name</code>, in this case <code>x_axis</code>.</li><li>The <code>data.args.value</code> which, in this case, is the value of the x coordinate.</li></ul><h2 id="Measuring-the-door"><a href="#Measuring-the-door" class="headerlink" title="Measuring the door"></a>Measuring the door</h2><p>We want to measure the extremes of the X/Y/Z values when the door is all the way up and all the way down. To do that we can create helpers.</p><ol><li>Go to <strong>Configuration</strong> &gt; <strong>Helpers</strong>.</li><li>Click <strong>Add Helper</strong>.</li><li>Click <strong>Number</strong>.</li><li>Enter values:<ul><li>Name = <code>Garage X</code></li><li>Ignore icon, this is only temporary</li><li>Minimum value = <code>-1200</code></li><li>Maximum value = <code>1200</code></li></ul></li><li>Click <strong>Create</strong>.</li><li>Create two more helpers for <code>Garage Y</code> and <code>Garage Z</code>.</li></ol><p>If you’re using the default auto-generated dashboard they’ll look like this:</p><p style="text-align:center;"><img src="/images/2020/smartthings-sensor/new-helpers.png" width="305" /></p><p>Now to fill them with data, I created an automation:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">alias:</span> <span class="string">Set</span> <span class="string">Garage</span> <span class="string">XYZ</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">''</span></span><br><span class="line"><span class="attr">trigger:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">event</span></span><br><span class="line">    <span class="attr">event_type:</span> <span class="string">zha_event</span></span><br><span class="line">    <span class="attr">event_data:</span></span><br><span class="line">      <span class="attr">device_ieee:</span> <span class="string">'28:6d:97:00:01:0a:a5:7a'</span></span><br><span class="line"><span class="attr">condition:</span> <span class="string">[]</span></span><br><span class="line"><span class="attr">action:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">choose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">            <span class="attr">value_template:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.attribute_name == "x_axis" &#125;&#125;</span>'</span></span><br><span class="line">        <span class="attr">sequence:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">input_number.set_value</span></span><br><span class="line">            <span class="attr">data:</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.value &#125;&#125;</span>'</span></span><br><span class="line">            <span class="attr">entity_id:</span> <span class="string">input_number.garage_x</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">            <span class="attr">value_template:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.attribute_name == "y_axis" &#125;&#125;</span>'</span></span><br><span class="line">        <span class="attr">sequence:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">input_number.set_value</span></span><br><span class="line">            <span class="attr">data:</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.value &#125;&#125;</span>'</span></span><br><span class="line">            <span class="attr">entity_id:</span> <span class="string">input_number.garage_y</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">            <span class="attr">value_template:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.attribute_name == "z_axis" &#125;&#125;</span>'</span></span><br><span class="line">        <span class="attr">sequence:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">input_number.set_value</span></span><br><span class="line">            <span class="attr">data:</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.value &#125;&#125;</span>'</span></span><br><span class="line">            <span class="attr">entity_id:</span> <span class="string">input_number.garage_z</span></span><br><span class="line">    <span class="attr">default:</span> <span class="string">[]</span></span><br><span class="line"><span class="attr">mode:</span> <span class="string">queued</span></span><br><span class="line"><span class="attr">max:</span> <span class="number">20</span></span><br></pre></td></tr></table></figure><p>Some notes:</p><ul><li>In the trigger section, the <code>device_ieee</code> needs to match the value for your sensor so that we filter out other sensors.</li><li>The actions section contains 3 conditions to match either <code>x_axis</code>, <code>y_axis</code>, or <code>z_axis</code> and then to set the appropriate helper value. Make sure the <code>entity_id</code> for each call to the <code>input_number.set_value</code> service matches the ones you created.</li><li>I set the mode to <code>queued</code> and the max to a relatively high value of <code>20</code>. I don’t know how quickly these will get processed but I know that they come in at least 3 at a time, one event for each access. I don’t really want to discard the X coordinate event because the Z coordinate bumped it out of the queue.</li></ul><p>Once this automation was in place, I raised and lowered the garage door. Here are two screenshots, along with the value of each helper that I added in red:</p><p><img src="/images/2020/smartthings-sensor/open-closed-values.png" alt="Open &amp; Closed XYZ Values"></p><h2 id="What-does-“open”-mean"><a href="#What-does-“open”-mean" class="headerlink" title="What does “open” mean?"></a>What does “open” mean?</h2><p>Now that we’ve got numbers, we need to figure out how to define open and closed. The numbers won’t be exact each time, so we need one of the axes to have some daylight between them so that the transition will be completely obvious.</p><p>In this case, X is completely out as the values are only a couple dozen apart. Either Y or Z are a good candidate though, as both are about 1000 apart between the fully-open and fully-closed states.</p><p>You can’t just assume though. Depending on where you place the sensor, it could be completely different. The garage door sensor goes from being vertical when closed to horizontal (pointing down) when open. On the mailbox door, however, I affixed the sensor pointing “up” when the door was open, and when you close the mailbox the sensor is vertical but upside-down. The result is that the axis you choose and the threshold for open/closed will be different in every situation.</p><p>In the garage door case, I decided to go with the Z axis, which is partially a coin flip between Y/Z, but since garage doors go “up” and “down”, Z-axis seems to make sense to me.</p><p>As for the value, if for some reason the garage door is halfway open, I still consider that <em>open</em>. So rather than take the halfway point of ~500 I instead chose to define open as being any value greater than 200.</p><h2 id="Open-helper"><a href="#Open-helper" class="headerlink" title="Open helper"></a>Open helper</h2><p>Armed with this information, I can create a new helper of type <strong>Toggle</strong>, and call it <strong>Garage Door Open</strong>. I recommend the icon <code>mdi:garage</code>.</p><p>I can disable the automation we just used, or delete it, along with all the numeric helpers.</p><p>This new automation will set the state of the Garage Door Open helper:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">alias:</span> <span class="string">Set</span> <span class="string">Garage</span> <span class="string">State</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">''</span></span><br><span class="line"><span class="attr">trigger:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">event</span></span><br><span class="line">    <span class="attr">event_type:</span> <span class="string">zha_event</span></span><br><span class="line">    <span class="attr">event_data:</span></span><br><span class="line">      <span class="attr">device_ieee:</span> <span class="string">'28:6d:97:00:01:0a:a5:7a'</span></span><br><span class="line"><span class="attr">condition:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">    <span class="attr">value_template:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.attribute_name == "z_axis" &#125;&#125;</span>'</span></span><br><span class="line"><span class="attr">action:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">choose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">            <span class="attr">value_template:</span> <span class="string">'<span class="template-variable">&#123;&#123; trigger.event.data.args.value &gt; 200 &#125;&#125;</span>'</span></span><br><span class="line">        <span class="attr">sequence:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">input_boolean.turn_on</span></span><br><span class="line">            <span class="attr">data:</span> <span class="string">&#123;&#125;</span></span><br><span class="line">            <span class="attr">entity_id:</span> <span class="string">input_boolean.garage_door_open</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">input_boolean.turn_off</span></span><br><span class="line">        <span class="attr">data:</span> <span class="string">&#123;&#125;</span></span><br><span class="line">        <span class="attr">entity_id:</span> <span class="string">input_boolean.garage_door_open</span></span><br><span class="line"><span class="attr">mode:</span> <span class="string">queued</span></span><br><span class="line"><span class="attr">max:</span> <span class="number">20</span></span><br></pre></td></tr></table></figure><p>Again, note that:</p><ul><li>The <code>device_ieee</code> must match your device.</li><li>The <code>entity_id</code> (used twice) must match the Toggle helper.</li><li>We’re still filtering on one axis’s events, so the queue makes sure we don’t lose important events.</li></ul><p>Now, you can raise and lower the garage door and watch the value of the helper flip back and forth. And now that there’s a helper, we can use the value in other automations.</p><p>For instance, I get a notification when my mailbox is opened, but <em>ONLY</em> when the front door is locked and the garage door is closed. Otherwise the person opening the mailbox is not the mail carrier, it’s a member of the family.</p><p>I also have an automation that announces there is someone at the front door, but only if the front door and garage door are closed.</p><h2 id="A-finishing-touch"><a href="#A-finishing-touch" class="headerlink" title="A finishing touch"></a>A finishing touch</h2><p>One problem is that because the helper is an <code>input_boolean</code> it’s always going to be editable on any dashboard you include it on, because anything <code>input_*</code> is designed in Home Assistant to be editable, period, while sensors are read-only.</p><p>If you wanted to display the value on a dashboard as read-only, you would need to create a <a href="https://www.home-assistant.io/integrations/binary_sensor.template" target="_blank" rel="noopener">template binary sensor</a>, which basically means a sensor that gets its data from another entity according to a template. You could also keep one of the XYZ axis helpers from before and define a template binary sensor based on the numeric value.</p><p>However, I have found that it can be useful to be able to switch the helper to the incorrect state for a time, so that I can test what an automation would do with the garage door open, without actually needing to open the garage.</p><h2 id="Wait-I-live-in-Minnesota"><a href="#Wait-I-live-in-Minnesota" class="headerlink" title="Wait, I live in Minnesota"></a>Wait, I live in Minnesota</h2><p>I mentioned that I use one of these sensors in the mailbox, so that I can tell when the mail has been delivered. However, I live in Minnesota and it’s just a few days before Christmas.</p><p>That means it’s cold.</p><p>The mail call automation worked for just a few abnormally warm days, but then the temperature dipped and the automation stopped working. The Energizer CR2450 Lithium batteries I’ve got (and all lithium batteries generally) are not a fan of the cold weather. <a href="https://www.panasonic-batteries.com/en/specialty/lithium-coin/coin-lithium-cr2450" target="_blank" rel="noopener">These Panasonic batteries</a> boast an operating range of -30°C to +60°C (or -22ºF to +140ºF) but I have not tried them yet.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>This post showed how to capture the X/Y/Z axis values of a SmartThings door/window multipurpose sensor and interpret those values as open or closed for a garage door, mailbox door, or other application where the magentic reed switch is not viable.</p><p>It’s possible the same procedure could be used on other types of sensors that include an accelerometer as well.</p><p>I have not tested it yet, but I wonder if this would be useful for applications like:</p><ul><li>French patio doors, or casement windows that crank out. Unlike a garage/mailbox door, the orientation of the sensor relative to gravity never changes, so I don’t know if the reported coordinates would vary enough to trigger on.</li><li>Sliding porch doors, where the sensor would only slide left or right, but never really change orientation.</li></ul><p>If you try any of those, let me know!</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I started my home automation journey with SmartThings but it didn’t take long to feel constrained by the things it couldn’t do, so I switched to &lt;a href=&quot;https://www.home-assistant.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Home Assistant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing SmartThings did well was their &lt;a href=&quot;https://www.samsung.com/us/smart-home/smartthings/sensors/samsung-smartthings-multipurpose-sensor-2018-gp-u999sjvlaaa/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;door/window/multipurpose sensor&lt;/a&gt;. (I purchased the few I have &lt;a href=&quot;https://www.amazon.com/gp/product/B07F956F3B/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;from Amazon&lt;/a&gt; but while I’m writing this they’re unavailable there—not sure why.)&lt;/p&gt;
&lt;p&gt;These sensors are a standard magnetic reed switch (the little part is just a magnet) paired with a temperature sensor and accelerometer. One thing you could do with the SmartThings software was chuck the magnet part and use the sensor in “garage door” mode. You affix the sensor to the inside of the garage door and then SmartThings interprets the tilting of the sensor from vertical to horizontal as the door raises as open/closed instead of the magnetic switch.&lt;/p&gt;
&lt;p&gt;In this post I’ll show how to get the same thing with Home Assistant to see the status of a garage door, mailbox, etc.&lt;/p&gt;
    
    </summary>
    
      <category term="Home Automation" scheme="https://www.davidboike.dev/categories/Home-Automation/"/>
    
    
      <category term="Home Assistant" scheme="https://www.davidboike.dev/tags/Home-Assistant/"/>
    
  </entry>
  
  <entry>
    <title>Distance learning alerts with Google Calendar, Alexa, and Home Assistant</title>
    <link href="https://www.davidboike.dev/2020/12/distance-learning-alerts-with-google-calendar-alexa-home-assistant/"/>
    <id>https://www.davidboike.dev/2020/12/distance-learning-alerts-with-google-calendar-alexa-home-assistant/</id>
    <updated>2020-12-03T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>My kids have been distance learning since March. When Fall came, we picked the Distance Choice option in our local school district. We wanted to support teachers in any way we could, we both have jobs that enable us to work from home, and we figured if cases continued to get worse then all students would be distance learning eventually and then our kids wouldn’t have to make that transition mid-year.</p><p>For what it’s worth, we were right. After Thanksgiving, everyone in our district is distance learning too.</p><p>My oldest is in 3rd grade, my youngest is in kindergarten. The district is pretty on top of things. Both kids have school-issued iPads, have sync lessons over <a href="https://zoom.us/" target="_blank" rel="noopener">Zoom</a> primarily on Mondays and Wednesdays, and get and submit assignments through <a href="https://web.seesaw.me/" target="_blank" rel="noopener">SeeSaw</a> on async days.</p><p>Two of the biggest problems at the start were schedule and Zoom links. Both kids have different schedules of when they need to be on Zoom, and needed an easy way to know which Zoom link they needed to join. I also didn’t want to be typing Zoom meeting IDs into an iPad keyboard that I was reading off my computer or phone. I needed a way to be able to manage schedule and links from my computer, make them available to each kid, and then make sure their butt was in the seat at the appropriate time.</p><p>I have now brought all this together by combining Google Calendar and Alexa with the power of Home Assistant.</p><a id="more"></a><h2 id="Google-Calendar"><a href="#Google-Calendar" class="headerlink" title="Google Calendar"></a>Google Calendar</h2><p>The first thing I did, even before I had Home Assistant, was to set up a Google Calendar for each kid.</p><p><img src="/images/2020/distance-learning-schedule.png" alt="Kid schedule #1"></p><p>I set up a new Google Calendar for each kid on my personal Google account, and that’s where I manage their schedule. The appropriate class Zoom URL goes in the Location. In some cases such as “Specialists” there are different links for Music, Tech, P.E., etc. which are in a Google Doc provided by the school. In those cases I just use the link to the Google Doc. Luckily, my daughter in 3rd grade is old enough to read and figure out some of those things without my help. With my son in Kindergarten I have to be a little more explicit.</p><p>Each kid has a Google sign-in through the school, so I manage the calendar and then I share it with them.</p><p>There was a trick to that, as at first the calendar didn’t want to show up on their device. Google has a semi-secret link for <a href="https://calendar.google.com/calendar/u/0/syncselect" target="_blank" rel="noopener">Sync Settings</a> which I had to visit on each of their iPads before the calendar would sync to the iOS Calendar app.</p><p>With the calendar, whenever I get communication from the school that affects the schedule, I can make changes to the kid’s Google calendar and the changes are automatically synced to them. So as long as they know to be on their tablet, they can figure out where to go.</p><p>The challenge is making sure they’re in front of the tablet.</p><h2 id="Alexa"><a href="#Alexa" class="headerlink" title="Alexa"></a>Alexa</h2><p>Each kid has an Alexa device near them. One is a 3rd-generation Echo Dot, the other is a Sonos One with Alexa enabled.</p><p>The natural thing to try was Alexa reminders. If the teacher says “Be back on Zoom after lunch at 1:00” then my kid would just say “Alexa, remind me to be back on Zoom at 1:00.”</p><p>That worked for my eldest. For the kindergartener, it was a shit-show. He doesn’t understand time yet. I would hear him outside my office door in the afternoon saying “Alexa, remind me to be back on Zoom at 9:50.” Except it was already <em>after noon</em> so instead of being reminded to be back on Zoom with his teacher at 1:50pm, my wife and I would get a reminder while watching TV long after the children had been put to bed.</p><p>So I tried scheduling reminders, but make no mistake, <strong>this was a giant pain in the butt</strong>. The only one that was really valuable was “Sign on for morning meeting” because it happened every weekday. The rest were impossible to keep synced with the calendar. Did I mention that there’s still the concept of a “Day 1” through “Day 5” in the school schedule that relates to when the kids have class with different specialists? You can’t set this up with Alexa when every recurring reminder takes about 150 taps in the Alexa app.</p><p><em>Enter Home Assistant</em>.</p><h2 id="Home-Assistant"><a href="#Home-Assistant" class="headerlink" title="Home Assistant"></a>Home Assistant</h2><p>The reason I decided to get Raspberry Pi running <a href="https://www.home-assistant.io/" target="_blank" rel="noopener">Home Assistant</a> wasn’t for this problem. I was (actually still am) running SmartThings, but I’ve started to get cranky with the lack of flexibility that SmartThings allows. That’s really a topic for another post.</p><p>After doing the <a href="https://www.home-assistant.io/getting-started/" target="_blank" rel="noopener">basic HomeAssistant setup</a>, this is what I had to set up.</p><p>First, I installed the File Editor from the Home Assistant Add-On Store. This is useful for editing Home Assisstant’s <code>configuration.yaml</code> file.</p><ol><li>In the main menu, go to <strong>Supervisor</strong> &gt; <strong>Add-on Store</strong>.</li><li>Under <strong>Official add-ons</strong> find and click on <strong>File editor</strong> and click <strong>INSTALL</strong>. When install is done, it goes to teh details page for the add-on.</li><li>Click <strong>START</strong>.</li><li>Now you have <strong>File editor</strong> in the main menu.</li></ol><p>Next, I set up the <a href="https://www.home-assistant.io/integrations/calendar.google/" target="_blank" rel="noopener">Google Calendar Event</a> which is bundled with Home Assistant, but just has to have configuration added. As part of that process, a <code>google_calendars.yaml</code> file was created, which includes details on all of my calendars, including one for each kid. The result is that I now have a binary sensor (something that is either on or off) for each calendar.</p><p>Next, I needed to be able to have Home Assistant command a specific Alexa device to say a specific phrase on command. This…isn’t <em>really</em> supported, but it <strong>is</strong> possible.</p><h2 id="HACS-amp-Alexa"><a href="#HACS-amp-Alexa" class="headerlink" title="HACS &amp; Alexa"></a>HACS &amp; Alexa</h2><p>For all the crazy things that aren’t direclty supported by Home Assistant, there’s HACS, which is short for <a href="https://hacs.xyz/" target="_blank" rel="noopener">Home Assistant Community Store</a>. To <a href="https://hacs.xyz/docs/installation/prerequisites" target="_blank" rel="noopener">install HACS</a> you need a GitHub account (check), and access to the Home Assistant filesystem. For the latter, I added the <a href="https://github.com/home-assistant/addons/tree/master/samba" target="_blank" rel="noopener">Samba share</a> add-on, the same way I added the File editor. That allowed me to open the file system from my computer, and would be another way to edit the <code>configuration.yaml</code> file as well.</p><p>After installing HACS, a HACS menu item appears in the Home Assistant main menu. From <strong>HACS</strong> &gt; <strong>Integrations</strong>, I installed the <a href="https://github.com/custom-components/alexa_media_player" target="_blank" rel="noopener">Alexa Media Player</a> component according to the <a href="https://github.com/custom-components/alexa_media_player/wiki/Configuration" target="_blank" rel="noopener">installation instructions</a>. This is not official so Amazon could cut off access at any time…hopefully not until this distance learning fiasco is long over.</p><p>I believe Alexa also needs a TTS (text-to-speech) service registered in order to translate your words to speech. At any rate, I have this in my <code>configuration.yaml</code> and I’m unwilling to remove it to see if it’s really required or not:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># Text to speech</span><br><span class="line">tts:</span><br><span class="line">  - platform: google_translate</span><br></pre></td></tr></table></figure><p>Now, to test Alexa’s ability to say stuff:</p><ol><li>Go to <strong>Developer Tools</strong> &gt; <strong>Services</strong>.</li><li>In the <strong>Service</strong> dropdown, select <code>notify.alexa_media_DEVICE_NAME</code>. One of these is generated for each of your Alexa devices. So for instance, the one in my office is <code>notify.alexa_media_office</code>. The auto-complete is key here.</li><li>In the <strong>Service Data</strong> editor, enter this: <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">data:</span><br><span class="line">  type: tts</span><br><span class="line">message: Hi there, I can make Alexa do my bidding.</span><br></pre></td></tr></table></figure></li><li>Click <strong>Call Service</strong>.</li></ol><p>No words can describe how giddy I was when this worked.</p><h2 id="Automation"><a href="#Automation" class="headerlink" title="Automation"></a>Automation</h2><p>Now to bring it all together. Go to <strong>Configuration</strong> &gt; <strong>Automations</strong> and create a new automation using the <strong>+</strong> in the lower-right. Then press the <strong>SKIP</strong> button as this is not a simple “turn off the lights” style automation.</p><p>These are the settings for my daughter Ellie’s notifications. If I don’t mention a field, leave it blank.</p><ol><li><strong>Triggers</strong>:<ul><li>Trigger type: State</li><li>Entity: <code>calendar.ellie_school</code></li><li>To: <code>on</code></li></ul></li><li><strong>Conditions</strong>: None</li><li><strong>Actions</strong>:<ul><li>Action type: Call service</li><li>Service: <code>notify.alexa_media_david_s_2nd_sonos_one_second_edition</code><ul><li>This is not the nicest name, but this is the device in the dining room where my daughter is set up.</li></ul></li><li>Service data: As shown below</li></ul></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">data:</span><br><span class="line">  type: tts</span><br><span class="line">message: &gt;-</span><br><span class="line">  &#123;&#123; state_attr(&#39;calendar.ellie_school&#39;, &#39;description&#39;) if</span><br><span class="line">  state_attr(&#39;calendar.ellie_school&#39;, &#39;description&#39;) !&#x3D; &#39;&#39; else</span><br><span class="line">  state_attr(&#39;calendar.ellie_school&#39;, &#39;message&#39;) &#125;&#125;</span><br></pre></td></tr></table></figure><p>I have another similar automation set up for my son, where the name of the calendar (in both the trigger and the script) and the device name in the Service are customized to him.</p><p>The Google Calendar integration exposes the calendar as an entity, similar to a sensor, and the properties of the current calendar entry as state attributes, similar to the temperature and humidity that the sensor would return. The message for Alexa to play is constructed from the Description field of the calendar event, or if that is missing, from the event title. That way I can have a title like “Morning Meeting” but have Alexa say something more useful like “Ellie, time to sign on to morning meeting.”</p><h2 id="Caveats"><a href="#Caveats" class="headerlink" title="Caveats"></a>Caveats</h2><p>The Google Calendar integration has some limitations that limit how I can use it.</p><p>The integration gets its data from Google by polling every 5-15 minutes, which is no surprise. My initial assumption was that on each poll, Home Assistant would download the next hour or so of events, and then dutifully trigger the sensor precisely at the beginning of every event in that range, until the data is updated by the next poll.</p><p><em>That’s just not at all how it works.</em></p><p>Remember that the calendar is surfaced like a sensor, and really more of a binary sensor, which is triggered when it turns on. That means if you have back to back events (like Reading directly after Number Corner) the sensor will transition from “on” to “on” and, as a result, the automation won’t be triggered.</p><p>The sensor, along with its collection of state attributes, is also the only place any data is stored that comes from Google. So it can only store data for one event at a time.</p><p>In practice, this means that I need to make sure events that are adjoining in real life are separated on the calendar by about 15 minutes, so that between events a poll of the server can get the next event’s data and reset the sensor to off until the next event starts.</p><p>I’ve also noticed that the Alexa notifications don’t happen right at the appointed time and can take up to about 45 seconds to happen. I ensured that all devices involved use a network time server and that clock drift is not an issue.</p><p>I’m not sure about the root cause. It could be some sort of refresh interval within Home Assistant. It could be that it takes some time for the text of the message to be translated to speech before it’s played. It could be many things.</p><p>Because I don’t want my kids to be consistently late, I set the calendar appointments to start a minute or two before the true start time.</p><p>The <a href="https://www.home-assistant.io/integrations/calendar.google/#sensor-attributes" target="_blank" rel="noopener">sensor attributes</a> include an <code>offset_reached</code> that is supposed to be used to trigger before the true event start time. Unfortunately, it requires including <code>!!-2</code> in the event title in order to have the offset reached 2 minutes before start time, and I thought that would confuse my kids. I wish there was a calendar-level setting so that all events on that calendar used a specific offset.</p><p>I get the feeling that this integration was meant for situations like not running certain automations when you’re on vacation, where these minute timing details wouldn’t matter.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>With Home Assistant, Google Calendar, and Alexa, I’m able to manage both my kids’ distance learning schedules, so that they have easy access to the Zoom links they need for school, and to be notified by Alexa when they need to use them.</p><p>This isn’t how I thought I’d be using Home Assistant when I ordered the Raspberry Pi. I haven’t even set up Z-Wave or ZigBee integration yet. But a few nights of wondering what I could get away with resulted in something that makes managing a nearly impossible situation a lot easier.</p><p>In the future I plan to use the Google Calendar integration more, for stuff like turning on the lights in my office in the morning automatically, but only on weekdays when I’m not on vacation.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;My kids have been distance learning since March. When Fall came, we picked the Distance Choice option in our local school district. We wanted to support teachers in any way we could, we both have jobs that enable us to work from home, and we figured if cases continued to get worse then all students would be distance learning eventually and then our kids wouldn’t have to make that transition mid-year.&lt;/p&gt;
&lt;p&gt;For what it’s worth, we were right. After Thanksgiving, everyone in our district is distance learning too.&lt;/p&gt;
&lt;p&gt;My oldest is in 3rd grade, my youngest is in kindergarten. The district is pretty on top of things. Both kids have school-issued iPads, have sync lessons over &lt;a href=&quot;https://zoom.us/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Zoom&lt;/a&gt; primarily on Mondays and Wednesdays, and get and submit assignments through &lt;a href=&quot;https://web.seesaw.me/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SeeSaw&lt;/a&gt; on async days.&lt;/p&gt;
&lt;p&gt;Two of the biggest problems at the start were schedule and Zoom links. Both kids have different schedules of when they need to be on Zoom, and needed an easy way to know which Zoom link they needed to join. I also didn’t want to be typing Zoom meeting IDs into an iPad keyboard that I was reading off my computer or phone. I needed a way to be able to manage schedule and links from my computer, make them available to each kid, and then make sure their butt was in the seat at the appropriate time.&lt;/p&gt;
&lt;p&gt;I have now brought all this together by combining Google Calendar and Alexa with the power of Home Assistant.&lt;/p&gt;
    
    </summary>
    
      <category term="Home Automation" scheme="https://www.davidboike.dev/categories/Home-Automation/"/>
    
    
      <category term="Home Assistant" scheme="https://www.davidboike.dev/tags/Home-Assistant/"/>
    
  </entry>
  
  <entry>
    <title>Path.Combine() isn&#39;t as cross-platform as you think it is</title>
    <link href="https://www.davidboike.dev/2020/06/path-combine-isnt-as-cross-platform-as-you-think-it-is/"/>
    <id>https://www.davidboike.dev/2020/06/path-combine-isnt-as-cross-platform-as-you-think-it-is/</id>
    <updated>2020-06-17T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>I started using .NET pretty close to the beginning, in either 2002 or 2003. It’s hard to accurately remember things that happened before I had kids.</p><p>Ever since that time, using <code>Path.Combine()</code> has been a best practice. You shouldn’t just concatenate paths together with <code>\\</code> after all, one day it’s possible that .NET could be cross-platform and then all that Windows-specific code will be broken! With each passing year, I grew less and less convinced that cross-platform .NET would ever happen but dutifully continued using <code>Path.Combine()</code> anyway.</p><p>Well now with .NET Core, cross-platform .NET is a reality, but as it turns out, <code>Path.Combine()</code> isn’t quite the cross-platform panacea I feel I was promised.</p><p>In this article, I’ll tell you what to look out for when using <code>Path.Combine()</code> on multiple platforms so you won’t get burned the same way I was.</p><a id="more"></a><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><p>The point of <code>Path.Combine()</code> is pretty simple on the surface.</p><p>Let’s say you have a base path <code>C:\base\path</code> and you want to add the filename <code>myfile.txt</code> to it.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var basePath &#x3D; @&quot;C:\base\path&quot;;</span><br><span class="line">var filename &#x3D; &quot;myfile.txt&quot;;</span><br></pre></td></tr></table></figure><p>You could just concatenate the strings:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var fullPath &#x3D; basePath + &quot;\\&quot; + filename;</span><br></pre></td></tr></table></figure><p>Or now that we have string interpolation you could concatenate it this way:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var fullPath &#x3D; $&quot;&#123;basePath&#125;\\&#123;filename&#125;&quot;;</span><br></pre></td></tr></table></figure><p>But that’s bad when we enter the realm of cross-platform because if I were executing this on macOS or Linux, or anything UNIX-like, my path separator would be different:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var basePath &#x3D; &quot;&#x2F;Users&#x2F;david&quot;;</span><br><span class="line">var filename &#x3D; &quot;myfile.txt&quot;;</span><br></pre></td></tr></table></figure><p>And so, using either of the concatenation options above, my result would be <code>/Users/david\myfile.txt</code>. That <em>will not</em> end the way you want it to.</p><p>That’s where <code>Path.Combine()</code> comes in. Instead of string concatenation, you call this instead:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var fullPath &#x3D; Path.Combine(basePath, filename);</span><br><span class="line">&#x2F;&#x2F; Windows Result: C:\base\path\myfile.txt</span><br><span class="line">&#x2F;&#x2F; Mac&#x2F;Linux Result: &#x2F;Users&#x2F;david&#x2F;myfile.txt</span><br></pre></td></tr></table></figure><p>So all we have to do is use <code>Path.Combine()</code> and our apps will be 100% ready to run cross-platform. Hooray!</p><p>If only it were that simple.</p><h2 id="Problem-1-Multi-segment-paths"><a href="#Problem-1-Multi-segment-paths" class="headerlink" title="Problem 1: Multi-segment paths"></a>Problem 1: Multi-segment paths</h2><p>Turns out, a lot of people use <code>Path.Combine()</code> <em>wrong</em> and there’s no feedback to tell you it’s wrong.</p><p>At a basic level, <code>Path.Combine(a,b)</code> simply concatenates <code>a</code> and <code>b</code> with whatever the local path separator is, as determined by <code>Path.DirectorySeparatorChar</code>. You can kind of think of it like this:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public static string Combine(string path1, string path2)</span><br><span class="line">&#123;</span><br><span class="line">    return path1 + Path.DirectorySeparatorChar + path2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There is <strong>absolutely zero</strong> checking for whether those two parameters contain <em>existing</em> directory separator characters for any platform. No sort of cross-platform normalization of directory separators going on there.</p><p>So what happens if you do this?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Path.Combine(basePath, @&quot;a\b\c&quot;);</span><br></pre></td></tr></table></figure><p>Keeping with our same <code>basePath</code> values for each platform above, for Windows you get <code>C:\base\path\a\b\c</code> which works great. But everywhere else, you get <code>/Users/david/a\b\c</code> which is not what you’re angling for.</p><p>But lots of developers do this, because there’s really no hint anywhere that a multi-segement path as one of those parameters is a bad idea. Let’s take a look at the method signature, with the xmldoc that defines what you get in Intellisense:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;&#x2F; &lt;summary&gt;Combines two strings into a path.&lt;&#x2F;summary&gt;</span><br><span class="line">&#x2F;&#x2F;&#x2F; &lt;param name&#x3D;&quot;path1&quot;&gt;The first path to combine.&lt;&#x2F;param&gt;</span><br><span class="line">&#x2F;&#x2F;&#x2F; &lt;param name&#x3D;&quot;path2&quot;&gt;The second path to combine.&lt;&#x2F;param&gt;</span><br><span class="line">&#x2F;&#x2F;&#x2F; &lt;returns&gt;</span><br><span class="line">&#x2F;&#x2F;&#x2F; The combined paths. If one of the specified paths is a zero-length string,</span><br><span class="line">&#x2F;&#x2F;&#x2F; this method returns the other path. If path2 contains an absolute path,</span><br><span class="line">&#x2F;&#x2F;&#x2F; this method returns path2.</span><br><span class="line">&#x2F;&#x2F;&#x2F; &lt;&#x2F;returns&gt;</span><br><span class="line">public static string Combine(string path1, string path2);</span><br></pre></td></tr></table></figure><p>Now, the first parameter is usually an established path that’s known to exist, so I have no qualms about <code>path1</code> here. But <code>path2</code> is extremely misleading. The definition of a <em>path</em> is a potentially really long string containing multiple directory names. That’s clearly not what is expected. Perhaps <code>path2</code> should be renamed to <code>pathSegment</code> or something else, but <code>path2</code> and the totally unhelpful parameter description “The second path to combine” are the exact opposite of what the method implementation expects.</p><p>The only real clue that something could be amiss (short of looking at the source code and understanding what it does…or reading this post) is that the <code>Combine</code> method has additional overloads that accept more parameters…</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public static string Combine(string path1, string path2, string path3)</span><br><span class="line">public static string Combine(string path1, string path2, string path3, string path4)</span><br><span class="line">public static string Combine(params string[] paths)</span><br></pre></td></tr></table></figure><p>…but really, these all continue the sins of the first.</p><p>So instead of this…</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Path.Combine(basePath, @&quot;a\b\c&quot;);</span><br></pre></td></tr></table></figure><p>…we should really be using this instead:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Path.Combine(basePath, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;);</span><br></pre></td></tr></table></figure><p>But unfortunately, it’s pretty common to see a lot more of the former than the latter.</p><h2 id="Problem-2-Windows-is-too-forgiving"><a href="#Problem-2-Windows-is-too-forgiving" class="headerlink" title="Problem 2: Windows is too forgiving"></a>Problem 2: Windows is too forgiving</h2><p>I’ve seen <code>Path.Combine(…)</code> used as sort of a low-rent version of <code>Server.MapPath(string path)</code> method, a staple of my (thankfully long-over) ASP.NET Web Forms days.</p><p>For those not familiar, <code>Server.MapPath(string path)</code> is <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.httpserverutility.mappath" target="_blank" rel="noopener">part of the System.Web assembly</a> and its purpose is to return a physical path that corresponds to a specific virtual path. So if you start out with a path from a web request, like <code>/path/to/file.html</code>, then <code>Server.MapPath(…)</code> understands what the root folder of the website is, as well as (if I recall correctly) any virtual directories set up in IIS as well. So then if your webroot is <code>C:\inetpub\wwwroot</code> and your virtual path is <code>/path/to/my-file.txt</code>, then <code>Server.MapPath(&quot;/path/to/my-file.txt&quot;)</code> will return that the file physically lives at <code>C:\inetpub\wwwroot\path\to\my-file.txt</code>.</p><p>All well and good, but living in <code>HttpServerUtility</code> in the monolithic <code>System.Web</code> assembly meant tight coupling to IIS. If you were building something with a different web framework, you didn’t have that.</p><p>So now, if you Google <a href="https://lmgtfy.com/?q=aspnetcore+MapPath" target="_blank" rel="noopener">aspnetcore MapPath</a>, what do you get? My <a href="https://www.mikesdotnetting.com/article/302/server-mappath-equivalent-in-asp-net-core" target="_blank" rel="noopener">first search result</a> says what?</p><p>It says use <code>Path.Combine(webRoot, &quot;test.txt&quot;)</code>.</p><p>OK, that works. What if your controller action is a catch-all like this?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">string webRoot;</span><br><span class="line"></span><br><span class="line">[HttpGet(&quot;&#123;*filePath&#125;&quot;)]</span><br><span class="line">public async Task&lt;ActionResult&gt; Get(string filePath)</span><br><span class="line">&#123;</span><br><span class="line">    var physicalPath &#x3D; Path.Combine(webRoot, filePath);</span><br><span class="line">    &#x2F;&#x2F; Do stuff</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If you try accessing something a few directories deep, you’ll end up with effectively this:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var physicalPath &#x3D; Path.Combine(@&quot;C:\root&quot;, &quot;virtual&#x2F;path-to&#x2F;file.html&quot;);</span><br></pre></td></tr></table></figure><p>And the result is: <code>C:\root\virtual/path/to/file.html</code>. That’s right, you’ll get <strong>mixed</strong> path separators.</p><p>But because Windows is too forgiving, <code>File.Exists()</code> on this path will return true, and you can happily return a <code>FileResponse</code> using that path. Maybe if it were a little more strict, people would get the memo that you aren’t supposed to have existing delimiters in <code>Path.Combine()</code> parameters.</p><p>For the record, the next few search results right at this moment:</p><ol start="2"><li>A <a href="https://stackoverflow.com/questions/49398965/what-is-the-equivalent-of-server-mappath-in-asp-net-core" target="_blank" rel="noopener">StackOverflow question</a> where the top-rated and accepted answer points out <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment" target="_blank" rel="noopener">IWebHostEnvironment</a> to get the root directory but not how to safely combine paths. That answer links to a different answer on the same page which…uses <code>Path.Combine()</code>.</li><li><a href="https://stackoverflow.com/questions/43992261/how-to-get-absolute-path-in-asp-net-core-alternative-way-for-server-mappath/43992313" target="_blank" rel="noopener">Another StackOverflow question</a>, same focus on how to get the web root and same use of <code>Path.Combine()</code>.</li><li><a href="https://gunnarpeipman.com/aspnet-core-content-webroot-server-mappath/" target="_blank" rel="noopener">Blog post</a> that ignores the “combine” part entirely.</li><li>A DZone scrape of the article in #4.</li><li><a href="https://inthetechpit.com/2020/02/17/how-to-use-server-mappath-in-asp-net-core/" target="_blank" rel="noopener">Another blog post</a> that ignores combining.</li><li><a href="https://kontext.tech/column/aspnet-core/228/servermappath-equivalent-in-aspnet-core-2" target="_blank" rel="noopener">Anotehr blog post</a> that uses <code>Path.Combine()</code>.</li><li>An <a href="https://github.com/dotnet/aspnetcore/issues/3824" target="_blank" rel="noopener">aspnetcore GitHub issue titled “Server.MapPath in AspNetCore”</a> that ends with “We don’t have plans to implement this.”</li></ol><p>You get the idea? How many developers would search farther than this? Maybe I’ll get lucky and this post will crack the top 5 and help somebody out. Maybe that person is you!</p><h2 id="Problem-3-Root-paths"><a href="#Problem-3-Root-paths" class="headerlink" title="Problem 3: Root paths"></a>Problem 3: Root paths</h2><p>Consider these two examples:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Console.WriteLine(Path.Combine(Environment.CurrentDirectory, &quot;\\abc\\abc&quot;));</span><br><span class="line">Console.WriteLine(Path.Combine(Environment.CurrentDirectory, &quot;&#x2F;abc&#x2F;abc&quot;));</span><br></pre></td></tr></table></figure><p>The <code>Path.Combine(…)</code> method has some kinda-sorta nods to trying to maybe a <em>little bit</em> be cross-platform, but it doesn’t work out too well in practice. In an internal <code>IsPathRooted()</code> method, a check is made to see if the first character of the second parameter is a directory separator or volume separator character.</p><p>On Windows, <code>\\</code> is considered the primary directory separator, while <code>/</code> is considered an <em>alternate</em> directory separator. So the result is this:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; Windows</span><br><span class="line">\abc\abc</span><br><span class="line">&#x2F;abc&#x2F;abc</span><br></pre></td></tr></table></figure><p>The beginning character was taken to represent the “root” of a filesystem, and so the first parameter wasn’t used at all. The answer in both cases was whatever the second path was.</p><p>Now here’s the result on my Mac:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;Users&#x2F;david&#x2F;testapp&#x2F;bin&#x2F;Debug&#x2F;netcoreapp3.1&#x2F;\abc\abc</span><br><span class="line">&#x2F;abc&#x2F;abc</span><br></pre></td></tr></table></figure><p>Well, that’s interesting. On macOS (and I assume on Linux as well, though I did not check) the primary directory separator AND the alternate directory separator are both <code>/</code> and the character <code>\\</code> is never considered, <em>ever</em>.</p><p>This is a bit of a corner-case, but still, drastically different results from code executing on different platforms. All the more reasons that <code>Path.Combine()</code> parameters should not be allowed to contain directory separators of any kind.</p><p>Perhaps one day I’ll get around to writing a Roslyn analyzer to make that a compile-time error.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>For a method that was created more than a decade before the framework was made cross-platform, it’s kind of amazing that <code>Path.Combine(…)</code> works at all. It does was it does, but you need to be aware of its idiosyncracies if you plan to use it in a cross-platform application or library.</p><p>There are really three basic, interrelated rules of thumb to keep in mind:</p><ol><li>The first parameter of <code>Path.Combine(…)</code> should be thought of as a base path, and you should always be absolutely sure that path already exists on the system.</li><li><strong>Every other parameter</strong> (because there are multiple overloads for different numbers of parameters) should not contain <strong>any</strong> path separator characters, from <strong>any</strong> platform.</li><li>When using <code>Path.Combine(…)</code> with user input, arbitrary inputs from a web request, or basically anything that isn’t a <em>literal</em> string, you should take care to split it apart based on all the different platform-specific directory separator characters (in practice, <code>/</code> and <code>\\</code>) and then feed the results of that into <code>Path.Combine(params string[] paths)</code>.</li></ol><p>One example of how to do #3 is this method:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">using System.IO;</span><br><span class="line">using System.Linq;</span><br><span class="line"></span><br><span class="line">public static class CrossPlatform</span><br><span class="line">&#123;</span><br><span class="line">    public static string PathCombine(string basePath, params string[] additional)</span><br><span class="line">    &#123;</span><br><span class="line">        var splits &#x3D; additional.Select(s &#x3D;&gt; s.Split(pathSplitCharacters)).ToArray();</span><br><span class="line">        var totalLength &#x3D; splits.Sum(arr &#x3D;&gt; arr.Length);</span><br><span class="line">        var segments &#x3D; new string[totalLength + 1];</span><br><span class="line">        segments[0] &#x3D; basePath;</span><br><span class="line">        var i &#x3D; 0;</span><br><span class="line">        foreach(var split in splits)</span><br><span class="line">        &#123;</span><br><span class="line">            foreach(var value in split)</span><br><span class="line">            &#123;</span><br><span class="line">                i++;</span><br><span class="line">                segments[i] &#x3D; value;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return Path.Combine(segments);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    static char[] pathSplitCharacters &#x3D; new char[] &#123; &#39;&#x2F;&#39;, &#39;\\&#39; &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Unfortunately, all the string splitting and then recombining allocates a lot more memory and will be quite a bit slower than <code>Path.Combine(…)</code> on a hot path, but more performant code will be inherently less readable and may need to re-implement some of the base assumptions that you take for granted in <code>Path.Combine()</code>.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I started using .NET pretty close to the beginning, in either 2002 or 2003. It’s hard to accurately remember things that happened before I had kids.&lt;/p&gt;
&lt;p&gt;Ever since that time, using &lt;code&gt;Path.Combine()&lt;/code&gt; has been a best practice. You shouldn’t just concatenate paths together with &lt;code&gt;\\&lt;/code&gt; after all, one day it’s possible that .NET could be cross-platform and then all that Windows-specific code will be broken! With each passing year, I grew less and less convinced that cross-platform .NET would ever happen but dutifully continued using &lt;code&gt;Path.Combine()&lt;/code&gt; anyway.&lt;/p&gt;
&lt;p&gt;Well now with .NET Core, cross-platform .NET is a reality, but as it turns out, &lt;code&gt;Path.Combine()&lt;/code&gt; isn’t quite the cross-platform panacea I feel I was promised.&lt;/p&gt;
&lt;p&gt;In this article, I’ll tell you what to look out for when using &lt;code&gt;Path.Combine()&lt;/code&gt; on multiple platforms so you won’t get burned the same way I was.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="C#" scheme="https://www.davidboike.dev/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>Overriding the NServiceBus ConversationId</title>
    <link href="https://www.davidboike.dev/2020/04/overriding-the-nservicebus-conversationid/"/>
    <id>https://www.davidboike.dev/2020/04/overriding-the-nservicebus-conversationid/</id>
    <updated>2020-04-15T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><em><strong>UPDATE:</strong> Starting in NServiceBus version 7.4 you can <a href="https://docs.particular.net/nservicebus/messaging/headers#messaging-interaction-headers-nservicebus-conversationid-starting-a-new-conversation" target="_blank" rel="noopener">create a new ConversationId</a> using <code>sendOptions.StartNewConversation()</code>. No more need to create a custom pipeline behavior as I explain here.</em></p></blockquote><p>The purpose of the <code>ConversationId</code> header included with every NServiceBus message is to relate a whole bunch of messages together as all having started from the same action. It’s generally a very bad idea to mess with the <code>ConversationId</code> in a message handler, so if you try, you’ll get this exception:</p><blockquote><p>System.Exception: Cannot set the NServiceBus.ConversationId header to ‘9203ecb1-d2ed-46eb-ae99-fbeb7a5db387’ as it cannot override the incoming header value (‘a1c91a87-2db9-493f-a638-ab9d016a1305’).</p></blockquote><p>But there are some times when it <em>might</em> be a good idea to override this id, if you know what you’re doing. This article shows you how to do that.</p><a id="more"></a><h2 id="What-ConversationId-is-for"><a href="#What-ConversationId-is-for" class="headerlink" title="What ConversationId is for"></a>What <code>ConversationId</code> is for</h2><p>When you click in a web application (for example) a message gets sent. This is the very first message in the “conversation” so a new <code>ConversationId</code> is generated. From that point on, every message that is sent as a result of that original message (from message handlers sending or publishing still more messages) copies the same <code>ConversationId</code> from the incoming message.</p><p>When these messages get successfuly processed, we can send a copy of them to an auditing store, like <a href="https://docs.particular.net/servicecontrol/" target="_blank" rel="noopener">ServiceControl</a>.</p><p>Then <a href="https://docs.particular.net/serviceinsight/" target="_blank" rel="noopener">ServiceInsight</a> can query our auditing store for messages having the same <code>ConversationId</code>, and with that information build a flow diagram like this:</p><p><img src="/images/2020/flow-diagram.png" alt="Flow diagram"></p><p>Or, a sequence diagram like this:</p><p><img src="/images/2020/sequence-diagram.png" alt="Sequence diagram"></p><h2 id="The-problem"><a href="#The-problem" class="headerlink" title="The problem"></a>The problem</h2><p>The problem is when you use a never-ending saga, something like the <code>CustomerHasBecomePreferred</code> saga I wrote about in <a href="https://particular.net/blog/death-to-the-batch-job" target="_blank" rel="noopener">Death to the batch job</a>, or the <a href="/2020/04/creating-a-scheduler-with-nservicebus/">sample scheduler saga</a> I wrote in my last post. There’s never an event to say “This is the start of a new conversation, please come up with a new ID.”</p><p>If you try to look at a saga like this using ServiceInsight, the diagrams would get larger and more complex the longer the saga lived, and it wouldn’t take long for the diagrams to become completely unusable.</p><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><p>Let’s take another look at the exception if we try to change the <code>ConversationId</code> within a message handler. This time I’ll include a couple lines from the stack trace.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">System.Exception: Cannot set the NServiceBus.ConversationId header to &#39;9203ecb1-d2ed-46eb-ae99-fbeb7a5db387&#39; as it cannot override the incoming header value (&#39;a1c91a87-2db9-493f-a638-ab9d016a1305&#39;).</span><br><span class="line">   at NServiceBus.AttachCausationHeadersBehavior.SetConversationIdHeader(IOutgoingLogicalMessageContext context, IncomingMessage incomingMessage)</span><br><span class="line">   at NServiceBus.AttachCausationHeadersBehavior.Invoke(IOutgoingLogicalMessageContext context, Func&#96;2 next)</span><br><span class="line">   ...</span><br></pre></td></tr></table></figure><p>I include the first couple lines of the stack trace because that’s a clue for how to get around this quandary. Specifically, the <code>AttachCausationHeadersBehavior</code> where the method takes an <code>IOutgoingLogicalMessageContext</code>.</p><p>This is a <a href="https://docs.particular.net/nservicebus/pipeline/manipulate-with-behaviors" target="_blank" rel="noopener">pipeline behavior</a>, one of many built into NServiceBus that do things to messages as they’re either processed (incoming behaviors) or sent out (outgoing behaviors).</p><p>In this case, <code>IOutgoingLogicalMessageContext</code> tells us that we’re operating on the part of the <em>outgoing</em> message pipeline where we have a <code>logical message</code>—in other words, we’re still dealing with a class and haven’t serialized the message to bytes to send to the message transport yet.</p><p>We can operate later in the pipeline by creating our own behavior operating on the <code>IOutgoingPhysicalMessageContext</code>.</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">public class ModifyConversationIdBehavior : Behavior&lt;IOutgoingPhysicalMessageContext&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">const</span> <span class="keyword">string</span> OverrideHeader = <span class="string">"Temp.OverrideConversationId"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> Task <span class="title">Invoke</span>(<span class="params">IOutgoingPhysicalMessageContext context, Func&lt;Task&gt; next</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">// If a temporary override header has been set, move THAT value into the real header</span></span><br><span class="line">        <span class="keyword">if</span>(context.Headers.TryGetValue(OverrideHeader, <span class="keyword">out</span> <span class="keyword">string</span> overridingConversationId))</span><br><span class="line">        &#123;</span><br><span class="line">            context.Headers[Headers.ConversationId] = overridingConversationId;</span><br><span class="line">            context.Headers.Remove(OverrideHeader);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Execute the rest of the pipeline</span></span><br><span class="line">        <span class="keyword">return</span> next();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We also have to register this new pipeline behavior when we configure the endpoint containing the scheduler saga:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">endpointConfiguration.Pipeline.Register(<span class="keyword">new</span> ModifyConversationIdBehavior(), <span class="string">"Modifies the ConversationId of an outgoing message if necessary."</span>);</span><br></pre></td></tr></table></figure><p>Now, from wherever point you want to cut the conversation into two (in my scheduler saga, it’s the point where the scheduler fires off a new execution of the task) you can do this:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> command = <span class="keyword">new</span> WhateverCommand();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> sendOptions = <span class="keyword">new</span> SendOptions();</span><br><span class="line">sendOptions.SetHeader(ModifyConversationIdBehavior.OverrideHeader, Guid.NewGuid().ToString());</span><br><span class="line"><span class="keyword">await</span> context.Send(command, sendOptions);</span><br></pre></td></tr></table></figure><p>When intercepted by the behavior the value stored in the <code>OverrideHeader</code> will override the value copied from the previous message in the chain, effectively starting a brand new conversation.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Overriding <code>ConversationId</code> isn’t something to be done lightly, as you can break your auditing and message visualizations. That’s why the NServiceBus API tries to prevent you from doing it. But with a framework as extensible as NServiceBus, there’s almost always a way to break the rules, and <a href="https://docs.particular.net/nservicebus/pipeline/manipulate-with-behaviors" target="_blank" rel="noopener">pipeline behaviors</a> are a common outlet for well-meaning rule-breakers to do just about anything you can dream up.</p><p>For more on useful behaviors, you might want to check out my post <a href="https://particular.net/blog/infrastructure-soup" target="_blank" rel="noopener">Infrastructure soup</a> on the Particular Software blog.</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Starting in NServiceBus version 7.4 you can &lt;a href=&quot;https://docs.particular.net/nservicebus/messaging/headers#messaging-interaction-headers-nservicebus-conversationid-starting-a-new-conversation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;create a new ConversationId&lt;/a&gt; using &lt;code&gt;sendOptions.StartNewConversation()&lt;/code&gt;. No more need to create a custom pipeline behavior as I explain here.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The purpose of the &lt;code&gt;ConversationId&lt;/code&gt; header included with every NServiceBus message is to relate a whole bunch of messages together as all having started from the same action. It’s generally a very bad idea to mess with the &lt;code&gt;ConversationId&lt;/code&gt; in a message handler, so if you try, you’ll get this exception:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;System.Exception: Cannot set the NServiceBus.ConversationId header to ‘9203ecb1-d2ed-46eb-ae99-fbeb7a5db387’ as it cannot override the incoming header value (‘a1c91a87-2db9-493f-a638-ab9d016a1305’).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But there are some times when it &lt;em&gt;might&lt;/em&gt; be a good idea to override this id, if you know what you’re doing. This article shows you how to do that.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="source code" scheme="https://www.davidboike.dev/tags/source-code/"/>
    
      <category term="Sagas" scheme="https://www.davidboike.dev/tags/Sagas/"/>
    
  </entry>
  
  <entry>
    <title>Creating a scheduler with NServiceBus</title>
    <link href="https://www.davidboike.dev/2020/04/creating-a-scheduler-with-nservicebus/"/>
    <id>https://www.davidboike.dev/2020/04/creating-a-scheduler-with-nservicebus/</id>
    <updated>2020-04-15T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Sometimes a system needs a kind of rudimentary scheduler to tell it when it’s time to do things. Run a weekly report. Poll a legacy system for changes every night. Run some script every 4 hours. The examples are plentiful but usually quite boring.</p><p>In a message-based system with NServiceBus, starting these tasks is usually as simple as sending a command, but something still has to send that command on the right schedule. We could use a Windows Scheduled Task, but that feels gross for a couple of reasons. First, we have to create a dedicated app just to spin up a send-only endpoint, send one command, and then die. Second, it feels wrong somehow that using scheduled tasks would take an important part of the system (the schedule) and place it entirely outside the system, complicating the deployment, especially if we (the developers) don’t have direct access to the production infrastructure, either because of our sysadmins or because there is no infrastructure because we’re using Platform-as-a-Service in the cloud.</p><p>So we’re dealing with NServiceBus and time, and NServiceBus is supposed to be able to model time through the use of sagas. So couldn’t we implement a simple scheduler using a saga?</p><p>Well sure of course we could. But should we? It’s a classic <em>it depends</em> scenario. So let’s delve a little further. I’ll explain how you <em>could</em> do it, and then we’ll be in a better place to talk about whether that’s even a good idea in the first place.</p><a id="more"></a><h2 id="The-saga"><a href="#The-saga" class="headerlink" title="The saga"></a>The saga</h2><p>Remember that a saga is basically a message-driven state machine (see the <a href="https://docs.particular.net/tutorials/nservicebus-sagas/1-getting-started/" target="_blank" rel="noopener">saga basics tutorial</a>) where the state is stored (usually in a database of some kind) between messages. Some of those messages can have a delay (see the <a href="https://docs.particular.net/tutorials/nservicebus-sagas/2-timeouts/" target="_blank" rel="noopener">timeouts tutorial</a>) to wake it up at some point in the future.</p><p>Ideally, a scheduler saga would be able to handle multiple schedules, otherwise, a saga is actually quite a bit of code for something so simple, especially if you have to create multiple of them. Sagas are hard to use as a singleton process anyway—they want to use a <code>CorrelationId</code> to tell between different <em>instances</em> of the saga that all have independent data.</p><p>We can take advantage of that - each saga instance will be for a different schedule, and the <code>CorrelationId</code> will be the type of message we want to send when the schedule comes due.</p><p>First, let’s look at the message we’ll send to start the scheduler saga:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Text;</span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Messages</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">StartSchedule</span> : <span class="title">ICommand</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">string</span> CommandTypeFullName &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> TimeSpan Interval &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> WeeklySchedule Weekly &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">StartSchedule</span>(<span class="params"></span>)</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">StartSchedule</span>(<span class="params">Type type, TimeSpan interval</span>)</span></span><br><span class="line"><span class="function">            : <span class="title">this</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>        &#123;</span><br><span class="line">            CommandTypeFullName = type.FullName;</span><br><span class="line">            Interval = interval;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">StartSchedule</span>(<span class="params">Type type, WeeklySchedule weekly</span>)</span></span><br><span class="line"><span class="function"></span>        &#123;</span><br><span class="line">            CommandTypeFullName = type.FullName;</span><br><span class="line">            Weekly = weekly;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">WeeklySchedule</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> DayOfWeek[] DaysOfWeek &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> TimeSpan TimeOfDay &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This allows us two variations on schedule. Either we can do a weekly schedule, on multiple days if necessary but all at the same time of day, by sending a message like this:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">await</span> endpoint.Send(<span class="keyword">new</span> StartSchedule(<span class="keyword">typeof</span>(DoFirstThingWeekly), <span class="keyword">new</span> WeeklySchedule</span><br><span class="line">&#123;</span><br><span class="line">    DaysOfWeek = <span class="keyword">new</span> [] &#123; DayOfWeek.Friday &#125;,</span><br><span class="line">    TimeOfDay = <span class="keyword">new</span> TimeSpan(<span class="number">12</span>, <span class="number">0</span> ,<span class="number">0</span>)</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><p>Or, we can do a regular time span, like every 8 hours for instance, like this:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">await</span> endpoint.Send(<span class="keyword">new</span> StartSchedule(<span class="keyword">typeof</span>(DoSecondThingEvery8Hours), TimeSpan.FromHours(<span class="number">8</span>)));</span><br></pre></td></tr></table></figure><p>These calls should be made from your application’s startup. This way, the saga will be “reminded” of the schedule every single time your app starts up, and also covers the use cases of bootstrapping the first run or changing the schedule later on.</p><p>Now let’s get to the actual saga, which I’ll attempt to mark up with comments:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"><span class="keyword">using</span> Messages;</span><br><span class="line"></span><br><span class="line">public class Scheduler : Saga&lt;Scheduler.ScheduleData&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;StartSchedule&gt;,</span><br><span class="line">    IHandleTimeouts&lt;Scheduler.NextTimeTimeout&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">ScheduleData</span> : <span class="title">ContainSagaData</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">string</span> CommandTypeFullName &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> TimeSpan Interval &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> WeeklySchedule Weekly &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">        <span class="keyword">public</span> DateTime NextRun &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">ConfigureHowToFindSaga</span>(<span class="params">SagaPropertyMapper&lt;ScheduleData&gt; mapper</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">// The mapper is responsible for finding the saga data based on a message.</span></span><br><span class="line">        <span class="comment">// So in SQL this would generate something like:</span></span><br><span class="line">        <span class="comment">//     select from ScheduleData </span></span><br><span class="line">        <span class="comment">//     where CommandTypeFullName = @MessageValueOfCommandTypeFullName</span></span><br><span class="line">        mapper.ConfigureMapping&lt;StartSchedule&gt;(m =&gt; m.CommandTypeFullName)</span><br><span class="line">           .ToSaga(s =&gt; s.CommandTypeFullName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Task <span class="title">Handle</span>(<span class="params">StartSchedule message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">// Either we're starting the saga for the first time, or "reprogramming" it </span></span><br><span class="line">        <span class="comment">// with new schedule data, so we store the new values and then go to Run() </span></span><br><span class="line">        <span class="comment">// to do the dirty work</span></span><br><span class="line">        Data.Interval = message.Interval;</span><br><span class="line">        Data.Weekly = message.Weekly;</span><br><span class="line">        <span class="keyword">return</span> Run(context);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Task <span class="title">Timeout</span>(<span class="params">NextTimeTimeout state, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">// When a timeout message comes due, we just want to see whether </span></span><br><span class="line">        <span class="comment">// it's time to act</span></span><br><span class="line">        <span class="keyword">return</span> Run(context);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">async</span> Task <span class="title">Run</span>(<span class="params">IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">var</span> now = DateTime.UtcNow;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// May be because the app just started up or we're "reprogramming" the saga</span></span><br><span class="line">        <span class="comment">// We don't necessarily want to spring into action unless it's really time.</span></span><br><span class="line">        <span class="keyword">if</span> (Data.NextRun &gt; now)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Depending on whether we're doing interval/weekly scheduling, we want to </span></span><br><span class="line">        <span class="comment">// figure out when the next time to run is.</span></span><br><span class="line">        <span class="keyword">if</span> (Data.Interval &gt; TimeSpan.Zero)</span><br><span class="line">        &#123;</span><br><span class="line">            Data.NextRun = now + Data.Interval;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Data.Weekly != <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// Cycle through the next 7 days until we find the next scheduled date</span></span><br><span class="line">            <span class="keyword">var</span> start = now.Date.AddDays(<span class="number">1</span>);</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">var</span> day = start; day &lt; start.AddDays(<span class="number">7</span>); day = day.AddDays(<span class="number">1</span>))</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span> (Data.Weekly.DaysOfWeek.Contains(day.DayOfWeek))</span><br><span class="line">                &#123;</span><br><span class="line">                    Data.NextRun = day + Data.Weekly.TimeOfDay;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Get a type from the command name. Must be in same assembly as StartSchedule</span></span><br><span class="line">        <span class="keyword">var</span> commandType = <span class="keyword">typeof</span>(StartSchedule).Assembly</span><br><span class="line">            .GetType(Data.CommandTypeFullName);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Create &amp; send instance of the command. Must be parameterless constructor</span></span><br><span class="line">        <span class="keyword">var</span> command = Activator.CreateInstance(commandType) <span class="keyword">as</span> ICommand;</span><br><span class="line">        <span class="keyword">await</span> context.Send(command);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Request the timeout for the next activation</span></span><br><span class="line">        <span class="keyword">await</span> RequestTimeout&lt;NextTimeTimeout&gt;(context, Data.NextRun);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">NextTimeTimeout</span> &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Does this work? Sure! Well, mostly. It’s got some issues, but depending on the project, these may be anything from easy-to-ignore minor details all the way up to showstoppers. Let’s look at each of these “it depends” scenarios.</p><h2 id="Clock-drift"><a href="#Clock-drift" class="headerlink" title="Clock drift"></a>Clock drift</h2><p>Arguably the biggest problem with this whole saga implementation are these 4 lines, which at first glance look logically correct:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (Data.NextRun &gt; now)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Because we want to be able to “reprogram” the saga at any time, we want to be able to ignore some messages, like if a <code>StartSchedule</code> message comes in that’s only the result of your app restarting.</p><p>Logically, the timeout message should occur after <code>now</code> and should skip this check—after all that’s the point of a timeout.</p><p>But what if the timeout is handled by a message transport like Azure Service Bus that has native scheduled messages, and what if the clock on the processing server has drifted such that it is a few seconds behind official “Azure Time”?</p><p>The result will be that the message scheduled for 12:00:00 UTC may arrive, as far as the processing server is concerned, at 11:59:57 UTC. At that point, <code>Data.NextRun</code> of noon is 3 seconds in the “future”, so the message will be ignored.</p><p>Not only will the scheduled task <em>not</em> fire, but because the next schedule is set at the same time, the scheduler is essentially in limbo until the next time your app starts up to send a <code>StartSchedule</code> that will be after the <code>Data.NextRun</code> time.</p><p>One way to try to fix this is to have a bit of tolerance for clock drift in the code, like this:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (Data.NextRun &gt; now.AddSeconds(<span class="number">-30</span>))</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This way, the clock can drift up to 30 seconds and still get the behavior we want. However, this saga is now pretty useless for doing anything with sub-minute times with much accuracy. In the cases where I’ve done this, I only care that the task happens once a day, or maybe every 8 hours, and I’m not too fussed if it happens 30 seconds off its scheduled time. I just want it done.</p><h2 id="Reprogramming-is-limited"><a href="#Reprogramming-is-limited" class="headerlink" title="Reprogramming is limited"></a>Reprogramming is limited</h2><p>In this saga, you can reprogram the timer, but only by deploying new code. Because we’re storing the next run time and not taking any action before that time, you basically can’t reprogram it to anything <em>faster</em> than what it was doing before. If it’s not going to run until 8 hours from now, and you reprogram it to be an hourly timer, then sorry, it’s not going to run for 8 hours, but <em>then</em> it will run for every hour after that.</p><p>This becomes more of a problem if you accidentally have a deployment set a next run time on the order of days/months, and then want it to be hourly for some reason.</p><p>It would be possible to change the code to always recalculate the next run time on receipt of <code>StartSchedule</code>, but then the code can get much more complex, and you run the risk of upsetting the schedule if for some reason you encounter more frequent app restarts.</p><p>In my situation, my schedules are completely arbitrary and I have no intention of changing them…ever. So no need to worry.</p><h2 id="Auditing"><a href="#Auditing" class="headerlink" title="Auditing"></a>Auditing</h2><p>If you’re auditing messages to ServiceControl, and want to be able to view diagrams in ServiceInsight, then this scheduler has a bit of a problem because all the messages coming from this saga are going to be viewed as part of the conversation because they all share the same <code>ConversationId</code> header. So there wouldn’t be any way to look at the message flow from just the most recent execution, or the one that happened 3 days ago. The diagrams would get bigger and more complex, and it wouldn’t take very long for the diagrams to become unusable.</p><p>This would be really frustrating if each schedule kicks off some process, such as a file import, with many substeps that you’d want to be able to visually debug later.</p><p>Well, there are ways around such things, like changing the <code>ConversationId</code> so that the conversations are separate. It’s not possible to directly set the <code>ConversationId</code> within a handler though.</p><p>This post was getting pretty long, and changing the <code>ConversationId</code> has uses outside of this scheduler saga, so I wrote a separate post on <a href="/2020/04/overriding-the-nservicebus-conversationid/">overriding the NServiceBus ConversationId</a>. If you create the behavior outlined in that article, then you can change the saga code a bit.</p><p>With the behavior outlined in that article in place, you can change this code in the saga:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create an instance of the command and send it. Must be parameterless constructor</span></span><br><span class="line"><span class="keyword">var</span> command = Activator.CreateInstance(commandType) <span class="keyword">as</span> ICommand;</span><br><span class="line"><span class="keyword">await</span> context.Send(command);</span><br></pre></td></tr></table></figure><p>To this:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create an instance of the command and send it. Must be parameterless constructor</span></span><br><span class="line"><span class="keyword">var</span> command = Activator.CreateInstance(commandType) <span class="keyword">as</span> ICommand;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> sendOptions = <span class="keyword">new</span> SendOptions();</span><br><span class="line">sendOptions.SetHeader(ModifyConversationIdBehavior.OverrideHeader, Guid.NewGuid().ToString());</span><br><span class="line"><span class="keyword">await</span> context.Send(command, sendOptions);</span><br></pre></td></tr></table></figure><p>Now every command sent out by the scheduler will be a fresh conversation you can look at individually in ServiceInsight.</p><p>Of course, if you’re not auditing messages at all, then you don’t need to do anything like that.</p><h2 id="Date-and-time-is-just-hard"><a href="#Date-and-time-is-just-hard" class="headerlink" title="Date and time is just hard"></a>Date and time is just hard</h2><p>This is a super-simple scheduler, doing only weekly and interval scheduling. For me, that’s all I need. If you wanted to do any of the fancy things your calendar app can do, like:</p><ul><li>Repeat every N days</li><li>Repeat every N weeks</li><li>Repeat every N months</li><li>Repeat on the Nth Tuesday of the month</li><li>Repeat every N years</li><li>Ending after N occurrences</li><li>Ending on X date</li><li>Anything to do with time zones</li><li>Anything where you care about Daylight Saving</li><li>Anything where you care about leap days</li><li>Anything involving <a href="https://www.timeanddate.com/calendar/determining-easter-date.html" target="_blank" rel="noopener">Easter</a></li></ul><p>…well then you’re out of luck. Date and time are hard, and if you don’t believe me, go read some blog posts by <a href="https://codeofmatt.com/" target="_blank" rel="noopener">Matt Johnson-Pint</a>.  I don’t recommend trying to extend the code to handle any of these other scenarios. It’s not worth it. Keep reading and pick a different option.</p><h2 id="Scheduling-alternatives"><a href="#Scheduling-alternatives" class="headerlink" title="Scheduling alternatives"></a>Scheduling alternatives</h2><p>I’ve presented a simple scheduler saga, and a bunch of problems it might cause you, all of which you can address to some varying degree. But is it worth it?</p><p><img src="/images/2020/could-should.jpg" alt="Your scientists were so preoccupied with whether or not you could, you didn&#39;t stop to think if you should"></p><p>If you’re running a small project with very simple scheduling needs, and none of the sections above gives you pause, then you’ll probably be fine. Otherwise, you might want to think about one of these alternatives, any of which you can use to send an NServiceBus message.</p><ul><li><a href="https://www.quartz-scheduler.net/" target="_blank" rel="noopener">Quartz.NET</a> is a full-featured scheduler library for .NET. It can be complex, but it can do anything you need it to.</li><li><a href="https://www.hangfire.io/" target="_blank" rel="noopener">Hangfire</a> is a way to perform background processing on .NET without a Windows service or separate process. It can handle recurring jobs using a CRON schedule and persists the jobs in a database, so you don’t have to worry about app restarts mucking up your schedule.</li><li><a href="https://azure.microsoft.com/en-us/services/functions/" target="_blank" rel="noopener">Azure Functions</a> has a <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=csharp" target="_blank" rel="noopener">timer trigger</a> that runs a CRON schedule.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>A bad consultant will say “it depends” and leave it at that. A good one will say “it depends” but then tell you the things it depends on.</p><p>My aim in writing this article is not so much to share the code for the scheduler saga, but to highlight all the “it depends” around whether or not it’s a good tool for your particular job.</p><p>I do use this exact scheduler saga in a project. It’s a small project, more of a proof of concept, that runs as a single NServiceBus endpoint embedded in an Azure App Service run using Visual Studio Azure credits. I’m already using NServiceBus in this project, and so I’m looking for reliable scheduling (i.e. not <code>System.Timers.Timer</code>) with the lowest possible barrier to entry.</p><p>None of the caveats in this article apply to me, so I’m happy to use it. Though I must advmit, if I had to do it over, I would strongly consider using an Azure Functions timer trigger instead.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Sometimes a system needs a kind of rudimentary scheduler to tell it when it’s time to do things. Run a weekly report. Poll a legacy system for changes every night. Run some script every 4 hours. The examples are plentiful but usually quite boring.&lt;/p&gt;
&lt;p&gt;In a message-based system with NServiceBus, starting these tasks is usually as simple as sending a command, but something still has to send that command on the right schedule. We could use a Windows Scheduled Task, but that feels gross for a couple of reasons. First, we have to create a dedicated app just to spin up a send-only endpoint, send one command, and then die. Second, it feels wrong somehow that using scheduled tasks would take an important part of the system (the schedule) and place it entirely outside the system, complicating the deployment, especially if we (the developers) don’t have direct access to the production infrastructure, either because of our sysadmins or because there is no infrastructure because we’re using Platform-as-a-Service in the cloud.&lt;/p&gt;
&lt;p&gt;So we’re dealing with NServiceBus and time, and NServiceBus is supposed to be able to model time through the use of sagas. So couldn’t we implement a simple scheduler using a saga?&lt;/p&gt;
&lt;p&gt;Well sure of course we could. But should we? It’s a classic &lt;em&gt;it depends&lt;/em&gt; scenario. So let’s delve a little further. I’ll explain how you &lt;em&gt;could&lt;/em&gt; do it, and then we’ll be in a better place to talk about whether that’s even a good idea in the first place.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="source code" scheme="https://www.davidboike.dev/tags/source-code/"/>
    
      <category term="Sagas" scheme="https://www.davidboike.dev/tags/Sagas/"/>
    
  </entry>
  
  <entry>
    <title>Creating a RavenDB cluster in Docker</title>
    <link href="https://www.davidboike.dev/2019/06/creating-a-ravendb-cluster-in-docker/"/>
    <id>https://www.davidboike.dev/2019/06/creating-a-ravendb-cluster-in-docker/</id>
    <updated>2019-06-11T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>At Particular we support a bunch of different technologies, so it seems there’s no end to the infrastructure software I might have to use on any given day. SQL Server, RabbitMQ, MongoDB, MySQL, PostgreSQL, MariaDB, even (shudder) <em>Oracle</em>.</p><p>I don’t want all that crap installed on my machine. In fact, I don’t want to install infrastructure on my machine again, like…ever.</p><p>So when I needed to work with a RavenDB cluster, I Dockerized it, and here’s how I did it. Maybe it’s not perfect, maybe it could be better? If you think so, let me know! I feel like I stumbled through this, but the result appears to work well.</p><a id="more"></a><h2 id="Docker-networking-is-fun"><a href="#Docker-networking-is-fun" class="headerlink" title="Docker networking is fun"></a>Docker networking is fun</h2><blockquote><p><em><strong>NOTE:</strong></em>: It’s helpful to know that I run Windows on macOS with Parallels, and that my Windows host file contains a <code>hostos</code> entry that is always addressable to the Mac, so I can use that like a <code>localhost</code> except it’s locahost on macOS, not on the Windows virtual machine. I hope to blog more about this in the near future.</p></blockquote><p>When dealing with Docker and networking, it seems if you’re not already a network engineer (which I am not) you’re already at a bit of a disadvantage.</p><p>There are two pretty easy modes of operation:</p><ul><li>If you declare mapped ports, you can talk to the application on those ports on localhost without much fuss.</li><li>If you give the containers names, the containers can talk to each other using those names.</li></ul><p>In either case, all the Docker stuff gets its own little island, and you have very defined bridges (the exposed ports) onto that island.</p><p>But a RavenDB cluster has a few different wrinkles. One RavenDB server will normally communicate on port 8080 (HTTP) and port 38888 (TCP), and need to communicate both externally <em>and</em> amongst themselves, but you can’t use the same address for both. This becomes a problem when the Raven cluster gives its internal addresses to the client, which then wants to verify that they’re all alive and can’t even find an address.</p><p>Let me give an example. If you create containers named <code>raven1</code>, <code>raven2</code>, and <code>raven3</code> and set them up as a cluster, <code>raven1</code> can see and talk to <code>raven2</code> and <code>raven3</code>, but then reports those names to the client, in this case code running in Visual Studio, and the Windows environment has no idea how to resolve <code>raven1</code>.</p><p>The Raven team knew this (they are much better at network engineering than me) and provided configuration options to deal with it by providing environment variables:</p><ul><li><code>RAVEN_ServerUrl</code> - The internal port 8080 address. This is always <code>http://0.0.0.0:8080</code>, the 0s mean that it can respond on any host name you throw at it. It’s always port 8080 because this is local to the container - nothing else will be vying for this.</li><li><code>RAVEN_ServerUrl_Tcp</code> - Same deal but for the TCP port. Always <code>tcp://0.0.0.0:38888</code>.</li><li><code>RAVEN_PublicServerUrl</code> - This is the external address for the 8080 address, or in other words, how you’d get onto the Docker island. Here I provide one of the following, one for each node:<ul><li><code>http://hostos:8080</code></li><li><code>http://hostos:8081</code></li><li><code>http://hostos:8082</code></li></ul></li><li><code>RAVEN_PublicServerUrl_Tcp</code> - Same deal but for TCP. Either:<ul><li><code>tcp://hostos:38888</code></li><li><code>tcp://hostos:38889</code></li><li><code>tcp://hostos:38890</code></li></ul></li></ul><p>With this setup of public/private URLs, Raven reports its server topology using the public URLs, which my code is able to look up, and everything just works.</p><h2 id="Docker-compose"><a href="#Docker-compose" class="headerlink" title="Docker compose"></a>Docker compose</h2><p>So given the networking aspects above, running <code>docker-compose up --detach</code> with the contents below in <code>docker-compose.yml</code> ramps up the 3 server nodes:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">version: &#39;3&#39;</span><br><span class="line">services:</span><br><span class="line">  raven1:</span><br><span class="line">    container_name: raven1</span><br><span class="line">    image: ravendb&#x2F;ravendb</span><br><span class="line">    ports:</span><br><span class="line">      - 8080:8080</span><br><span class="line">      - 38888:38888</span><br><span class="line">    extra_hosts:</span><br><span class="line">      - &quot;hostos:10.211.55.2&quot;</span><br><span class="line">    environment:</span><br><span class="line">      - RAVEN_Security_UnsecuredAccessAllowed&#x3D;PublicNetwork</span><br><span class="line">      - RAVEN_Setup_Mode&#x3D;None</span><br><span class="line">      - RAVEN_License_Eula_Accepted&#x3D;true</span><br><span class="line">      - &quot;RAVEN_ServerUrl&#x3D;http:&#x2F;&#x2F;0.0.0.0:8080&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl&#x3D;http:&#x2F;&#x2F;hostos:8080&quot;</span><br><span class="line">      - &quot;RAVEN_ServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;0.0.0.0:38888&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;hostos:38888&quot;</span><br><span class="line">  raven2:</span><br><span class="line">    container_name: raven2</span><br><span class="line">    image: ravendb&#x2F;ravendb</span><br><span class="line">    ports:</span><br><span class="line">      - 8081:8080</span><br><span class="line">      - 38889:38888</span><br><span class="line">    extra_hosts:</span><br><span class="line">      - &quot;hostos:10.211.55.2&quot;</span><br><span class="line">    environment:</span><br><span class="line">      - RAVEN_Security_UnsecuredAccessAllowed&#x3D;PublicNetwork</span><br><span class="line">      - RAVEN_Setup_Mode&#x3D;None</span><br><span class="line">      - RAVEN_License_Eula_Accepted&#x3D;true</span><br><span class="line">      - &quot;RAVEN_ServerUrl&#x3D;http:&#x2F;&#x2F;0.0.0.0:8080&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl&#x3D;http:&#x2F;&#x2F;hostos:8081&quot;</span><br><span class="line">      - &quot;RAVEN_ServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;0.0.0.0:38888&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;hostos:38889&quot;</span><br><span class="line">  raven3:</span><br><span class="line">    container_name: raven3</span><br><span class="line">    image: ravendb&#x2F;ravendb</span><br><span class="line">    ports:</span><br><span class="line">      - 8082:8080</span><br><span class="line">      - 38890:38888</span><br><span class="line">    extra_hosts:</span><br><span class="line">      - &quot;hostos:10.211.55.2&quot;</span><br><span class="line">    environment:</span><br><span class="line">      - RAVEN_Security_UnsecuredAccessAllowed&#x3D;PublicNetwork</span><br><span class="line">      - RAVEN_Setup_Mode&#x3D;None</span><br><span class="line">      - RAVEN_License_Eula_Accepted&#x3D;true</span><br><span class="line">      - &quot;RAVEN_ServerUrl&#x3D;http:&#x2F;&#x2F;0.0.0.0:8080&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl&#x3D;http:&#x2F;&#x2F;hostos:8082&quot;</span><br><span class="line">      - &quot;RAVEN_ServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;0.0.0.0:38888&quot;</span><br><span class="line">      - &quot;RAVEN_PublicServerUrl_Tcp&#x3D;tcp:&#x2F;&#x2F;hostos:38890&quot;</span><br></pre></td></tr></table></figure><p>Just a few other notes:</p><ul><li><code>extra_hosts</code> defines my macOS host entry on each of the Docker containers as well. Essentially this gives network traffic a way to get off the Docker island and then return.</li><li>There are extra environment variables that make sure I don’t have to go through a lot of Raven setup mumbo-jumbo on each server node. You can look up exactly what they do in the RavenDB documentation.</li></ul><h2 id="Setting-up-the-cluster"><a href="#Setting-up-the-cluster" class="headerlink" title="Setting up the cluster"></a>Setting up the cluster</h2><p>Running <code>docker-compose</code> only gets you so far. When it’s complete you get 3 Raven nodes that aren’t connected in any way, and don’t even have a license applied. In order to set up a cluster you <strong>must</strong> have a license, and it must be applied <strong>only</strong> to the node you intend to be the leader. The remaining nodes are then joined to the already-licensed leader and are allotted a number of assigned cores from the license’s maximum limit. Because a (free) development license allows up to 3 cores, that’s 1 core per node.</p><p>So I actually have a bash script (remember I’m on a Mac) that runs <code>docker-compose</code> and then executes a series of <code>curl</code> commands to configure the cluster.</p><p>First, I apply the license to <code>raven1</code>. This is back to using <code>localhost</code> becuase I execute it on the Mac:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;Applying licenses...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;license&#x2F;activate&#39; -H &#39;Content-Type: application&#x2F;json; charset&#x3D;UTF-8&#39; --data-binary &#39;&#123;LICENSE_JSON&#125;&#39; --compressed ;</span><br></pre></td></tr></table></figure><p>You’ll need to provide your own <code>LICENSE_JSON</code> stripped of any prettified whitespace. However instead of copying my script and trying to do this manually, the easiest way is to do it using RavenDB Studio in Chrome, while watching with Chrome developer tools. On the Network tab, you can take any request, right-click, and get a bunch of options. On Windows, you can copy a request as PowerShell or cURL.</p><p><img src="/images/2019/chrome-network-copy-menu.png" alt="Chrome Debugger Network Right-Click Menu"></p><p>So pick your scripting poison, and then just remove any irrelevant headers. The RavenDB server doesn’t really care what your user agent is.</p><p>Next I want to tell <code>raven1</code>, the cluster leader, that it only gets to use 1 core, in order to leave 2 cores remaining for the rest of the cluster:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;Assigning 1 core for leader node...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;license&#x2F;set-limit?nodeTag&#x3D;A&amp;newAssignedCores&#x3D;1&#39; -X POST -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed ;</span><br></pre></td></tr></table></figure><p>And lastly, I want to join <code>raven2</code> and <code>raven3</code> to the cluster as watcher nodes, allotting only 1 assigned core for each. Again, I discovered these URLs using the Chrome network tools. Note that the url-encoded <code>url</code> parameter uses <code>hostos</code> as the host. I don’t know why but using <code>raven2</code> and <code>raven3</code> didn’t work for me. This is also why my compose file needed to specify the <code>extra_hosts</code> parameter:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;Adding raven2 to the cluster...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;cluster&#x2F;node?url&#x3D;http%3A%2F%2Fhostos%3A8081&amp;watcher&#x3D;true&amp;assignedCores&#x3D;1&#39; -X PUT -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed</span><br><span class="line"></span><br><span class="line">echo &quot;Adding raven3 to the cluster...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;cluster&#x2F;node?url&#x3D;http%3A%2F%2Fhostos%3A8082&amp;watcher&#x3D;true&amp;assignedCores&#x3D;1&#39; -X PUT -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed</span><br></pre></td></tr></table></figure><p>So putting it all together, assuming Docker is already running on my Mac, here is the script that launches my cluster for me:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;Running docker-compose up&quot;</span><br><span class="line">docker-compose up --detach</span><br><span class="line"></span><br><span class="line">sleep 2</span><br><span class="line"></span><br><span class="line">echo &quot;Applying license...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;license&#x2F;activate&#39; -H &#39;Content-Type: application&#x2F;json; charset&#x3D;UTF-8&#39; --data-binary &#39;&#123;LICENSE_JSON&#125;&#39; --compressed ;</span><br><span class="line"></span><br><span class="line">echo &quot;Assigning 1 core for leader node...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;license&#x2F;set-limit?nodeTag&#x3D;A&amp;newAssignedCores&#x3D;1&#39; -X POST -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed ;</span><br><span class="line"></span><br><span class="line">echo &quot;Adding raven2 to the cluster...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;cluster&#x2F;node?url&#x3D;http%3A%2F%2Fhostos%3A8081&amp;watcher&#x3D;true&amp;assignedCores&#x3D;1&#39; -X PUT -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed</span><br><span class="line"></span><br><span class="line">echo &quot;Adding raven3 to the cluster...&quot;</span><br><span class="line">curl &#39;http:&#x2F;&#x2F;localhost:8080&#x2F;admin&#x2F;cluster&#x2F;node?url&#x3D;http%3A%2F%2Fhostos%3A8082&amp;watcher&#x3D;true&amp;assignedCores&#x3D;1&#39; -X PUT -H &#39;Content-Type: application&#x2F;json; charset&#x3D;utf-8&#39; -H &#39;Content-Length: 0&#39; --compressed</span><br></pre></td></tr></table></figure><p>The result is this in the Cluster view in Raven Studio: A 3-node cluster with one Leader node and two Watcher nodes:</p><p><img src="/images/2019/raven-cluster-view.png" alt="RavenDB Cluster View"></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>So that’s how you create a 3-node RavenDB cluster in Docker containers. Hopefully it will be useful to somebody. Probably that somebody will be me 6 months from now when I google it and find this post.</p><p>By no means do I find this perfect. If you can do better, please use the <strong>Edit</strong> button at the top of this post and send me a PR!</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;At Particular we support a bunch of different technologies, so it seems there’s no end to the infrastructure software I might have to use on any given day. SQL Server, RabbitMQ, MongoDB, MySQL, PostgreSQL, MariaDB, even (shudder) &lt;em&gt;Oracle&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I don’t want all that crap installed on my machine. In fact, I don’t want to install infrastructure on my machine again, like…ever.&lt;/p&gt;
&lt;p&gt;So when I needed to work with a RavenDB cluster, I Dockerized it, and here’s how I did it. Maybe it’s not perfect, maybe it could be better? If you think so, let me know! I feel like I stumbled through this, but the result appears to work well.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="RavenDB" scheme="https://www.davidboike.dev/tags/RavenDB/"/>
    
      <category term="Docker" scheme="https://www.davidboike.dev/tags/Docker/"/>
    
      <category term="containers" scheme="https://www.davidboike.dev/tags/containers/"/>
    
  </entry>
  
  <entry>
    <title>Sure, you can just use RabbitMQ</title>
    <link href="https://www.davidboike.dev/2017/12/sure-you-can-just-use-rabbitmq/"/>
    <id>https://www.davidboike.dev/2017/12/sure-you-can-just-use-rabbitmq/</id>
    <updated>2017-12-13T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><i>Note: This post was adapted from an <a rel="canonical noopener" href="https://stackoverflow.com/questions/47060893/what-are-advantages-of-using-nservicebus-rabbitmq-against-pure-rabbitmq" target="_blank">answer I originally posted to a Stack Overflow question</a>.</i></p><p>People ask (frequently) why they need NServiceBus. “I’ve got RabbitMQ and that has built-in Pub/Sub,” they might say. “Isn’t NServiceBus just a wrapper around RabbitMQ? I could probably write that in less than a weekend. After all, <em>how hard could it be?</em></p><p>Well sure, you can definitely just use pure RabbitMQ. I’ll even help you get started writing that wrapper. You just have to keep a couple things in mind.</p><a id="more"></a><p>First you should read <a href="https://www.amazon.com/dp/0321200683/" target="_blank" rel="noopener">Enterprise Integration Patterns</a> cover to cover and make sure you understand it well. It is 736 pages, and a bit dry, but extremely useful information. It also wouldn’t hurt to become an expert in all the peculiarities of RabbitMQ.</p><h2 id="Messaging"><a href="#Messaging" class="headerlink" title="Messaging"></a>Messaging</h2><p>Then you just have to decide how you’ll <a href="https://docs.particular.net/nservicebus/messaging/messages-events-commands" target="_blank" rel="noopener">define messages</a>, how to <a href="https://docs.particular.net/nservicebus/handlers/" target="_blank" rel="noopener">define message handlers</a>, how to <a href="https://docs.particular.net/nservicebus/messaging/send-a-message" target="_blank" rel="noopener">send messages</a> and <a href="https://docs.particular.net/nservicebus/messaging/publish-subscribe/" target="_blank" rel="noopener">publish events</a>. Before you get too far you’ll want a good <a href="https://docs.particular.net/nservicebus/logging/" target="_blank" rel="noopener">logging infrastructure</a>. You’ll need to create a <a href="https://docs.particular.net/nservicebus/serialization/" target="_blank" rel="noopener">message serializer</a> and infrastructure for <a href="https://docs.particular.net/nservicebus/messaging/routing" target="_blank" rel="noopener">message routing</a>. You’ll need to include a bunch of infrastructure-related metadata with the content of each business message. You’ll want to build a message dequeuing strategy that performs well and uses broker connections efficiently, keeping concurrency needs in mind.</p><p>Next you’ll need to figure out how to <a href="https://docs.particular.net/nservicebus/recoverability/" target="_blank" rel="noopener">retry messages automatically when the handling logic fails</a>, but not <em>too many</em> times. You have to have a strategy for dealing with poison messages, so you’ll need to move them aside so your handling logic doesn’t get jammed preventing valid messages from being processed. You’ll need a way to <a href="https://docs.particular.net/servicepulse/" target="_blank" rel="noopener">show those messages that have failed and figure out why</a>, so you can fix the problem. You’ll want some sort of <a href="https://docs.particular.net/servicecontrol/contracts" target="_blank" rel="noopener">alerting options</a> so you know when that happens. It would be nice if that poison message display also showed you where that message came from and what the exception was so you don’t need to go digging through log files. After that you’ll need to be able to <a href="https://docs.particular.net/tutorials/message-replay/" target="_blank" rel="noopener">reroute the poison messages back into the queue to try again</a>. In the event of a bad deployment you might have a lot of failed messages, so it would be really nice if you didn’t have to retry the messages one at a time.</p><h2 id="Transactions"><a href="#Transactions" class="headerlink" title="Transactions"></a>Transactions</h2><p>Since you’re using RabbitMQ, there are no transactions on the message broker, so ghost messages and duplicate entities are very real problems. You’ll need to code all message handling logic with idempotency in mind or your RabbitMQ messages and database entities will begin to get inconsistent. Alternatively you could <a href="https://docs.particular.net/nservicebus/outbox/" target="_blank" rel="noopener">design infrastructure to mimic distributed transactions</a> by storing outgoing messaging operations in your business database and then executing the message dispatch operations separately. That results in duplicate messages (by design) so you’ll need to deduplicate messages as they come in, which means you need well a well-defined <a href="https://docs.particular.net/transports/rabbitmq/message-id-strategy" target="_blank" rel="noopener">strategy for consistent message IDs</a> across your system. Be careful, as anything dealing with transactions and concurrency can be extremely tricky.</p><h2 id="Workflow"><a href="#Workflow" class="headerlink" title="Workflow"></a>Workflow</h2><p>You’ll probably want to do some <a href="https://docs.particular.net/nservicebus/sagas/" target="_blank" rel="noopener">workflow type stuff</a>, where an incoming message starts a process that’s essentially a message-driven state machine. Then you can do things like trigger an action once 2 required messages have been received. You’ll need to design a storage system for that data. You’ll probably also need a way to have delayed messages, so you can do things like the <a href="/2015/06/how-to-build-gmails-undo-send-feature/">buyer’s remorse pattern</a>. RabbitMQ has no way to have an arbitrary delay on a message, so you’ll have to <a href="https://docs.particular.net/transports/rabbitmq/delayed-delivery" target="_blank" rel="noopener">come up with a way to implement that</a>.</p><p>You’ll probably want some <a href="https://docs.particular.net/nservicebus/operations/metrics" target="_blank" rel="noopener">metrics</a> and <a href="https://docs.particular.net/nservicebus/operations/performance-counters" target="_blank" rel="noopener">performance counters</a> on this system to know how it’s performing. You’ll want some way to be able to <a href="https://docs.particular.net/nservicebus/testing/" target="_blank" rel="noopener">have tests on your message handling logic</a>, so if you need to swap out some dependencies to make that work you might want to <a href="https://docs.particular.net/nservicebus/dependency-injection/" target="_blank" rel="noopener">integrate a dependency injection framework</a>.</p><h2 id="Documentation"><a href="#Documentation" class="headerlink" title="Documentation"></a>Documentation</h2><p>Because these systems are decentralized by nature it can get pretty difficult to accurately picture what your system looks like. If you <a href="https://docs.particular.net/nservicebus/operations/auditing" target="_blank" rel="noopener">send a copy of every message to a central location</a>, you can <a href="https://docs.particular.net/servicecontrol/" target="_blank" rel="noopener">write some code</a> to stitch together all the message conversations, and then you can use that data to build message flow diagrams, sequence diagrams, etc. This kind of living documentation based on live data can be critical for explaining things to managers or figuring out why a process isn’t working as expected.</p><p>Speaking of documentation, make sure you <a href="https://docs.particular.net/nservicebus/" target="_blank" rel="noopener"><strong>write a whole lot of it</strong></a> for your message queue wrapper, otherwise it will be pretty difficult for other developers to help you maintain it. Of if someone else on your team is writing it, you’ll be totally screwed when they get a different job and leave the company. You’re also going to want a ton of unit tests on the RabbitMQ wrapper you’ve built. Infrastructure code like this should be rock-solid. You don’t want losing a message to result in lost sales or anything like that.</p><p>So if you keep those few things in mind, you can totally use pure RabbitMQ without NServiceBus.</p><p>Hopefully, when you’re done, your boss won’t decide that you need to switch from <a href="https://docs.particular.net/transports/rabbitmq/" target="_blank" rel="noopener">RabbitMQ</a> to <a href="https://docs.particular.net/transports/azure-service-bus/" target="_blank" rel="noopener">Azure Service Bus</a> or <a href="https://docs.particular.net/transports/sqs/" target="_blank" rel="noopener">Amazon SQS</a>.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;i&gt;Note: This post was adapted from an &lt;a rel=&quot;canonical noopener&quot; href=&quot;https://stackoverflow.com/questions/47060893/what-are-advantages-of-using-nservicebus-rabbitmq-against-pure-rabbitmq&quot; target=&quot;_blank&quot;&gt;answer I originally posted to a Stack Overflow question&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;People ask (frequently) why they need NServiceBus. “I’ve got RabbitMQ and that has built-in Pub/Sub,” they might say. “Isn’t NServiceBus just a wrapper around RabbitMQ? I could probably write that in less than a weekend. After all, &lt;em&gt;how hard could it be?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well sure, you can definitely just use pure RabbitMQ. I’ll even help you get started writing that wrapper. You just have to keep a couple things in mind.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="RabbitMQ" scheme="https://www.davidboike.dev/tags/RabbitMQ/"/>
    
  </entry>
  
  <entry>
    <title>Introduction to NServiceBus</title>
    <link href="https://www.davidboike.dev/2017/03/introduction-to-nservicebus/"/>
    <id>https://www.davidboike.dev/2017/03/introduction-to-nservicebus/</id>
    <updated>2017-03-17T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<div style="float:right;margin:1em;"><img src="/images/learning-nservicebus-cover-small.png" style="width:175px;height:213px;vertical-align:text-top;" /><img src="/images/learning-nservicebus-2ndEd.jpg" style="border:solid 1px #999;width:162px;height:200px;vertical-align:text-top;box-shadow:6px 6px 5px #ddd" /></div><p>If you’re looking at my blog right now (hint: you are) then you might already know that I am the author of <a href="https://www.packtpub.com/application-development/learning-nservicebus" target="_blank" rel="noopener"><em>Learning NServiceBus</em></a> which focused on NServiceBus 4, and <a href="https://www.packtpub.com/application-development/learning-nservicebus-second-edition" target="_blank" rel="noopener"><em>Learning NServiceBus - Second Edition</em></a> which focused on NServiceBus 5.</p><p>Now NServiceBus 6 has been released, and with the change to a fully async API, quite a bit has changed. In fact, enough has changed that if you tried to use my latest book, which is called <em>Learning NServiceBus</em>, to actually, you know, <em>learn NServiceBus</em>, it might not be the greatest experience in the world.</p><p>The problem is, writing books sucks. A lot. So I’m not doing that anymore.</p><a id="more"></a><h2 id="What’s-wrong-with-books"><a href="#What’s-wrong-with-books" class="headerlink" title="What’s wrong with books?"></a>What’s wrong with books?</h2><p>To publish a book you spend countless hours, writing, editing, rewriting, editing, proofing, proofing some more, then proofing again. By the time you get done and you get the physical copy of the book in your hands, you really don’t want to look at that content probably ever again. So when the first version of my book was published, and my copies arrived in the mail, here’s what I did: I cracked one open <em>just to make sure there were actually words in it</em>, and then I closed it and put every single copy on my bookshelf.</p><p>I seriously did not want to look at those words one more time. I stopped.</p><p>But NServiceBus didn’t stop. NServiceBus just kept going. By the time my book was published, NServiceBus 4 had already been out for a month and a half. Within another two months, NServiceBus 4.1 was out. Then 4.2, and so on. Each release made my book a little more wrong.</p><p>Just a bit over a year after I published, NServiceBus 5 was released, making my book <em>completely wrong</em>. I figured that, at the very least, updating the book to a new major version would require much less effort than went into the first version.</p><p>Well, I was wrong. Granted, I didn’t just update the code samples and call it a day, because I wanted the book to be the best it could possibly be. Although I didn’t keep track of hours, I’m fairly certain that the updated took every bit as much effort as the one that came before.</p><p>And here we are again. Now that NServiceBus 6 has been released, the second book is also completely wrong. Putting words to <em>actual paper</em> is just no good way to actually educate people how to use a software product that’s changing and evolving.</p><p>So now that I have the advantage of working direclty for the <a href="https://particular.net" target="_blank" rel="noopener">makers of NServiceBus</a>, we’ve decided to go in a bit of a different direction. I’ve been working on a new <a href="https://docs.particular.net/tutorials/intro-to-nservicebus/" target="_blank" rel="noopener">Introduction to NServiceBus</a> tutorial, hosted within our documentation site. That means we’ll be able to keep the tutorial up-to-date and relevant using the same tech we use for our documentation and samples, in a way a paper book could never hope to accomplish.</p><h2 id="The-new-tutorial"><a href="#The-new-tutorial" class="headerlink" title="The new tutorial"></a>The new tutorial</h2><p>I designed the new tutorial as a series of 5 lessons, each designed to be accomplished in a half hour or less. The tutorial teaches the basics of working with NServiceBus but a lot more than that as well. It also covers the messaging theory to help you get your NServiceBus projects off on the right foot.</p><p>Each lesson contains an exercise that guides you through each step of creating a sample messaging system in the retail/ecommerce domain. By the end of the tutorial, you’ll have 4 messaging endpoints all exchanging messages with each other, and also learn how to use the tools in the Particular Service Platform to replay messages when the system suffers from temporary failures.</p><p>When complete, the system you build will look something like this:</p><p><a href="https://docs.particular.net/tutorials/intro-to-nservicebus/" target="_blank" rel="noopener"><img src="/images/intro-to-nsb-tutorial-diagram.svg" style="border:solid 1px #ccc;padding:1em;" /></a></p><p>Sound interesting? If so, give the <a href="https://docs.particular.net/tutorials/intro-to-nservicebus/" target="_blank" rel="noopener"><strong>Introduction to NServiceBus</strong></a> tutorial a try, and then ping me on Twitter <a href="https://twitter.com/DavidBoike" target="_blank" rel="noopener">@DavidBoike</a> to let me know what you think.</p><p>The first tutorial is introductory, and introduces the concepts of messaging, sending messages, and using Publish/Subscribe. I would love to build additional tutorials in the future that will extend on this, showing how to organize NServiceBus projects, centralize repeated configuration, and show how to get the most out of NServiceBus Sagas. Let me know if you’d be interested in that as well!</p>]]></content>
    
    <summary type="html">
    
      &lt;div style=&quot;float:right;margin:1em;&quot;&gt;
&lt;img src=&quot;/images/learning-nservicebus-cover-small.png&quot; style=&quot;width:175px;height:213px;vertical-align:text-top;&quot; /&gt;
&lt;img src=&quot;/images/learning-nservicebus-2ndEd.jpg&quot; style=&quot;border:solid 1px #999;width:162px;height:200px;vertical-align:text-top;box-shadow:6px 6px 5px #ddd&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;If you’re looking at my blog right now (hint: you are) then you might already know that I am the author of &lt;a href=&quot;https://www.packtpub.com/application-development/learning-nservicebus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;Learning NServiceBus&lt;/em&gt;&lt;/a&gt; which focused on NServiceBus 4, and &lt;a href=&quot;https://www.packtpub.com/application-development/learning-nservicebus-second-edition&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;Learning NServiceBus - Second Edition&lt;/em&gt;&lt;/a&gt; which focused on NServiceBus 5.&lt;/p&gt;
&lt;p&gt;Now NServiceBus 6 has been released, and with the change to a fully async API, quite a bit has changed. In fact, enough has changed that if you tried to use my latest book, which is called &lt;em&gt;Learning NServiceBus&lt;/em&gt;, to actually, you know, &lt;em&gt;learn NServiceBus&lt;/em&gt;, it might not be the greatest experience in the world.&lt;/p&gt;
&lt;p&gt;The problem is, writing books sucks. A lot. So I’m not doing that anymore.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="announcements" scheme="https://www.davidboike.dev/tags/announcements/"/>
    
      <category term="book" scheme="https://www.davidboike.dev/tags/book/"/>
    
      <category term="career" scheme="https://www.davidboike.dev/tags/career/"/>
    
  </entry>
  
  <entry>
    <title>The code isn&#39;t everything</title>
    <link href="https://www.davidboike.dev/2017/02/the-code-isnt-everything/"/>
    <id>https://www.davidboike.dev/2017/02/the-code-isnt-everything/</id>
    <updated>2017-02-25T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Since I work from home, I make it a point to go out for lunch at least once per week. It helps to keep me from going a bit nuts staying in the house all day, especially during cold, snowy Minnesota winters.</p><p>This week I visited a local fast food joint that happens to have a new loyalty program via a mobile app. You visit eight times and you earn a free combo meal. To log a visit, you tap a button in the app, and it displays a QR code that they scan at the register, or if you forget, the app can scan a barcode on your receipt. I’m … <a href="/2010/05/qr-codes-the-lamest-thing-to-never-reach-critical-mass/">not exactly a fan of QR codes</a>, but this seems like a pretty good use for them. I had never seen their system in action so I decided to check it out.</p><p>So after placing my order, I ask the employee at the register if he can scan the QR code in the app, and he looked at me like I was born on Mars.</p><a id="more"></a><p>I looked around the restaurant. On every table, there’s a table tent advertising the new app. There’s a giant, backlit poster right next to the ordering counter advertising the new app. This is clearly a pretty big push for them.</p><p><em>But he had no idea what I was talking about.</em></p><p>So the poor employee at the register shouts to his manager, who shouts back to do it manually. I didn’t quite hear the exchange, but as I’m watching the screen, I see that he’s trying to give me a free meal. So I protested, as I wasn’t about to accept a free meal I hadn’t earned. I was also starting to grow a little embarrassed as it was lunchtime and there were hungry people waiting to order behind me. I asked him to reverse the discount, and I would just pay for the meal normally and scan the receipt barcode later.</p><p>Well that didn’t work either. Even though the barcode was printed on the receipt very clearly, the app refused to scan it. I had to enter the numeric code below the barcode manually.</p><p>This got me thinking about the things people – <em>especially software developers</em> – completely forget about in software projects.</p><p>It’s far too easy to fall into the trap of writing code for code’s sake, and forgetting that for the code to be successful in solving a business problem, so much more is usually needed. You need usability testing, good documentation, marketing, training, or a hundred other potential other things for software to really be successful.</p><p>In the case of my lunch visit, a little bit of employee training would have gone a long way. Hopefully this was just an isolated incident of a new employee just learning the ropes and some miscommunication. Otherwise, a lot of money spent on app development was a wasted investment. </p><p>So just remember, the code isn’t everything.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Since I work from home, I make it a point to go out for lunch at least once per week. It helps to keep me from going a bit nuts staying in the house all day, especially during cold, snowy Minnesota winters.&lt;/p&gt;
&lt;p&gt;This week I visited a local fast food joint that happens to have a new loyalty program via a mobile app. You visit eight times and you earn a free combo meal. To log a visit, you tap a button in the app, and it displays a QR code that they scan at the register, or if you forget, the app can scan a barcode on your receipt. I’m … &lt;a href=&quot;/2010/05/qr-codes-the-lamest-thing-to-never-reach-critical-mass/&quot;&gt;not exactly a fan of QR codes&lt;/a&gt;, but this seems like a pretty good use for them. I had never seen their system in action so I decided to check it out.&lt;/p&gt;
&lt;p&gt;So after placing my order, I ask the employee at the register if he can scan the QR code in the app, and he looked at me like I was born on Mars.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
  </entry>
  
  <entry>
    <title>Inbox Few - How I wrangle my email</title>
    <link href="https://www.davidboike.dev/2017/01/inbox-few-how-i-wrangle-my-email/"/>
    <id>https://www.davidboike.dev/2017/01/inbox-few-how-i-wrangle-my-email/</id>
    <updated>2017-01-31T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>I used to be a practitioner of Inbox Infinite. The incoming messages would just land at the top of my inbox, and as I scrolled down, the date of the email would slowly get closer and closer to the date when I originally opened my Gmail account.</p><p>Let’s just say I could scroll down a long damn way.</p><p>My primary method of determining if something was <em>handled</em> was whether or not it was read. If I needed to look at it later, mark it as unread. It worked out alright, as a software developer I didn’t get that much email to start with, and work generally happened only during work hours.</p><p>When I joined <a href="https://particular.net" target="_blank" rel="noopener">Particular Software</a> it became quickly apparent that how I managed my email was going to have to change.</p><a id="more"></a><p>Particular Software is a 100% dispersed organization with no “home office.” We have people in North America, Europe, and Australia. The sun never goes down on us, and so throughout most of the week, during every hour of the day, someone <em>somewhere</em> is working.</p><p>We also do all our work in GitHub, from work on code in our public repositories, right down to discussing changes to our parental leave policy.</p><p>This results in <em>lots of notification emails</em>. The problem isn’t just dealing with them in my inbox. I also need to curtail the sheer amount of them that are archived so that I have a remote chance of finding anything <em>else</em> in my email.</p><p>So here’s the system I’ve come up with to wrangle my email. I’m not necessarily saying it will work for you, but I’ve found it works for me.</p><h2 id="Basic-setup-and-filters"><a href="#Basic-setup-and-filters" class="headerlink" title="Basic setup and filters"></a>Basic setup and filters</h2><p>I like to use a native Mail client, not the Gmail web interface, especially since I prefer to blend my personal and work email together into one experience. I actually really like Apple’s built-in Mail app when I’m on my MacBook Pro.</p><p>So the first thing I had to turn off is Gmail’s tabbed email system that separates Social/Promotions/Updates/Forums content into different tabs in the Gmail web interface, because it doesn’t do any good from a native client.</p><p>Instead, I set up filters to map this kind of stuff to labels/folders in Gmail. You can take advantage of Gmail’s intelligence to identify these messages and direct them where you want.</p><p>Using the searches <code>category:social</code> and <code>category:promotions</code>, you can create Gmail filters to move these to separate labels, skip the inbox, and never mark it as important. I direct these to labels called <strong>Notifications</strong> and <strong>Notify</strong>, and this cleans up an amazing amount of what would otherwise land in my Inbox.</p><p>I frequently skim these folders once per day, and mark them read en masse.</p><p>I also have another Gmail filter for advertising that somehow makes it through Gmail’s algorithms. The search is along the lines of <code>from:(A OR B OR C OR ...)</code> and I add to it as necessary. Honestly though, I can’t remember the last time I amended that filter. Gmail seems to be really good at that.</p><p>Lastly, I have a filter for <code>from:notifications@github.com</code> that applies a <strong>GitHub</strong> label, but here I do <em>not skip the Inbox</em>. More on this later.</p><h2 id="Inbox-Few"><a href="#Inbox-Few" class="headerlink" title="Inbox Few"></a>Inbox Few</h2><p>It didn’t take long after joining Particular to realize that using Read/Unread status on a message to keep track of what I needed to do with it would not work. I just get too many GitHub notifications.</p><p>Particular Software creates a messaging product called NServiceBus, so it made sense to treat my Inbox as a persistent queue. If it’s in my inbox, that means there’s something there for me to do. If there isn’t anything for me to do, it shouldn’t be in my inbox.</p><p>I tried Inbox Zero, but found it impossible to get to zero. I’m not sure if that would even be a good thing. If I didn’t have anything to do in my persistent queue, that might be bad for job security?</p><p>In any case, I’ve settled on Inbox Few. When I seriously triage my email, I try to tackle small, achievable tasks that I can quickly dispatch, in any order that happens to suit my fancy. Sometimes, this is just typing a quick response on a GitHub issue to voice my opinion. Other times it involves a little more work, but never an hour-long task. If something is too big of a time suck, I leave it alone. It will probably need its own period of undivided attention anyway, so I leave it until I can devote that time to it.</p><p>I keep going at this roughly until the scrollbar on my message list disappears and I can see all the remaining messages in one view. This is a highly subjective measure of course. I have my email client set up in the Folders On Left + Message List On Top + Message Preview On Bottom configuration, and I don’t maximize my email client to the whole screen. At this moment, I have 11 items in my Inbox and I’m feeling very good about that.</p><p>Once I get down to roughly this scale, what’s left can fit in my head and I can start to make better value judgments about what Big Ticket task is truly worth my time and then start on that. Usually I’ll also take a scan of the <a href="https://github.com/issues/assigned" target="_blank" rel="noopener">GitHub issues assigned to me</a> and take that into consideration.</p><p>I also try, whenever possible, to eliminate items where I’m waiting for something. In most GitHub issues, I trust there will be some further activity from one of my teammates that will “wake me up” with a new notification. However, in some situations that isn’t likely to happen, so in those cases I’m quick to use <a href="https://get.slack.help/hc/en-us/articles/208423427-Set-a-reminder" target="_blank" rel="noopener">Slack’s reminder feature</a> to point me back to a specific issue URL at some time in the future.</p><p>I feel the biggest hole in this process is currently when I expect that someone else will reply to an issue, giving me my wake-up call, but it never happens. This is when I run the risk of forgetting about an issue for an extended period of time, especially if I’m not assigned to it. I’m currently not sure how to address that.</p><h2 id="Cleaning-up-cruft"><a href="#Cleaning-up-cruft" class="headerlink" title="Cleaning up cruft"></a>Cleaning up cruft</h2><p>Sometimes I’ll want to find something useful in my email, and for that I have to rely upon search. It really doesn’t help if there are hundreds of GitHub notifications, ads, or other cruft gumming up everything, turning my signal-to-noise ratio to utter crap.</p><p>But on the other hand, I don’t want to get rid of these emails immediately, especially the GitHub notifications. After I archive a message, if someone later responds on that issue, it raises the entire thread back into my Inbox. This way I can see most of the context right there in my email client, and I don’t have to waste half of my day launching into a GitHub browser window only to find out which issue that was anyway.</p><p>To deal with this, I use a <a href="https://www.google.com/script/start/" target="_blank" rel="noopener">Google Apps Script</a> to delete email conversations out of Gmail after a certain period.</p><p>These scripts are really easy to make. They are stored in Google Drive, so you just go to the link above and click the <strong>Start Scripting</strong> button. Save it in a Drive folder and you can set it up to run once per night without having to hassle with a crontab or anything.</p><p>Here’s my script that archives messages with the <em>Notify</em> and <em>Advertising</em> labels after 30 days, which is plenty long enough to retrieve a coupon the day my wife wants to go shopping. It also archives messages in the <strong>GitHub</strong> label (remember that from earlier?) after 180 days.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">function archive(label, days, trash) &#123;</span><br><span class="line">  var query &#x3D; &#39;label:&#39; + label + &#39; is:read older_than:&#39; + days + &#39;d&#39;;</span><br><span class="line">  var emails &#x3D; GmailApp.search(query, 0, 500);</span><br><span class="line">  Logger.log(&#39;Folder &quot;&#39; + label + &#39;&quot;: retrieved &#39; + parseInt(emails.length, 0) + &#39; emails to &#39; + (trash ? &#39;delete.&#39; : &#39;archive.&#39;));</span><br><span class="line">  for(var i&#x3D;0; i&lt;emails.length; i +&#x3D; 100) &#123;</span><br><span class="line">    var batch &#x3D; emails.slice(i, i + 100);</span><br><span class="line">    Logger.log(&#39;...Processed &#39; + parseInt(batch.length, 0) + &#39; threads.&#39;);</span><br><span class="line">    if(trash) &#123;</span><br><span class="line">      GmailApp.moveThreadsToTrash(batch);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      GmailApp.moveThreadsToArchive(batch);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function main() &#123;</span><br><span class="line">  archive(&#39;notify&#39;, 30, true);</span><br><span class="line">  archive(&#39;advertising&#39;, 30, true);</span><br><span class="line">  archive(&#39;github&#39;, 180, true);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Through the clock-ish icon in the script editor, you can schedule this script to run the <code>main</code> function on a scheduled basis. Mine runs daily between midnight and 1am in order to make sure the batch size doesn’t exceed the 500 allowed by the script. (If you try to make that larger, the script can fail.) When I originally created it, I had to run it manually many <em>many</em> times to get through all the junk in my previous Inbox Infinite.</p><p>You may notice the script has an option for archiving messages that I’m not currently using. If you, like me, are currently at Inbox Infinite, you can apply a label to all the messages in your Inbox, and then use that label to archive messages until you get from Inbox Infinite down to at least Inbox Semi-Recent.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>As I said at the outset, this may not work for everybody, but it seems to be working pretty well for me. I always have a persistent task list available to me, which I can manage from my laptop or from my phone in a similar fashion. It also allows me to do high-level triage of “Yep, got it” type things easily from my phone during downtime when I would be bored anyway.</p><p>Some have suggested I use Google Inbox instead of all this, especially because the “Snooze” feature might help with the problem of getting a message out of the way but coming back to it later, whether or not anyone has replied.</p><p>My problem with that is that I like email and how it’s set up, I just want it to work for me. I don’t see the need to totally reimagine it, nor do I want to give up my native client for a webapp, or have to try out a bunch of native Inbox clients like <a href="http://www.boxyapp.co/" target="_blank" rel="noopener">Boxy</a>.</p><p>Basically, what I’ve got now seems to be working out fine. Why would I mess with that?</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I used to be a practitioner of Inbox Infinite. The incoming messages would just land at the top of my inbox, and as I scrolled down, the date of the email would slowly get closer and closer to the date when I originally opened my Gmail account.&lt;/p&gt;
&lt;p&gt;Let’s just say I could scroll down a long damn way.&lt;/p&gt;
&lt;p&gt;My primary method of determining if something was &lt;em&gt;handled&lt;/em&gt; was whether or not it was read. If I needed to look at it later, mark it as unread. It worked out alright, as a software developer I didn’t get that much email to start with, and work generally happened only during work hours.&lt;/p&gt;
&lt;p&gt;When I joined &lt;a href=&quot;https://particular.net&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Particular Software&lt;/a&gt; it became quickly apparent that how I managed my email was going to have to change.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="Gmail" scheme="https://www.davidboike.dev/tags/Gmail/"/>
    
  </entry>
  
  <entry>
    <title>How to build Gmail&#39;s &quot;Undo Send&quot; feature</title>
    <link href="https://www.davidboike.dev/2015/06/how-to-build-gmails-undo-send-feature/"/>
    <id>https://www.davidboike.dev/2015/06/how-to-build-gmails-undo-send-feature/</id>
    <updated>2015-06-30T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>The other day I read this article about how Gmail will <a href="http://mashable.com/2015/06/23/undo-send-gmail/" target="_blank" rel="noopener">finally let you “Undo Send” emails you wish you didn’t send</a>.</p><p>Really Google? <em>Really?</em> What took you so long? I mean, I know you’ve been very busy shuttering Google Reader and all that, but offering the ability to undo sending an email within 30 seconds is actually pretty easy to build.</p><p>At least, it is with <a href="http://particular.net/nservicebus" target="_blank" rel="noopener">NServiceBus</a>, and specifically, using an NServiceBus <a href="http://docs.particular.net/nservicebus/sagas/" target="_blank" rel="noopener">Saga</a>. I’ll show you how.</p><p>“Undo Send” is really just a specific case of a much more general pattern I’ll call the buyer’s remorse pattern.</p><a id="more"></a><h2 id="Buyer’s-remorse-pattern"><a href="#Buyer’s-remorse-pattern" class="headerlink" title="Buyer’s remorse pattern"></a>Buyer’s remorse pattern</h2><p><a href="https://en.wikipedia.org/wiki/Buyer%27s_remorse" target="_blank" rel="noopener">In real life</a>, we might get buyer’s remorse when we buy an expensive car and then realize just how long we’re going to have to make expensive payments on it. In software, we’re not talking about a purchase - we’re really referring to any action that cannot be easily undone. Sending emails fall squarely within this category of problems, along with charging credit cards, which can cause <em>real</em> buyer’s remorse.</p><p>Using the buyer’s remorse pattern simply means that instead of immediately sending the email, the software will wait a certain amount of time first, in case the user thinks better about what they just did and wants to back out.</p><p>The time delay is the tricky part of implementing buyer’s remorse, but with NServiceBus, it becomes easy.</p><h2 id="Step-by-step"><a href="#Step-by-step" class="headerlink" title="Step-by-step"></a>Step-by-step</h2><p>I won’t cover how to do buyer’s remorse in your UI. I would assume your user would click the Send button, and then you’d display an alert bar saying “Your message has been sent. (Click to undo)”. For the sake of this example, let’s assume this is a single-page application, and both the “Send” button and “Click to undo” would fire a request to a REST API that could send NServiceBus commands to a back-end service.</p><h3 id="Message-definitions"><a href="#Message-definitions" class="headerlink" title="Message definitions"></a>Message definitions</h3><p>Here’s what the commands would look like:</p><pre><code>public class SendEmail : ICommand{    public Guid MessageId { get; set; }    public EmailDetails MessageDetails { get; set; }}public class UndoSendEmail : ICommand{    public Guid MessageId { get; set; }}</code></pre><p>The <code>MessageId</code> is just a <code>Guid</code> that serves as a unique identifier for the message being sent. That way when we “send” the message, and then subsequently “undo send”, we know we’re talking about the same message.</p><p>I’ll leave the <code>EmailDetails</code> class up to you. Actually, that’s the <em>real</em> hard part about sending an email. Classes like <a href="https://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage.aspx" target="_blank" rel="noopener"><code>System.Net.MailMessage</code></a> and <a href="https://msdn.microsoft.com/en-us/library/system.net.mail.mailaddress.aspx" target="_blank" rel="noopener"><code>System.Net.MailAddress</code></a> are chock full of get-only properties and other gross stuff that don’t make them good candidates to include in messages, plus they probably contain way more information than you really need for your use case anyway. So just create your own containing only get/set properties for the details you need.</p><p>We’re also going to need a class to represent the timeout message. This is far from complex:</p><pre><code>public class UndoSendTimeout { }</code></pre><h3 id="Basic-saga-structure"><a href="#Basic-saga-structure" class="headerlink" title="Basic saga structure"></a>Basic saga structure</h3><p>We start with the scaffolding of an NServiceBus Saga that handles these messages. An NServiceBus Saga is really just a collection of message handling methods that store some shared state in a database between messages.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">public class UndoSendPolicy : Saga&lt;UndoSendPolicyData&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;SendEmail&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;UndoSendEmail&gt;,</span><br><span class="line">    IHandleTimeouts&lt;UndoSendTimeout&gt;</span><br><span class="line">&#123;</span><br><span class="line">    protected override void ConfigureHowToFindSaga(SagaPropertyMapper&lt;UndoSendPolicyData&gt; mapper)</span><br><span class="line">    &#123;</span><br><span class="line">        mapper.ConfigureMapping&lt;SendEmail&gt;(msg &#x3D;&gt; msg.MessageId)</span><br><span class="line">            .ToSaga(data &#x3D;&gt; data.MessageId);</span><br><span class="line">        mapper.ConfigureMapping&lt;UndoSendEmail&gt;(msg &#x3D;&gt; msg.MessageId)</span><br><span class="line">            .ToSaga(data &#x3D;&gt; data.MessageId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    public void Handle(SendEmail message)</span><br><span class="line">    &#123;</span><br><span class="line">        &#x2F;&#x2F; TODO</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void Handle(UndoSendEmail message)</span><br><span class="line">    &#123;</span><br><span class="line">        &#x2F;&#x2F; TODO</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    public void Timeout(UndoSendTimeout state)</span><br><span class="line">    &#123;</span><br><span class="line">        &#x2F;&#x2F; TODO</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>For the moment, let’s ignore <code>UndoSendPolicyData</code>, the class that represents the state stored in the database between messages.</p><p>You may find it odd that both the <code>SendEmail</code> command and the <code>UndoSendEmail</code> command are both implemented as <code>IAmStartedByMessages&lt;TMessage&gt;</code> rather than the alternative <code>IHandleMessages&lt;TMessage&gt;</code>, when clearly if the <code>UndoSendEmail</code> occurs, it will happen later in time. It’s important to remember that in an eventually consistent, asynchronous system, it’s possible that <code>SendEmail</code> could be delayed for some reason and <code>UndoSendEmail</code> might actually arrive first!</p><p>Because of this possibility, it’s best to use <code>IHandleMessages&lt;TMessage&gt;</code> <em>only for messages sent by the Saga itself!</em></p><p>The last thing to look at is the <code>ConfigureHowToFindSaga</code> method, which teaches the persistence how to look for saga data for each incoming message. For both message types, this is saying “Find a property in the message called <code>MessageId</code>, and try to match that up to some saga data in the database with a matching <code>MessageId</code>.” If none is found, and the message is an <code>IAmStartedByMessages&lt;TMessage&gt;</code> then the Saga will create new data for us.</p><p>Now let’s get back to the <code>UndoSendPolicyData</code> class that will store our saga data for us while we’re waiting for the timeout period.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public class UndoSendPolicyData : ContainSagaData</span><br><span class="line">&#123;</span><br><span class="line">    public Guid MessageId &#123; get; set; &#125;</span><br><span class="line">    public EmailDetails MessageDetails &#123; get; set; &#125;</span><br><span class="line">    public bool UndoSend &#123; get; set; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We store the <code>MessageId</code>, so that our saga-finding mapper can match it up later. We also store the message details received from the <code>SendEmail</code> command. Lastly, an <code>UndoSend</code> indicator lets us know if an <code>UndoSendEmail</code> command has been received.</p><h3 id="Message-handlers"><a href="#Message-handlers" class="headerlink" title="Message handlers"></a>Message handlers</h3><p>Now let’s start to implement our message handlers, starting with the handler for the <code>SendEmail</code> command:</p><pre><code>public void Handle(SendEmail message){    this.Data.MessageId = message.MessageId;    this.Data.MessageDetails = message.MessageDetails;    this.RequestTimeout&lt;UndoSendTimeout&gt;(TimeSpan.FromSeconds(30));}</code></pre><p>When the Saga receives its very first message, the saga data (in <code>this.Data</code>) will be uninitialized, so it’s very important to fill it with information from the incoming command. We don’t actually want to send the email yet, so we request a timeout from the Saga infrastructure so that we can get a <code>UndoSendTimeout</code> reminder in 30 seconds.</p><p>Next, let’s handle the <code>UndoSendEmail</code> command:</p><pre><code>public void Handle(UndoSendEmail message){    this.Data.MessageId = message.MessageId;    this.Data.UndoSend = true;}</code></pre><p>We don’t actually <em>do</em> much of anything here either. We still initialize the <code>MessageId</code> property, because remember that it’s possible for <code>UndoSendEmail</code> to arrive first, meaning the saga data could be uninitialized for this handler as well.</p><p>Lastly, we implement the handler for the <code>UndoSendTimeout</code> message:</p><pre><code>public void Timeout(UndoSendTimeout state){    if (this.Data.UndoSend == false)    {        SendEmail(this.Data.MessageDetails);    }    this.MarkAsComplete();}</code></pre><p>If we haven’t cancelled the send, then we call a method that will build the <code>MailMessage</code> from our message details and dispatch it to the SMTP server. Then, in either case, we call <code>MarkAsComplete()</code>, which will remove the saga data from the database. If the Saga did receive an <code>UndoSendEmail</code> command then the message just goes away, no harm, no foul.</p><p>So remember that the messages could arrive in any order. This means that one of the following will happen:</p><ol><li><code>SendEmail</code> arrives, and the user does not undo, so the email is sent 30 seconds later.</li><li><code>SendEmail</code> arrives, and the <code>UndoSendEmail</code> command arrives a bit later, so when the timeout fires, the email is not sent.</li><li><code>SendEmail</code> is delayed and <code>UndoSendEmail</code> arrives first. The saga data is created with <code>UndoSend == true</code>. When <code>SendEmail</code> arrives, it dutifully requests the timeout. 30 seconds later, the timeout is received, and because <code>UndoSend</code> is set, the email is not sent.</li><li><code>SendEmail</code> arrives, and the user stares at their screen for 29 seconds, then finally sends <code>UndoSendEmail</code> too late. The timeout fires, sends the email, and removes the saga data. Finally, the <code>UndoSendEmail</code> arrives, and because it is also an <code>IAmStartedBy&lt;T&gt;</code> message, it recreates the saga data! Oops!</li></ol><p>Scenario #4 isn’t really what we had in mind, but it illustrates that we always need to consider what will happen if messages arrive out of their expected order. There are a lot of ways to handle this, including an additional timeout to delay cleaning up the saga for a much longer period (perhaps 24 hours), or a timestamp added to the <code>UndoSendEmail</code> command so that it could be effectively ignored if old enough. I’ll leave implementing one of those as an exercise for the reader.</p><h2 id="Full-code"><a href="#Full-code" class="headerlink" title="Full code"></a>Full code</h2><p>Here is the full code for the <code>UndoSendPolicy</code> saga, for those who prefer to see everything at once:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">public class UndoSendPolicy : Saga&lt;UndoSendPolicyData&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;SendEmail&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;UndoSendEmail&gt;,</span><br><span class="line">    IHandleTimeouts&lt;UndoSendTimeout&gt;</span><br><span class="line">&#123;</span><br><span class="line">    protected override void ConfigureHowToFindSaga(SagaPropertyMapper&lt;UndoSendPolicyData&gt; mapper)</span><br><span class="line">    &#123;</span><br><span class="line">        mapper.ConfigureMapping&lt;SendEmail&gt;(msg &#x3D;&gt; msg.MessageId)</span><br><span class="line">            .ToSaga(data &#x3D;&gt; data.MessageId);</span><br><span class="line">        mapper.ConfigureMapping&lt;UndoSendEmail&gt;(msg &#x3D;&gt; msg.MessageId)</span><br><span class="line">            .ToSaga(data &#x3D;&gt; data.MessageId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void Handle(SendEmail message)</span><br><span class="line">    &#123;</span><br><span class="line">        this.Data.MessageId &#x3D; message.MessageId;</span><br><span class="line">        this.Data.MessageDetails &#x3D; message.MessageDetails;</span><br><span class="line"></span><br><span class="line">        this.RequestTimeout&lt;UndoSendTimeout&gt;(TimeSpan.FromSeconds(30));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void Handle(UndoSendEmail message)</span><br><span class="line">    &#123;</span><br><span class="line">        this.Data.MessageId &#x3D; message.MessageId;</span><br><span class="line">        this.Data.UndoSend &#x3D; true;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void Timeout(UndoSendTimeout state)</span><br><span class="line">    &#123;</span><br><span class="line">        if (this.Data.UndoSend &#x3D;&#x3D; false)</span><br><span class="line">        &#123;</span><br><span class="line">            SendEmail(this.Data.MessageDetails);</span><br><span class="line">        &#125;</span><br><span class="line">        this.MarkAsComplete();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private void SendEmail(EmailDetails email)</span><br><span class="line">    &#123;</span><br><span class="line">        &#x2F;&#x2F; Dispatch the message to the SMTP server</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class UndoSendPolicyData : ContainSagaData</span><br><span class="line">&#123;</span><br><span class="line">    public Guid MessageId &#123; get; set; &#125;</span><br><span class="line">    public EmailDetails MessageDetails &#123; get; set; &#125;</span><br><span class="line">    public bool UndoSend &#123; get; set; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class SendEmail : ICommand</span><br><span class="line">&#123;</span><br><span class="line">    public Guid MessageId &#123; get; set; &#125;</span><br><span class="line">    public EmailDetails MessageDetails &#123; get; set; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class UndoSendEmail : ICommand</span><br><span class="line">&#123;</span><br><span class="line">    public Guid MessageId &#123; get; set; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class EmailDetails</span><br><span class="line">&#123;</span><br><span class="line">    &#x2F;&#x2F; Up to you - create what you need for your use case</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class UndoSendTimeout &#123; &#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Buyer’s remorse is a pattern to deal with operations that otherwise cannot be undone by delaying them for a short time while waiting for cancellation. With it we can implement Google’s Undo Send feature, but there are many more uses for it.</p><p>Credit card charges are technically reversible, although doing so is messy, as it requires a charge reversal that would appear on the customer’s statement. It’s a lot cleaner to prevent the credit card from having ever been charged by introducing the time delay with the buyer’s remorse pattern.</p><p>In fact, with e-commerce use cases, the buyer’s remorse pattern can get a little more interesting. It should always be possible to cancel an order. Just after the order, we can use the buyer’s remorse pattern to prevent accidental orders. After the credit card is charged and before products are shipped, we should be able to cancel the order and refund the payment. Even after products are shipped, we should be able to cancel the order, and provide a refund (perhaps partial) provided that the items are returned.</p><p>All of these are great applications for sagas, which give you the ability to model business requirements together with the passage of time. Buyer’s remorse is just a start.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;The other day I read this article about how Gmail will &lt;a href=&quot;http://mashable.com/2015/06/23/undo-send-gmail/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;finally let you “Undo Send” emails you wish you didn’t send&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Really Google? &lt;em&gt;Really?&lt;/em&gt; What took you so long? I mean, I know you’ve been very busy shuttering Google Reader and all that, but offering the ability to undo sending an email within 30 seconds is actually pretty easy to build.&lt;/p&gt;
&lt;p&gt;At least, it is with &lt;a href=&quot;http://particular.net/nservicebus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NServiceBus&lt;/a&gt;, and specifically, using an NServiceBus &lt;a href=&quot;http://docs.particular.net/nservicebus/sagas/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Saga&lt;/a&gt;. I’ll show you how.&lt;/p&gt;
&lt;p&gt;“Undo Send” is really just a specific case of a much more general pattern I’ll call the buyer’s remorse pattern.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="Google" scheme="https://www.davidboike.dev/tags/Google/"/>
    
  </entry>
  
  <entry>
    <title>Goodbye WordPress, Hello Jekyll</title>
    <link href="https://www.davidboike.dev/2015/05/goodbye-wordpress-hello-jekyll/"/>
    <id>https://www.davidboike.dev/2015/05/goodbye-wordpress-hello-jekyll/</id>
    <updated>2015-05-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>WordPress and I had an altercation the other day.</p><p>I logged in to the management interface for my blog, and it was begging me to update to WordPress 4.2.2, and given the <a href="http://www.cvedetails.com/vulnerability-list/vendor_id-2337/product_id-4096/" target="_blank" rel="noopener">number of security vulnerabilities</a> that have been in the news lately, I figured that was probably a good idea.</p><p>But this time, the automated, one-click update process failed me for the first time. I don’t know precisely why, but it blew up mid-stream. Luckily the public portion of the site was still serving, but the admin was completely roasted. So I had to go through the painful process of doing a manual upgrade over FTP.</p><p>Luckily, I got everything working on Version 4.2.2, but I resolved at that point that 4.2.2 would be my last.</p><p>It’s not that I’m a huge WordPress hater. When it works it works well enough. But I absolutely disdain PHP, kind of like <a href="http://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/" target="_blank" rel="noopener">this guy</a>, so I can’t really go hack on it very well because to do so would give me the itchies all over. The freedom I want to have with my blog makes WordPress.com hosting impossible, but I don’t want to go through the hassle of self-hosting either. Plus I really <em>really</em> want to the particular webhost I’m using <a href="http://gawker.com/5787676/meet-godaddys-ridiculous-elephant-killing-ceo" target="_blank" rel="noopener">because</a> <a href="http://breakupwithgodaddy.com/" target="_blank" rel="noopener">reasons</a>, and moving my blog is a great first step.</p><p>I recently joined <a href="http://particular.net" target="_blank" rel="noopener">Particular Software</a> and we do everything on <a href="https://github.com" target="_blank" rel="noopener">GitHub</a>. No really, I mean <em>everything</em>. (Well OK, GitHub and <a href="https://slack.com/" target="_blank" rel="noopener">Slack</a>.) And while I’m no slouch at HTML when I really want to write, nothing beats Markdown. So it makes sense to take advantage of that.</p><p>So, if you’re reading this post, my blog is now run by <a href="http://jekyllrb.com/" target="_blank" rel="noopener">Jekyll</a> on <a href="https://pages.github.com/" target="_blank" rel="noopener">GitHub Pages</a>. How does that work? Glad you asked.</p><ol><li>Create a new GitHub repository called <em>your-github-username</em>.github.io - mine is <a href="https://github.com/DavidBoike/davidboike.github.io" target="_blank" rel="noopener">davidboike.github.io</a>.</li><li>Create a Jekyll repository. (This is the tricky part.)</li><li>Write your posts as Markdown files.</li><li>Push your changes to GitHub.</li><li>GitHub compiles your posts from your master branch into HTML pages serves it up as static content.</li></ol><p>Rather than do a whole bunch of work on #2, I decided to stand on the shoulders of <a href="http://haacked.com" target="_blank" rel="noopener">Phil Haack</a> whose <a href="http://haacked.com/archive/2013/12/02/dr-jekyll-and-mr-haack/" target="_blank" rel="noopener">blog post on converting his own blog</a> had originally informed me Jekyll, and <del>take inspiration from</del> outright steal his Jekyll repository as a starting point. Luckily, <a href="https://github.com/Haacked/feedback/issues/69" target="_blank" rel="noopener">he’s OK with that</a>. I did make some changes to make it my own.</p><p>The trickiest part turned out to be porting my content. There is a <a href="http://import.jekyllrb.com/docs/wordpress/" target="_blank" rel="noopener">WordPress to Jekyll converter</a> but it goes pretty crazy on you, and doesn’t convert the WordPress HTML to Markdown. So I had to do a lot of work on my own to <a href="http://stackoverflow.com/questions/6119793/convert-html-or-rtf-to-markdown-or-wiki-compatible-syntax" target="_blank" rel="noopener">convert the HTML to Markdown with Pandoc</a> and then clean up a lot of the mess afterwards.</p><p>But it’s definitely worth it. Now I don’t have to worry if my blog is down. That’s GitHub’s problem. I can edit my posts with Markdown using the same GitHub workflow I use every day. And it means I can accept pull requests on my blog! So if I make a mistake, please speak up and correct me!</p><p>Hopefully this will make it even easier for me to blog in the future.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;WordPress and I had an altercation the other day.&lt;/p&gt;
&lt;p&gt;I logged in to the management interface for my blog, and it was begging me to up
      
    
    </summary>
    
      <category term="Announcements" scheme="https://www.davidboike.dev/categories/Announcements/"/>
    
    
      <category term="Jekyll" scheme="https://www.davidboike.dev/tags/Jekyll/"/>
    
      <category term="GitHub" scheme="https://www.davidboike.dev/tags/GitHub/"/>
    
      <category term="GitHub Pages" scheme="https://www.davidboike.dev/tags/GitHub-Pages/"/>
    
      <category term="WordPress" scheme="https://www.davidboike.dev/tags/WordPress/"/>
    
  </entry>
  
  <entry>
    <title>Joining Particular Software</title>
    <link href="https://www.davidboike.dev/2015/04/joining-particular-software/"/>
    <id>https://www.davidboike.dev/2015/04/joining-particular-software/</id>
    <updated>2015-04-30T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>For the last few years, I have tried to arrange my career around the following two principles:</p><ol><li>Surround myself with the smartest people I can find.</li><li>Be prepared to do the thing that scares me a little bit.</li></ol><p>And it’s been working great. This led me to leave a full-time SaaS company to join <a href="http://www.ilmservice.com" target="_blank" rel="noopener">ILM Professional Services</a> a few years ago. As a software consultant, I was able to work on multiple projects for different companies, learn new technologies and different methodologies, and have a lot of fun doing it. But the best part about ILM is its people. Even though I would go out to work at client sites, ILM fosters a real sense of community and family, so I was never on an island.</p><p>I really can’t say enough good things about ILM. If you’re a software developer in the Minneapolis/St. Paul area, you love to code, and you have a thirst for knowledge, you should check them out. Tell them David sent you.</p><p>These principles of mine also led me to write my book, <a href="https://www.packtpub.com/application-development/learning-nservicebus-second-edition" target="_blank" rel="noopener">Learning NServiceBus</a>, which is now in its second edition. Compared to the effort that goes into writing a book, you really don’t get paid that much in terms of raw dollars, but it has been amazing for my career growth. Pretty soon I was speaking at conferences and teaching the official NServiceBus training course.</p><p>And that has all led to this moment.</p><p>It’s sad to have to leave ILM, because they have become a family to me, but I am very excited to be joining <a href="http://particular.net/" target="_blank" rel="noopener">Particular Software</a> full-time on May 4. (That’s right, <a href="http://www.starwars.com/may-the-4th" target="_blank" rel="noopener">May the Fourth</a>!) It’s pretty difficult to find someone smarter than Udi Dahan, and it will be a privilege to work for him. Add in all the other extremely smart and talented people in the company, and I just hope that I’ll be able to keep up.</p><p>When I first moved away from home to go to college, my father told me, “Well son, this is going to be quite the adventure.” Well, the adventure continues, and I’m excited to get started.</p>]]></content>
    
    <summary type="html">
    
      For the last few years, I have tried to arrange my career around the following  two principles:

	Surround myself with the smartest people I can  find.
	Be prepared to do the thing that scares me a little bit.


    
    </summary>
    
      <category term="Announcements" scheme="https://www.davidboike.dev/categories/Announcements/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="book" scheme="https://www.davidboike.dev/tags/book/"/>
    
      <category term="career" scheme="https://www.davidboike.dev/tags/career/"/>
    
  </entry>
  
  <entry>
    <title>Learning NServiceBus Second Edition</title>
    <link href="https://www.davidboike.dev/2015/02/learning-nservicebus-second-edition/"/>
    <id>https://www.davidboike.dev/2015/02/learning-nservicebus-second-edition/</id>
    <updated>2015-02-02T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>I’m excited to say that the second edition of my book, <a href="https://www.packtpub.com/application-development/learning-nservicebus-second-edition" target="_blank" rel="noopener">Learning NServiceBus</a>, has now been published!</p><p><a href="https://www.packtpub.com/application-development/learning-nservicebus-second-edition" target="_blank" rel="noopener"><img src="/images/learning-nservicebus-2ndEd.jpg" alt="Learning NServiceBus Second Edition"></a></p><p>The second edition of the book includes the following improvements over the first edition:</p><ul><li><p>Completely updated to cover NServiceBus 5.0</p></li><li><p>All-new chapter on the Service Platform (ServiceControl, ServiceInsight, ServicePulse, and ServiceMatrix)</p></li><li><p>More diagrams (these were unfortunately sparse in the first edition)</p></li><li><p>Coverage of V5-specific features (Pipeline, Outbox)</p></li><li><p>Revised and expanded…everything</p><p>All told, there are <a href="https://twitter.com/DavidBoike/status/556103930430910465" target="_blank" rel="noopener">roughly 44 additional pages</a> (over the first edition) of just raw new content.</p></li></ul><p>And perhaps best of all, the new edition includes a foreward from <a href="http://www.udidahan.com/" target="_blank" rel="noopener">Udi Dahan</a> himself, which tells the story of how NServiceBus got its start in the first place, tracing the history from his early days as a programmer to the point where this book has been published in its second edition. It’s very humbling for me personally to have his endorsement on my work, and I am very thankful.</p><p>Also, as always, so many thanks to everyone at Particular Software who were very helpful during the development of the book, and to my tech reviewers <a href="https://twitter.com/danielmarbach" target="_blank" rel="noopener">Daniel Marbach</a>, <a href="https://twitter.com/hadi_es" target="_blank" rel="noopener">Hadi Eskandari</a>, <a href="https://twitter.com/roycornelissen" target="_blank" rel="noopener">Roy Cornelissen</a>, and <a href="https://twitter.com/PrashantBrall" target="_blank" rel="noopener">Prashant Brall</a>, who made sure that you have the best content in your hands possible.</p><p>The book is <a href="https://www.packtpub.com/application-development/learning-nservicebus-second-edition" target="_blank" rel="noopener">available for purchase right now</a> from the publisher in physical and eBook forms, and will be available via other channels (Amazon, Barnes &amp; Noble, Safari Books Online, etc.) shortly. I hope of course that you buy it, but more importantly, that you find it useful.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I’m excited to say that the second edition of my book, &lt;a href=&quot;https://www.packtpub.com/application-development/learning-nservicebus-sec
      
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="announcements" scheme="https://www.davidboike.dev/tags/announcements/"/>
    
      <category term="book" scheme="https://www.davidboike.dev/tags/book/"/>
    
  </entry>
  
  <entry>
    <title>My Next Endeavor: Teaching</title>
    <link href="https://www.davidboike.dev/2014/12/my-next-endeavor-teaching/"/>
    <id>https://www.davidboike.dev/2014/12/my-next-endeavor-teaching/</id>
    <updated>2014-12-16T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<div style="float:right;width:200px;margin:1em;"><img src="/images/teaching..-300x217.png" /></div><p>Uncle Bob Martin is one of the true learned elders of our industry, one of those who signed the <a href="http://agilemanifesto.org/" target="_blank" rel="noopener">Agile Manifesto</a> when I was still taking college courses. Recently, he <a href="http://blog.cleancoder.com/uncle-bob/2014/06/20/MyLawn.html" target="_blank" rel="noopener">wrote about</a> (and <a href="https://skillsmatter.com/skillscasts/5224-the-future-of-agile-if-any" target="_blank" rel="noopener">has talked about</a>) something that absolutely blew me away.</p><p>Uncle Bob correctly identifies that many in our industry are young (even too young) and that there is a relative lack of older (and one would hope, more experienced) software developers. Not because they are going away, but because of the exponential growth in the number of total software developers. Indeed, he estimates that the number of software developers <strong>doubles every five years</strong>.</p><p>This was not altogether unsurprising to me, until he pointed out that this means if the number of developers is doubling every five years, then at any given point in time <strong>half of all software developers on the planet have less than five years of experience</strong>.</p><p><em>Woah.</em></p><p>Half of ALL software developers are fairly junior developers without a lot of experience under their belt. So to really help advance the field of software development, we should be finding better ways to train those scores of junior developers joining our ranks each and every year.</p><p>Luckily, I’ve been given the opportunity to do something about that.</p><p>This January, I won’t be going to code up some website for a new client. As part of a partnership between <a href="http://www.ilmservice.com" target="_blank" rel="noopener">ILM</a>, <a href="http://www.learninghouse.com/" target="_blank" rel="noopener">The Learning House</a>, the <a href="http://www.swcguild.com/" target="_blank" rel="noopener">Software Craftsmanship Guild</a>, and <a href="http://www.csp.edu/" target="_blank" rel="noopener">Concordia University</a>, I will be serving as an Adjunct Professor at Concordia teaching the .NET track of the <a href="http://bootcamp.csp.edu/" target="_blank" rel="noopener">Coding Bootcamp</a>, an intensive 12-week course designed to take a student with an aptitude for computers and turn them into a well-trained junior developer ready to write code in the real world.</p><p>I can’t speak for Computer Science curriculum around the country, but in my own college experience I felt that while I was taught the basics of a computer language (C++ at the time) I was not effectively prepared to be a software developer in the real world. Hopefully this has changed in the intervening years, but I found I had to learn a lot of those other skills on the job.</p><p>This is why I’m so excited about the <a href="http://bootcamp.csp.edu/program/" target="_blank" rel="noopener">Coding Bootcamp curriculum</a>. (Look at the very bottom of the linked page.) It’s not just about learning C#. It doesn’t cover what was current five years ago. Students will be learning ASP.NET MVC 5, Web API, and SQL Server 2012. It’s not just Microsoft-centered; there’s some Dapper in there in addition to Entity Framework. Beyond just HTML and CSS, students will be introduced to jQuery and AngularJS. And they’ll learn about effective source control with Git–a skill I consider just as important as writing the code itself.</p><p>All told, what the students will learn has less in common with what I learned in college, and much more in common with the core areas on which we evaluate all potential ILM employees. Teaching students the exact skills they’ll actually need in a real-life job seems like such a crazy idea that it just might work.</p><div style="float:right;width:240px;margin:1em;"><img src="/images/applesoft.jpg" /></div><p>I don’t remember a lot about my early days in elementary school, but I do remember that my fourth grade teacher Mrs. Dickerson gave me a book about programming in AppleSoft BASIC <sup><a href="#footnote-1">1</a></sup> that I read cover to cover. (The book pictured <em>may</em> be that book or perhaps a later edition of it. I was unable to find it in my parents’ attic.) While other events certainly had an impact on my life’s direction, you could argue that she set me upon a path that led to my eventual career and where I am today. For that I am forever grateful to her, and I hope that I can do her proud.</p><p>I’m really looking forward to helping people the way Mrs. Dickerson helped me by sharing what I love to do. If the number of software developers really is doubling every five years, hopefully I can ensure that 10-15 of them at a time are at the very least well prepared to get started.</p><p>If you would like to attend the Coding Bootcamp you can apply at <a href="http://bootcamp.csp.edu" target="_blank" rel="noopener">bootcamp.csp.edu</a>.</p><p>Or, if you are looking for talented new developers, you can <a href="http://bootcamp.csp.edu/employer-network/" target="_blank" rel="noopener">join the employer network</a>.</p><ol><li>In yet another example of <a href="http://blog.codinghorror.com/the-principle-of-least-power/" target="_blank" rel="noopener">Atwood’s Law</a>, <a href="http://www.calormen.com/jsbasic/" target="_blank" rel="noopener">AppleSoft BASIC now runs in JavaScript</a>.</li></ol>]]></content>
    
    <summary type="html">
    
      Uncle Bob Martin is one of the true  learned elders of our industry, one of those who signed the Agile  Manifesto when I was still taking college courses. Recently, he wrote  about (and has  talked about) something that absolutely blew me away.

Uncle Bob correctly  identifies that many in our industry are young (even too young) and that there is  a relative lack of older (and one would hope, more experienced) software developers.  Not because they are going away, but because of the exponential growth in the number  of total software developers. Indeed, he estimates that the number of software developers  doubles every five years.

This was not altogether unsurprising  to me, until he pointed out that this means if the number of developers is doubling  every five years, then at any given point in time half of all software developers  on the planet have less than five years of experience.

Woah.


    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
  </entry>
  
  <entry>
    <title>Wrapping a jQuery plugin in an AngularJS directive (Screencast)</title>
    <link href="https://www.davidboike.dev/2014/06/wrapping-a-jquery-plugin-in-an-angularjs-directive-screencast/"/>
    <id>https://www.davidboike.dev/2014/06/wrapping-a-jquery-plugin-in-an-angularjs-directive-screencast/</id>
    <updated>2014-06-16T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Recently I saw Evil Trout’s screencast <a href="http://eviltrout.com/2014/06/03/jquery-component.html" target="_blank" rel="noopener">Wrapping a jQuery plugin in an Ember.js component</a>, and thought that it would be really valuable to show the exact same plugin implemented instead as an AngularJS directive.</p><p>I want to be clear that I don’t intend to start some sort of Angular vs. Ember flame war. I happen to believe, as Ben Lesh concludes in his <a href="http://www.benlesh.com/2014/04/embular-part-1-comparing-ember-and.html" target="_blank" rel="noopener">excellent 6-part blog series comparing the two frameworks</a>, that Angular and Ember are two paths up the same mountain, and that together they are pushing the state of web development forward.</p><p>It’s clear that I need better audio equipment if I intend to keep doing screencasts, but I think it’s passable. I hope you enjoy it!</p><a id="more"></a><div style="text-align: center; margin: 1em 0;"><iframe src="//www.youtube.com/embed/nz8lAKHBgJY" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe></div>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Recently I saw Evil Trout’s screencast &lt;a href=&quot;http://eviltrout.com/2014/06/03/jquery-component.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Wrapping a jQuery plugin in an Ember.js component&lt;/a&gt;, and thought that it would be really valuable to show the exact same plugin implemented instead as an AngularJS directive.&lt;/p&gt;
&lt;p&gt;I want to be clear that I don’t intend to start some sort of Angular vs. Ember flame war. I happen to believe, as Ben Lesh concludes in his &lt;a href=&quot;http://www.benlesh.com/2014/04/embular-part-1-comparing-ember-and.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;excellent 6-part blog series comparing the two frameworks&lt;/a&gt;, that Angular and Ember are two paths up the same mountain, and that together they are pushing the state of web development forward.&lt;/p&gt;
&lt;p&gt;It’s clear that I need better audio equipment if I intend to keep doing screencasts, but I think it’s passable. I hope you enjoy it!&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="jQuery" scheme="https://www.davidboike.dev/tags/jQuery/"/>
    
      <category term="Angular" scheme="https://www.davidboike.dev/tags/Angular/"/>
    
      <category term="screencast" scheme="https://www.davidboike.dev/tags/screencast/"/>
    
  </entry>
  
  <entry>
    <title>NServiceBus and the Mystery of IWantCustomInitialization</title>
    <link href="https://www.davidboike.dev/2014/05/nservicebus-and-the-mystery-of-iwantcustominitialization/"/>
    <id>https://www.davidboike.dev/2014/05/nservicebus-and-the-mystery-of-iwantcustominitialization/</id>
    <updated>2014-05-27T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Recently <a href="http://romikoderbynew.com/" target="_blank" rel="noopener">Romiko Derbynew</a> was reading my book, <a href="http://www.packtpub.com/build-distributed-software-systems-using-dot-net-enterprise-service-bus/book" target="_blank" rel="noopener">Learning NServiceBus</a>, and noticed a contradiction between the manuscript and the included source code:</p><blockquote><p>In the Sample for DependecyInjection in the book, the code is:</p><p>public class ConfigureDependencyInjection : NServiceBus.<strong>IWantCustomInitialization</strong></p><p>However in the book, is says</p><p>IWantCustomInitialization should be implemented <strong>only on the class</strong> that implements <strong>IConfigureThisEndpoint</strong> and allows you to perform customizations that are unique to that endpoint.</p></blockquote><p> Of course I could wax philosophic about the tight deadline of book publishing, or how difficult it is to keep the sample code in sync with the manuscript, or how I probably wrote that part of the code and that part of the manuscript on different days on different pre-betas of NServiceBus 4.0, but what it comes down to at the end of the day is <a href="http://failblog.cheezburger.com/" target="_blank" rel="noopener">#FAIL</a>!</p><p>So here’s the real scoop, or at least, updated information as I see it and would recommend now, circa NServiceBus 4.6.1.</p><a id="more"></a><p>The part of the book referenced comes from Chapter 5: Advanced Messaging, where I am discussing the various general extension points in the NServiceBus framework that you can hook into by implementing certain marker interfaces, and then at startup, NServiceBus finds all these classes via assembly scanning and executes them at the proper time.</p><p>The interfaces are nominally described in the order that they are executed, and so I described IWantCustomInitialization third, after IWantCustomLogging and IWantToRunBeforeConfiguration, and described it as shown above. (The quoted passage is the entire bullet point.)</p><p>Unfortunately, it isn’t that easy.</p><p>(The following bit of explanation references history that exists mainly in my mind and, therefore, may not be entirely accurate as I’m not willing to dig through years of Git history to prove it. I might get a detail or two wrong, but stay with me.)</p><p>IWantCustomInitialization and IWantCustomLogging are somewhat unique in the list because they have been around forever (I gauge “forever” as since I started with NServiceBus at Version 2.0) and in the meantime, all of the other interfaces were added on (at least as far as my memory serves) through the development of V3 and V4.</p><p>So in this before-time of long-long-ago, these two interfaces only worked when applied to the EndpointConfig (the class that implements IConfigureThisEndpoint) but the new ones can be on any class, and there can be multiple ones.</p><p>Except as it turns out, IWantCustomInitialization pulls double-duty. It will execute either on the EndpointConfig OR as a standalone class, but with one critical difference: <strong>Whether or not it exists on the EndpointConfig changes the order of execution with respect to the other extension point interfaces!</strong></p><p>When implemented on a random class, an IWantCustomInitialization will run third, where I described in the book (after IWantToRunBeforeConfiguration, but before INeedInitialization) but if implemented on EndpointConfig, it will run second only to IWantCustomLogging, which always runs first because otherwise, you don’t have logging.</p><p>Confused yet? Here’s the definitive updated order:</p><ol><li><p>IWantCustomLogging (only executes on EndpointConfig)</p></li><li><p>IWantCustomInitialization, implemented on EndpointConfig</p></li><li><p>IWantToRunBeforeConfiguration</p></li><li><p>IWantCustomInitialization, implemented on its own class</p></li><li><p>INeedInitialization</p></li><li><p>IWantToRunBeforeConfigurationIsFinalized</p></li><li><p>IWantToRunWhenConfigurationIsComplete *</p></li><li><p>IWantToRunWhenBusStartsAndStops</p><p><em>\</em> One could argue that IWantToRunWhenConfigurationIsComplete should not be listed as a “general extension point” because it alone is located in the NServiceBus.Config namespace, not in the root NServiceBus namespace with all the others. This may have been an oversight that the NServiceBus developers weren’t willing to break SemVer for (which would require bumping to 5.0) or may be intentional, but I personally see the value in having a near-the-end extension point with full access to the DI container.*</p></li></ol><p>So what would I recommend about IWantCustomInitialization now?</p><p>I view the duality of “runs at different times depending upon which class it’s implemented on” to be dangerous and I would rather avoid that, especially since INeedInitialization provides basically the exact same behavior at the exact same time. So I would respect history (and the text in the book) and say that IWantCustomInitialization should only be used on the EndpointConfig for endpoint-specific behaviors, or in a BaseEndpointConfig class you inherit real EndpointConfigs from.</p><p>So that would make the code sample from the book <em>wrong</em>, even though it technically works. I would use INeedInitialization for that class instead.</p><p>By the way, this is a great time to mention a previously reported issue with the same section of the book. Even though the text says that “INeedInitialization is the best place to perform common customizations<br> such as setting a custom dependency injection container…” but as it turns out, the <a href="https://groups.google.com/forum/#!msg/particularsoftware/yDWc7LUMH1M/pv_gEQIBuksJ" target="_blank" rel="noopener">only place you can set up a custom DI container is from IWantCustomInitialization on the EndpointConfig</a>.</p><p>And on a closing note, I would suggest that if you don’t get enough evidence that you are human and make mistakes by being a software developer, you should perhaps consider writing a book. ;-)</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Recently &lt;a href=&quot;http://romikoderbynew.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Romiko Derbynew&lt;/a&gt; was reading my book, &lt;a href=&quot;http://www.packtpub.com/build-distributed-software-systems-using-dot-net-enterprise-service-bus/book&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Learning NServiceBus&lt;/a&gt;, and noticed a contradiction between the manuscript and the included source code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the Sample for DependecyInjection in the book, the code is:&lt;/p&gt;
&lt;p&gt;public class ConfigureDependencyInjection : NServiceBus.&lt;strong&gt;IWantCustomInitialization&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However in the book, is says&lt;/p&gt;
&lt;p&gt;IWantCustomInitialization should be implemented &lt;strong&gt;only on the class&lt;/strong&gt; that implements &lt;strong&gt;IConfigureThisEndpoint&lt;/strong&gt; and allows you to perform customizations that are unique to that endpoint.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt; Of course I could wax philosophic about the tight deadline of book publishing, or how difficult it is to keep the sample code in sync with the manuscript, or how I probably wrote that part of the code and that part of the manuscript on different days on different pre-betas of NServiceBus 4.0, but what it comes down to at the end of the day is &lt;a href=&quot;http://failblog.cheezburger.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;#FAIL&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;So here’s the real scoop, or at least, updated information as I see it and would recommend now, circa NServiceBus 4.6.1.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="dependency injection" scheme="https://www.davidboike.dev/tags/dependency-injection/"/>
    
      <category term="book" scheme="https://www.davidboike.dev/tags/book/"/>
    
  </entry>
  
  <entry>
    <title>Failed Message Notification with ServiceControl</title>
    <link href="https://www.davidboike.dev/2014/05/failed-message-notification-with-servicecontrol/"/>
    <id>https://www.davidboike.dev/2014/05/failed-message-notification-with-servicecontrol/</id>
    <updated>2014-05-07T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>In my post <a href="/2014/04/distributed-system-monitoring-done-right/">Distributed System Monitoring Done Right</a>, I mentioned in passing how <a href="http://particular.net/servicepulse" target="_blank" rel="noopener">ServicePulse</a> doesn’t ship with any built-in notification system for failed messages, but that you could easily build a system to send an email (or SMS, or carrier pigeon) to do so.</p><p>In this post I’ll show you how.</p><a id="more"></a><p>First, create a new Class Project called ErrorNotify, and turn it into an endpoint by including the <a href="http://www.nuget.org/packages/NServiceBus.Host" target="_blank" rel="noopener">NServiceBus.Host NuGet package</a>.</p><p>Next, you need to reference the messages assembly that ServiceControl uses for its externally published events. It’s called ServiceControl.Contracts and you can find it in your ServiceControl installation directory. For me that’s located at:</p><p>C:\Program Files (x86)\Particular Software\ServiceControl\ServiceControl.Contracts.dll</p><p>Note that ServiceControl uses the JSON serializer internally, so if you subscribe to the failed message notifications, your endpoint will need to use the JSON serializer too. Even if you use a different serializer (like the default XML one) in the rest of your system, it doesn’t matter because this error notifier endpoint is completely separate and decoupled from the rest of your system.</p><p>To set the serializer to JSON, modify your EndpointConfig.cs given to you by NuGet so that it implements IWantCustomInitialization:</p><script src="https://gist.github.com/ff420add0138b6b6c9d9.js?file=EndpointConfig.cs"></script><p>Next we need to write the actual code to subscribe to the MessageFailed event published by ServiceControl. I’m not going to show you how to build and send an email. That would be boring and silly and I’m sure you can do it yourself. But it is important to point out that you can extract the FailedMessageId from the failed message details and craft a URL using ServiceInsight’s URL scheme that will launch <a href="http://particular.net/serviceinsight" target="_blank" rel="noopener">ServiceInsight</a>and show you the offending message directly!</p><script src="https://gist.github.com/ff420add0138b6b6c9d9.js?file=ErrorNotify.cs"></script><p>Lastly, we need to modify the App.config file to subscribe to messages from the Particular.ServiceControl service.</p><script src="https://gist.github.com/ff420add0138b6b6c9d9.js?file=App.config"></script><p>That’s it! Once we deploy this code, we will get email notifications of failures complete with links to ServiceInsight so we can go figure out exactly what went wrong.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;In my post &lt;a href=&quot;/2014/04/distributed-system-monitoring-done-right/&quot;&gt;Distributed System Monitoring Done Right&lt;/a&gt;, I mentioned in passing how &lt;a href=&quot;http://particular.net/servicepulse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ServicePulse&lt;/a&gt; doesn’t ship with any built-in notification system for failed messages, but that you could easily build a system to send an email (or SMS, or carrier pigeon) to do so.&lt;/p&gt;
&lt;p&gt;In this post I’ll show you how.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="ServiceInsight" scheme="https://www.davidboike.dev/tags/ServiceInsight/"/>
    
      <category term="ServiceControl" scheme="https://www.davidboike.dev/tags/ServiceControl/"/>
    
  </entry>
  
  <entry>
    <title>RavenConf 2014 Slides and Video</title>
    <link href="https://www.davidboike.dev/2014/04/ravenconf-2014-slides-and-video/"/>
    <id>https://www.davidboike.dev/2014/04/ravenconf-2014-slides-and-video/</id>
    <updated>2014-04-30T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Here is the <a href="http://conference.ravendb.net/" target="_blank" rel="noopener">RavenConf 2014</a> video and slides for my presentation “Modeling Tricks My Relational Database Never Taught Me” in which I compare SQL Server to Big Brother from <a href="https://www.youtube.com/watch?v=VtvjbmoDx-I" target="_blank" rel="noopener">Apple’s 1984 ad</a> and RavenDB to the Dude from <a href="http://www.imdb.com/title/tt0118715/" target="_blank" rel="noopener">The Big Lebowski</a>.</p><a id="more"></a><p align="center"><iframe src="&#47;&#47;www.youtube.com&#47;embed&#47;GLmk4_BjQl4" height="315" width="560" allowfullscreen="" frameborder="0"></iframe></p><p align="center"><iframe src="https://www.slideshare.net/slideshow/embed_code/34132643" height="400" width="476" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Here is the &lt;a href=&quot;http://conference.ravendb.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RavenConf 2014&lt;/a&gt; video and slides for my presentation “Modeling Tricks My Relational Database Never Taught Me” in which I compare SQL Server to Big Brother from &lt;a href=&quot;https://www.youtube.com/watch?v=VtvjbmoDx-I&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple’s 1984 ad&lt;/a&gt; and RavenDB to the Dude from &lt;a href=&quot;http://www.imdb.com/title/tt0118715/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Big Lebowski&lt;/a&gt;.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
      <category term="Announcements" scheme="https://www.davidboike.dev/categories/Development/Announcements/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="book" scheme="https://www.davidboike.dev/tags/book/"/>
    
      <category term="RavenDB" scheme="https://www.davidboike.dev/tags/RavenDB/"/>
    
      <category term="presentations" scheme="https://www.davidboike.dev/tags/presentations/"/>
    
      <category term="SQL Server" scheme="https://www.davidboike.dev/tags/SQL-Server/"/>
    
      <category term="speaking" scheme="https://www.davidboike.dev/tags/speaking/"/>
    
  </entry>
  
  <entry>
    <title>Distributed System Monitoring Done Right</title>
    <link href="https://www.davidboike.dev/2014/04/distributed-system-monitoring-done-right/"/>
    <id>https://www.davidboike.dev/2014/04/distributed-system-monitoring-done-right/</id>
    <updated>2014-04-24T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>When I first started writing <a href="http://www.packtpub.com/build-distributed-software-systems-using-dot-net-enterprise-service-bus/book" target="_blank" rel="noopener">Learning NServiceBus</a>, I was targeting Version 4.0 which, at that time, was still several months away from release. Writing about something that’s still very much in flux is definitely a challenge, and to some extent I was definitely learning as I went.</p><p>What really struck me during the writing process was how much easier people learning NServiceBus 4.0 were going to have it than I did when I learned NServiceBus 2.0. The developers at <a href="http://particular.net/" target="_blank" rel="noopener">Particular Software</a> (a name change from NServiceBus Ltd – a lot of people seem to think they were bought and this is not the case) are really obsessive about making a powerful framework as easy to use as possible, and I salute them for that.</p><p>I remember creating endpoints by hand. Create a new Class Library project. Reference the NServiceBus DLLs and NServiceBus.Host.exe. Build so that the EXE is copied to the bin directory. Go to Project Properties. Set the debugger to run the Host. Create an EndpointConfig class. Add an App.config. Enter a bunch of required XML configuration. OK that’s a lie. As I was <a href="https://twitter.com/baskint/status/254665318927052800" target="_blank" rel="noopener">once quoted during a live coding demo</a>, “Don’t worry I have been doing this for years. You never write this yourself; you always copy it from somewhere else.” Not exactly a glowing recommendation right?</p><p>Then you start debugging and hope you didn’t screw anything up.</p><p>NServiceBus 3.x and 4.x changed all that. Now you just reference a the NServiceBus.Host NuGet package and it sets all that stuff up for you. And if you need some bit of config, you can run a helpful PowerShell cmdlet from the Package Manager Console to generate it for you along with XML comments describing what every knob and lever does.</p><p>NServiceBus 4.x is a fantastic platform to build distributed systems, but as of the <a href="http://www.udidahan.com/2013/07/11/nservicebus-4-0-released/" target="_blank" rel="noopener">release of NServiceBus 4.0 in July 2013</a>, the big thing still missing was the ability to effectively debug a messaging system (let’s face it, gargantuan log files don’t count) and monitor a distributed system in production to make sure everything isn’t running off the rails.</p><p>Well that’s all about to change.</p><a id="more"></a><h2 id="Don’t-Build-Your-Own-Monitoring-Tools"><a href="#Don’t-Build-Your-Own-Monitoring-Tools" class="headerlink" title="Don’t Build Your Own Monitoring Tools"></a>Don’t Build Your Own Monitoring Tools</h2><p> For the first system I ever built on NServiceBus 2.x, I built my own monitoring and management tools because I had no other choice. I didn’t want to remote desktop into a server and launch Computer Management to view the Message Queues. Let’s face it, that tool is heinous enough when run locally. And I certainly didn’t want to remote desktop into the server to run ReturnToSourceQueue.exe, and have to potentially copy and paste a message id into a console window over remote desktop. No thank you!</p><p>So I built a tool called MsmqRemote that had a daemon process that I installed on every single server that hosted any NServiceBus queues. It was responsible for interacting with MSMQ and NServiceBus on each server. It had the capability to list queues, and get details about the messages in each queue, and return all of this information to a client application via a WCF service hosted over TCP. It could move and delete messages, all based on MSMQ code I had to write myself. It contained a copy of the relevant ReturnToSourceQueue code so that it could do that operation as well.</p><p>The client application was a WinForms monstrosity with four panes. First you selected a server which was populated from a config file, that told the application which WCF service URL to try to connect to. Then it would ask the server for a list of queues, which would appear in the second pane. After selecting a queue, it would ask for a list of messages, which would appear in the third pane, and finally, selecting a message would again go to the server to ask for message details and contents, and the XML representation of the message would appear in the fourth and final pane.</p><p>The tool suffered from the same problem that plagues many internal tools. It wasn’t refined or nice or even very usable. It was always the minimum necessary to get the job done which meant that it was always pretty shitty. It didn’t always work quite right either, especially when a queue would fill up with a significant amount of messages, everything would slow to a crawl. And sometimes the daemon process would just completely crap out, because as you’re probably already aware WCF is <em>such a joy to work with.</em>(Sarcasm intentional.)</p><p>I don’t have any idea how many hours I ended up pouring into that tool, but what I do know for sure is that I wasn’t solving any business problems during that time. Meanwhile it was never the tool I wanted or really needed it to be, and addressing its shortcomings was always my lowest priority.</p><p>And MsmqRemote even begin to cover everything we needed to effectively monitor a production system. Endpoint health was a big concern. It wasn’t unheard of for an endpoint to appear to be healthy as far as the Process Manager was concerned, but for some reason to have stopped processing messages for whatever reason. I can think of one instance where in retrospect I’m sure my crappy code was to blame – a “command and control” sort of component implemented in an IWantToRunAtStartup that should have been a bunch of never-ending sagas instead. So my IT Manager would create a bunch of monitors in <a href="http://en.wikipedia.org/wiki/System_Center_Operations_Manager" target="_blank" rel="noopener" title="Microsoft System Center Operations Manager">Microsoft SCOM</a> (may have been MOM at the time) based on queue sizes and performance counters and all that sort of stuff. That was really his deal, not mine. But every once in awhile we’d forget to register a new endpoint when it got deployed for the first time, so then the first time it acted up or stalled we’d have to deal with problems like a few million messages backed up in a queue with no warning.</p><p>What a pain! If only there was a company out there that understood how distributed systems worked that could make tools to address these issues!</p><h2 id="The-Service-Platform"><a href="#The-Service-Platform" class="headerlink" title="The Service Platform"></a>The Service Platform</h2><p> The whole reason that NServiceBus Ltd. changed its name to Particular Software is that they were developing products to meet these needs, making NServiceBus itself only part of the story.</p><p>NServiceBus is now joined by a bunch of friends:</p><h3 id="ServiceControl"><a href="#ServiceControl" class="headerlink" title="ServiceControl"></a>ServiceControl</h3><p> ServiceControl is a specialized endpoint that lives on the same server as your centralized audit and error queues. It processes every message that arrives in the audit queue (in other words, a copy of every message that flows through your entire system) and stores the details in an embedded <a href="http://ravendb.net/" target="_blank" rel="noopener">RavenDB</a> database. It then discards those audit messages because otherwise you’d be running out of disk space in a hurry. It also reads the messages off the error queue and similarly stores these in Raven, but keeps these message around in a new queue called error.log because you’ll more than likely want to send those messages back to their source queues after you fix the underlying problem.</p><p>All this information stored in the embedded RavenDB database is made available via a REST API. (Suck it WCF.) With this API you can build your own reports and tools if you like, but this provides the foundation from which the other Service Platform tools are built.</p><h3 id="ServiceInsight"><a href="#ServiceInsight" class="headerlink" title="ServiceInsight"></a>ServiceInsight</h3><p> ServiceInsight is a WPF application that makes my little MsmqRemote look like it was written by a 3rd grader, but it extends much deeper than just showing message details and retrying errors. Because it feeds off the ServiceControl API, which is processing the audit messages from ALL your endpoints, it shows a holistic view of your entire distributed system.</p><p>When NServiceBus sends or publishes a message in the scope of a message handler, enough headers are added that by the time it gets to ServiceInsight, complete conversations can be stitched together and represented as graphical flows, where sending commands are represented as solid lines and published events are represented as dashed lines.</p><p>Check out the flow diagram in this screenshot.</p><p><a href="/images/serviceinsight-flowdiagram.png"><img src="/images/serviceinsight-flowdiagram_thumb.png" alt="ServiceInsight Flow Diagram"></a></p><p>Notice how some of those messages have “policies” mentioned under the timestamp. Those are sagas, and show how the message flow integrates with sagas you write. This is because I’ve included the <a href="http://www.nuget.org/packages/ServiceControl.Plugin.SagaAudit/1.0.0-Beta0007-0009" target="_blank" rel="noopener">ServiceControl.Plugin.SagaAudit NuGet Package</a> in my endpoints, which inserts itself into the pipeline to send saga auditing information to ServiceControl.</p><p>If you click on one of those, or on the Saga tab near the bottom, you’ll get this amazing visualization showing the saga’s state changes in vivid detail, like this screenshot zoomed to show only the saga flow:</p><p><a href="/images/serviceinsight-sagaflow.png"><img src="/images/serviceinsight-sagaflow_thumb.png" alt="ServiceInsight Saga Diagram"></a></p><p>This is pure awesome, and something you’ll only ever have time to build on your own if either 1) you work for Particular, or 2) you work for a company that somehow isn’t concerned with making money. You’re also not going to get this level of tooling from MassTransit. You do, after all, get what you pay for.</p><h3 id="ServicePulse"><a href="#ServicePulse" class="headerlink" title="ServicePulse"></a>ServicePulse</h3><p> Where ServiceInsight is the tool for a developer to debug a system, ServicePulse is the tool for my IT Manager and our other Ops friends to monitor our systems in production and make sure that everything is healthy.</p><p>All you need to do is deploy the <a href="http://www.nuget.org/packages/ServiceControl.Plugin.Heartbeat/" target="_blank" rel="noopener">ServiceControl.Plugin.Heartbeat NuGet package</a> with your endpoint, and it will begin periodically sending heartbeat messages to ServiceControl. ServicePulse is a web application that will use this information, along with information about failed messages, and serve up a dashboard giving you near real-time updates on system health with all sorts of SignalR-powered goodness.</p><p>In addition, you can program your own custom checks to be tracked in ServicePulse. For instance, let’s say you needed to be sure a certain FTP server was up. You could program a custom check for that by including the <a href="http://www.nuget.org/packages/ServiceControl.Plugin.CustomChecks" target="_blank" rel="noopener">ServiceControl.Plugin.CustomChecks NuGet package</a> and creating a class that inherits PeriodicCheck.</p><p>This is what ServicePulse looks like moments after I stopped debugging in Visual Studio, causing the heartbeat messages to stop.</p><p><a href="/images/ServicePulse-Screenshot-Beta4-9.png"><img src="/images/ServicePulse-Screenshot-Beta4-9_thumb.png" alt="ServicePulse Screenshot"></a></p><p>Yes, the Endpoints box bounces when there’s an issue. I guess it’s mad at me! I would show you more screenshots, but they’re full of a recent client’s name and I don’t love image editing that much, plus you should go try for yourself!</p><p>The one thing that is missing from ServicePulse, by necessity really, is a direct notification feature. You aren’t going to want your Ops people constantly staring at the ServicePulse website; you need some way for them to be notified when there’s an issue. Every company is going to want to do that differently, of course. Some will want a simple email notification, some will want an SMS, some will want integration with a <a href="http://help.hipchat.com/knowledgebase/articles/64359-running-a-hipchat-bot" target="_blank" rel="noopener">HipChat bot</a>, and of course some will want all of the above!</p><p>It’s convenient that ServiceControl is really just another endpoint. It has an events assembly ServiceControl.Contracts that contains events that you can subscribe to. Check out this <a href="https://github.com/Particular/ServiceControl/blob/bc1dfe3e372e6148e0e57d03f64a5d959f815174/src/ServiceControl.IntegrationDemo/MessageFailedHandler.cs" target="_blank" rel="noopener">sample MessageFailedHandler</a> that shows how you could subscribe to the <a href="https://github.com/Particular/ServiceControl/blob/ccc47271f779e9c23b612081d57cd520de1236c6/src/ServiceControl.Contracts/MessageFailures/MessageFailed.cs" target="_blank" rel="noopener">MessageFailed</a> event and send a notification email.</p><p>In the future there will be additional tooling to connect ServicePulse with <a href="http://en.wikipedia.org/wiki/System_Center_Operations_Manager" target="_blank" rel="noopener" title="Microsoft System Center Operations Manager">Microsoft SCOM</a> and perhaps other monitoring suites as well.</p><h3 id="ServiceMatrix"><a href="#ServiceMatrix" class="headerlink" title="ServiceMatrix"></a>ServiceMatrix</h3><p> This article is mostly about system monitoring, and ServiceMatrix is really not a monitoring tool, but it deserves a mention because it is also a part of the new Service Platform suite of tools.</p><p>ServiceMatrix is a Visual Studio plugin that makes it possible to build an NServiceBus system with graphical design tools, dragging and dropping to send messages from one endpoint to another, and that sort of thing. It really deserves an article all to itself.</p><p>I’ve been doing NServiceBus the hard way for quite some time, so it’s hard for me to wrap my head around doing it graphically. But the hard truth is that the NServiceBus code-by-hand demo I frequently give that takes about an hour to create manually can be done in about 5 minutes with ServiceMatrix. Five Minutes. Udi himself has stated that now that he’s gotten used to ServiceMatrix, he can’t envision creating NServiceBus solutions any other way.</p><p>Aside from creation speed and baking in NServiceBus design best practices, ServiceMatrix contains two features I really feel are game-changers.</p><p>First, whenever you debug your solution with ServiceMatrix, it will generate a debug session id that is shared with all your endpoints, and reported to ServiceControl via the <a href="http://www.nuget.org/packages/ServiceControl.Plugin.DebugSession" target="_blank" rel="noopener">ServiceControl.Plugin.DebugSession NuGet Package</a>. It will then navigate to a URL starting with the si:// scheme, which is registered to ServiceInsight, so ServiceInsight will open up and show you details for just the messages volleyed around during the current debug session. This means, in many cases, you won’t need to painstakingly arrange all of your endpoint console windows just right so that you can see what’s going on, you’ll just look at the results in ServiceInsight.</p><p>Second, when you create an MVC website with ServiceMatrix, it will auto-scaffold a UI to test sending messages with fields to enter message property values. What a big time saver over creating temporary controllers just to test things out, and having to interact with them only from the query string!</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p> When I think about the old about the crummy tools I built in the past for NServiceBus monitoring in comparison to the new tools in the Service Platform, it reminds me of the difference between my garage and my grandfather’s woodshop. My garage contains a bunch of the basics. Sure I have a couple saws and screwdrivers and a hammer or two, but my grandfather has been retired for several years and in that time has been pursuing woodworking seriously as more than a hobby, so he’s got a dozen saws and the central vacuum system and all the little toys and jigs you need to really get some serious work done. Every time I need to use the table saw I have to back a car out and drag the saw out from the corner, but he doesn’t waste time with that because his whole workshop is set up and ready to go.</p><p>Just as I could accomplish so much more in my grandpa’s workshop than in my garage, I will be able to accomplish so much more with NServiceBus using the tools in the Service Platform. They’re exactly the tools I would have built myself (or better) if only I’d had the time.</p><p>But I didn’t have to.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;When I first started writing &lt;a href=&quot;http://www.packtpub.com/build-distributed-software-systems-using-dot-net-enterprise-service-bus/book&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Learning NServiceBus&lt;/a&gt;, I was targeting Version 4.0 which, at that time, was still several months away from release. Writing about something that’s still very much in flux is definitely a challenge, and to some extent I was definitely learning as I went.&lt;/p&gt;
&lt;p&gt;What really struck me during the writing process was how much easier people learning NServiceBus 4.0 were going to have it than I did when I learned NServiceBus 2.0. The developers at &lt;a href=&quot;http://particular.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Particular Software&lt;/a&gt; (a name change from NServiceBus Ltd – a lot of people seem to think they were bought and this is not the case) are really obsessive about making a powerful framework as easy to use as possible, and I salute them for that.&lt;/p&gt;
&lt;p&gt;I remember creating endpoints by hand. Create a new Class Library project. Reference the NServiceBus DLLs and NServiceBus.Host.exe. Build so that the EXE is copied to the bin directory. Go to Project Properties. Set the debugger to run the Host. Create an EndpointConfig class. Add an App.config. Enter a bunch of required XML configuration. OK that’s a lie. As I was &lt;a href=&quot;https://twitter.com/baskint/status/254665318927052800&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;once quoted during a live coding demo&lt;/a&gt;, “Don’t worry I have been doing this for years. You never write this yourself; you always copy it from somewhere else.” Not exactly a glowing recommendation right?&lt;/p&gt;
&lt;p&gt;Then you start debugging and hope you didn’t screw anything up.&lt;/p&gt;
&lt;p&gt;NServiceBus 3.x and 4.x changed all that. Now you just reference a the NServiceBus.Host NuGet package and it sets all that stuff up for you. And if you need some bit of config, you can run a helpful PowerShell cmdlet from the Package Manager Console to generate it for you along with XML comments describing what every knob and lever does.&lt;/p&gt;
&lt;p&gt;NServiceBus 4.x is a fantastic platform to build distributed systems, but as of the &lt;a href=&quot;http://www.udidahan.com/2013/07/11/nservicebus-4-0-released/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;release of NServiceBus 4.0 in July 2013&lt;/a&gt;, the big thing still missing was the ability to effectively debug a messaging system (let’s face it, gargantuan log files don’t count) and monitor a distributed system in production to make sure everything isn’t running off the rails.&lt;/p&gt;
&lt;p&gt;Well that’s all about to change.&lt;/p&gt;
    
    </summary>
    
      <category term="Development" scheme="https://www.davidboike.dev/categories/Development/"/>
    
    
      <category term="NServiceBus" scheme="https://www.davidboike.dev/tags/NServiceBus/"/>
    
      <category term="ServiceInsight" scheme="https://www.davidboike.dev/tags/ServiceInsight/"/>
    
      <category term="ServiceControl" scheme="https://www.davidboike.dev/tags/ServiceControl/"/>
    
      <category term="ServicePulse" scheme="https://www.davidboike.dev/tags/ServicePulse/"/>
    
      <category term="ServiceMatrix" scheme="https://www.davidboike.dev/tags/ServiceMatrix/"/>
    
  </entry>
  
</feed>