<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Western Devs</title>
  
  <link href="/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://westerndevs.com" rel="alternate" type="application/atom+xml"/>
  
  <updated>2026-11-04T17:00:00.000Z</updated>
  <id>https://westerndevs.com/</id>
  
  <author>
    <name>Western Devs</name>
	<uri>https://westerndevs.com</uri>
    <email>info@westerndevs.com</email>
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title type="html">Can You Validate Game Logic WITHOUT Code?</title>
    <link href="https://westerndevs.com/game-design/david-wesst-can-you-validate-game-logic-without-code/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/game-design/david-wesst-can-you-validate-game-logic-without-code/</id>
    <published>2026-11-04T17:00:00.000Z</published>
    <updated>2026-11-04T17:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<iframe src="https://www.youtube.com/embed/KkKHnrUmEE4" title="Watch Can You Validate Game Logic WITHOUT Code? on YouTube" allow="encrypted-media; picture-in-picture; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" style="display:block;width:100%;aspect-ratio:16/9;border:0"></iframe><p><a href="https://www.youtube.com/watch?v=KkKHnrUmEE4" target="_blank" rel="noopener">Watch on YouTube</a></p><p>Spreadsheets are magical tools that can not only help you organize your finances, but they can also help you prototype video game logic. No really-- they can. That is what I spent a good chunk of my time this past iteration doing: modelling the new part of the base game and balancing it. All done without a line of code or even opening a code editor.</p><!-- Optional: add context or notes about the video here. -->]]></content>
    
    <summary type="html">
    
      Spreadsheets are magical tools that can not only help you organize your finances, but they can also help you prototype video game logic. No really they can. That is what I spent…
    
    </summary>
    
      <category term="game-design" scheme="https://westerndevs.com/categories/game-design/"/>
    
    
      <category term="gamedev" scheme="https://westerndevs.com/tags/gamedev/"/>
    
      <category term="devlog" scheme="https://westerndevs.com/tags/devlog/"/>
    
      <category term="game-design" scheme="https://westerndevs.com/tags/game-design/"/>
    
      <category term="game-development" scheme="https://westerndevs.com/tags/game-development/"/>
    
      <category term="video" scheme="https://westerndevs.com/tags/video/"/>
    
      <category term="project-passenger" scheme="https://westerndevs.com/tags/project-passenger/"/>
    
      <category term="spreadsheets" scheme="https://westerndevs.com/tags/spreadsheets/"/>
    
  </entry>
  
  <entry>
    <title type="html">Video Games are Built with Spreadsheets</title>
    <link href="https://westerndevs.com/game-design/david-wesst-video-games-are-built-with-spreadsheets/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/game-design/david-wesst-video-games-are-built-with-spreadsheets/</id>
    <published>2026-10-27T16:00:00.000Z</published>
    <updated>2026-10-27T16:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<img src="/images/david-wesst/2026-10-27-david-wesst-video-games-are-built-with-spreadsheets/974bbb04087cb0be-spreadsheets-banner.png" alt="A player-stats and energy-balance spreadsheet with an arrow pointing to the Passenger game prototype."><p>I knew that spreadsheets were the glue that kept software projects together, but never realized how they would be so much more critical in video game development projects.</p><p>The more time I spend making a video game, the more I have come to realize that video games start with spreadsheets. Not code. Not pictures. But spreadsheets.</p><p>Spreadsheets for funding. Spreadsheets for balancing. Spreadsheets for localization. Spreadsheets, spreadsheets, and more spreadsheets.</p><p>Working in enterprise software, I am all too familiar with spreadsheets for managing projects, organizing teams, and all sorts of business activities.</p><p>They are the glue or duct tape that hold the project together, in a lot of cases.</p><p>For video games, I keep finding myself and the various experts I learn from using spreadsheets <em>to build</em> the video game itself, not just organize the business activities around it.</p><p>I'm sure there are more examples than I know about, but in this post I want to share where spreadsheets seem to make sense as the default tool to use.</p><blockquote><p>If you have other examples, reach out to me on <a href="https://bsky.app/profile/david.wes.st" target="_blank" rel="noopener">Bluesky</a>, <a href="https://ca.linkedin.com/in/davidwesst" target="_blank" rel="noopener">LinkedIn</a>, or <a href="https://youtube.com/davidwesst" target="_blank" rel="noopener">YouTube</a> and tell me how you use spreadsheets to make your video game. I am genuinely interested in learning where else they fit into the development process.</p></blockquote><h2>Text Localization</h2><p>I have only dabbled in this one so far, but as I experimented with various dialogue systems, I saw how any dialogue or text needs to fit into a spreadsheet at some point to be localized.</p><p>A spreadsheet is a table, plain and simple. Using it to store data and then mapping that data to other data just makes logical sense.</p><p>This is why when I look at tools to help me with dialogue or text in my game, I make sure that they support export to a spreadsheet-friendly format like CSV.</p><h2>Gameplay Models and Balancing</h2><p>This one is a little more involved. I heard <a href="https://jesseschell.com/" target="_blank" rel="noopener">Jesse Schell</a> on a podcast talking about how he had used a spreadsheet to determine the balance of a trivia game he was making.</p><p>I figure if you can create a model to simulate trivia gameplay, you must be able to do it for other systems.</p><p>And so, with my current project <a href="https://cocobokostudios.com/#games" target="_blank" rel="noopener">Passenger</a>, I have spent time modelling out how to crunch the numbers in a new part of the game I am trying to prototype.</p><p>After a few evenings of fiddling, I managed to get a spreadsheet that showed me how the formulas balanced out and what variables I had to adjust to make things easier or harder for the player.</p><p><img src="/images/david-wesst/2026-10-27-david-wesst-video-games-are-built-with-spreadsheets/243c2ea4620142b4-gameplay-model-screenshot.png" alt="Gameplay-model spreadsheet showing player stats and adjustable energy-balance constants, including base energy cost, task multipliers, and optional task chance."></p><p>Let me emphasize what this has done for my project-- before adding more content to my game, I <em>know</em> how the gameplay system will be balanced. I don't think I know; I <em>actually know</em> how it will be balanced.</p><p>That doesn't mean the work is actually done. I mean, I still have to make it fun and all that. But I have a formula that I can adjust to make things easier (or harder) depending on player feedback and the feeling I am going for in the scene.</p><h2>Understanding Your Audience</h2><p>In <a href="https://david.wes.st/blog/defining-video-game-scope-with-documentation/" target="_blank" rel="noopener">my last devlog</a>, I talked about documentation helping me define scope for Passenger by doing a competitive analysis on games I thought seemed comparable to the vision I have for Passenger.</p><p>This is all done in spreadsheets.</p><p>With the competitive analysis, I pulled all the tags for the comparable games and started crunching the data to figure out what tags aligned with my vision and what tags appeared more frequently with the comparable games. The overlap between these two sets of tags gives me the features I should include in my game.</p><h3>Example</h3><p><img src="/images/david-wesst/2026-10-27-david-wesst-video-games-are-built-with-spreadsheets/57f245f9789402e8-tag-analysis-screenshot.png" alt="Competitive analysis spreadsheet comparing game tags, including Story Rich, Life Sim, and Dating Sim, by applicability to Passenger, reference count, and appearances."></p><p>For example, you can see that Dating Sim is one of the tags I was considering for the game-- however, after doing some analysis on the comparables, I can see that it doesn't show up nearly as much as I thought it would.</p><p>This means that in terms of prototyping priority, the dating sim mechanics have dropped pretty low on my development schedule.</p><p>This saves me the effort of trying to figure out if that even works, as the audience I am trying to reach with Passenger doesn't really care about dating sim mechanics as compared to other features.</p><h2>Conclusion</h2><p>Spreadsheets are the duct tape of software development in general-- but they appear to be more than that for video game development.</p><p>They provide more than just the glue for video games. They provide the interface for working with non-game developer types and allow you to prove out your game idea before you even start drawing it out.</p>]]></content>
    
    <summary type="html">
    
      I knew that spreadsheets were the glue that kept software projects together, but never realized how they would be so much more critical in video game development projects.
    
    </summary>
    
      <category term="game-design" scheme="https://westerndevs.com/categories/game-design/"/>
    
    
      <category term="gamedev" scheme="https://westerndevs.com/tags/gamedev/"/>
    
      <category term="game-design" scheme="https://westerndevs.com/tags/game-design/"/>
    
      <category term="game-development" scheme="https://westerndevs.com/tags/game-development/"/>
    
      <category term="project-passenger" scheme="https://westerndevs.com/tags/project-passenger/"/>
    
      <category term="spreadsheets" scheme="https://westerndevs.com/tags/spreadsheets/"/>
    
  </entry>
  
  <entry>
    <title type="html">Defining Video Game Scope With Documentation</title>
    <link href="https://westerndevs.com/gamedev/david-wesst-defining-video-game-scope-with-documentation/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/gamedev/david-wesst-defining-video-game-scope-with-documentation/</id>
    <published>2026-10-21T16:00:00.000Z</published>
    <updated>2026-10-21T16:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<img src="/images/david-wesst/2026-10-21-david-wesst-defining-video-game-scope-with-documentation/d66f1d39a0962506-0012-devlog-thumbnail_UPDATED.png" alt="A man holding up both hands, with the text &quot;Defining Scope for Games&quot;."><p>Every project needs an end, and that includes video games. But how do you decide what "done" means for your prototype, your vertical slice, or even your whole game? I might not know much about game development—but I have a lot of experience defining software scope. For my most recent iteration, I spent time out of the code editor and in a Markdown editor. I even used spreadsheets! *gasp* 😜</p><p>As I went through this past two-week iteration, I spent the majority of my time trying to nail down the project scope. More specifically, trying to answer three questions:</p><ul><li>What is this game?</li><li>What are my project constraints?</li><li>Where is my audience?</li></ul><p>Although I am not experienced in game development, I have quite a bit of experience defining scope for non-gaming projects. I tried to map that experience to what I am trying to do with the Passenger prototype.</p><p>So, what do you do to define scope in software?</p><p>You write documentation, of course! Which is exactly what I did this iteration.</p><p>Here's what I worked on in terms of documentation.</p><h2>GDD (Game Design Document)</h2><p><img src="/images/david-wesst/2026-10-21-david-wesst-defining-video-game-scope-with-documentation/30a469c5fdf049b6-gdd.png" alt="Passenger's game design document, showing its genre, target audience, and game loop."></p><p>A GDD is sort of the obvious one. I am not going to go into depth here about how to write one, as much more experienced folks out there have already done that. However, if you are unfamiliar with them, I suggest doing some research.</p><p>I based mine on the one shared at <a href="https://develop.games/" target="_blank" rel="noopener">develop.games</a>, which is still available as a <a href="https://docs.google.com/document/d/1Vl7BMvzUOhbunJrI_X1gUc6x-LAp3aaBiPwHUf27B70/" target="_blank" rel="noopener">Google Doc here</a>.</p><p>The GDD provides the scaffold for the project and, more importantly, a reference you can always return to when figuring out if something belongs in your game or if it's scope creep.</p><h2>Project Plan / Schedule</h2><p><img src="/images/david-wesst/2026-10-21-david-wesst-defining-video-game-scope-with-documentation/3b94a8910a8c6b1f-issue-backlog.png" alt="Passenger's project issue backlog."></p><p>I have grouped these together because they help define <em>the constraints</em> for the project.</p><p>With projects, you always have constraints. The usual suspects are time and money. In my case, I am most curious about time (mainly because I have very little to no money for the project at this point).</p><p>I use GitHub for the project plan, and it helps me understand how much work I need to do to complete the various milestones of the project. The schedule shows me how much time I have available and what beats I want to hit with my game. Together, these let you do "project math" to determine if you have enough time in your schedule based on how long you estimate the project will take.</p><p>Again, not going to elaborate on project math—and estimates, by definition, are not facts or truth. However, they can help you determine if you have too much scope in your plan and need to trim things down a bit.</p><p>You trim them down by reviewing the GDD and the issues list in your project plan to figure out if every feature is needed.</p><h2>Competitive Analysis</h2><p><img src="/images/david-wesst/2026-10-21-david-wesst-defining-video-game-scope-with-documentation/2456d8c6a86a6c96-competitive-analysis.png" alt="Passenger's competitive analysis spreadsheet."></p><p>I learned to put together a competitive analysis for a game through a masterclass and through local resources with the business development group <a href="https://newmediamanitoba.com/" target="_blank" rel="noopener">New Media Manitoba</a>.</p><p>When you learn about what it takes to get funding for a video game, you find that it takes more than just a great pitch and a demo. It also takes a business plan.</p><p>The competitive analysis is what publishers and investors are going to want to see (amongst other things) to determine if your game is commercially viable, because it helps answer the question of <em>finding your audience</em>.</p><p>Once you know where your audience is (i.e. they are on the Steam pages of those comparable games), you can analyze the tags and figure out if your game idea aligns with the games you defined as comparable.</p><p>In my case, I assessed the tags that I wanted to use for my game and figured out if they aligned, in some capacity, with the games I believed to be comparable to Passenger. From there, I can start to deduce if my scope is big enough, too big, or too small for that audience.</p><p>It helped me answer the question <em>where is my audience?</em> when it comes to Steam. I can see the size of other games I believe to be comparable and figure out if my scope is realistic, too big, or too small.</p><h2>Story Outline</h2><p>This might not work for every game project, but it works for the ones with a narrative as a core feature of the game.</p><p>I started writing down the story to figure out how I would tell it through the gameplay mechanics I had defined so far and those I had planned. This exercise of understanding all my story beats helped me clarify the game I have in my head and, ultimately, whether I have all the pieces to deliver that story.</p><p>If I do, then my scope is probably in a reasonable state to deliver the vision I have in my head.</p><p>If I don't, then I either need to change my story or add the features I think I need.</p><h2>Related Devlog</h2><p>I recorded and published a video on <a href="https://youtu.be/EAB4uFQN6LE" target="_blank" rel="noopener">YouTube</a>. I go into a bit more depth about some of these documents if you want more specific context about how I use them for Passenger.</p><p>Have a great rest of your day.</p>]]></content>
    
    <summary type="html">
    
      Every project needs an end, and that includes video games. But how do you decide what &quot;done&quot; means for your prototype, your vertical slice, or even your whole game? I might not…
    
    </summary>
    
      <category term="gamedev" scheme="https://westerndevs.com/categories/gamedev/"/>
    
    
      <category term="gamedev" scheme="https://westerndevs.com/tags/gamedev/"/>
    
      <category term="devlog" scheme="https://westerndevs.com/tags/devlog/"/>
    
      <category term="video" scheme="https://westerndevs.com/tags/video/"/>
    
      <category term="project-passenger" scheme="https://westerndevs.com/tags/project-passenger/"/>
    
  </entry>
  
  <entry>
    <title type="html">Is Scalable Game Design a Thing?</title>
    <link href="https://westerndevs.com/game-design/david-wesst-is-scalable-game-design-a-thing/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/game-design/david-wesst-is-scalable-game-design-a-thing/</id>
    <published>2026-10-13T16:00:00.000Z</published>
    <updated>2026-10-13T16:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[  <img src="/images/david-wesst/2026-10-13-david-wesst-is-scalable-game-design-a-thing/af90227ca3e52949-0011-devlog-thumbnail.png" alt="A man holds both hands open toward the camera beside bold text reading “Scalable Game Design?”, with “Game” highlighted in yellow."><p>As familiar as I am with scalable software design for non-game development projects, in this past iteration, I started to assess whether or not scalability is something I need to consider for video game design.</p><p>Video games are software, but they're not like any other software.</p><p>I have been dealing in software development for almost two decades at this point, but the more I tackle creating a commercial video game, the more I see how game development is just not the same.</p><p>My journey in game development continued with another completed iteration where game design was the focus. As the game started taking shape and a project started to form, the idea of <em>scalable game design</em> entered my mind.</p><p>Is scalable game design a thing?</p><p>This is what I reflect on in the devlog for this iteration.</p><iframe width="560" height="315" src="https://www.youtube.com/embed/VKzO3YvJ_Jg?si=dLhFRCt4EMyjtuZJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>]]></content>
    
    <summary type="html">
    
      As familiar as I am with scalable software design for non game development projects, in this past iteration, I started to assess whether or not scalability is something I need to…
    
    </summary>
    
      <category term="game-design" scheme="https://westerndevs.com/categories/game-design/"/>
    
    
      <category term="gamedev" scheme="https://westerndevs.com/tags/gamedev/"/>
    
      <category term="devlog" scheme="https://westerndevs.com/tags/devlog/"/>
    
      <category term="game-design" scheme="https://westerndevs.com/tags/game-design/"/>
    
      <category term="game-development" scheme="https://westerndevs.com/tags/game-development/"/>
    
      <category term="video" scheme="https://westerndevs.com/tags/video/"/>
    
      <category term="project-passenger" scheme="https://westerndevs.com/tags/project-passenger/"/>
    
  </entry>
  
  <entry>
    <title type="html">The Future is Agentic (Prairie Dev Con 2026 Takeaways)</title>
    <link href="https://westerndevs.com/prdc-2026/david-wesst-prairie-dev-con-2026-the-future-is-agentic/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/prdc-2026/david-wesst-prairie-dev-con-2026-the-future-is-agentic/</id>
    <published>2026-09-24T16:00:00.000Z</published>
    <updated>2026-09-24T16:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<img src="/images/david-wesst/2026-09-24-david-wesst-prairie-dev-con-2026-the-future-is-agentic/59fbf41b709a4816-prdc2026-panel.jpg" alt="Panel discussion on stage with three seated speakers and a moderator at a podium. One speaker is holding a microphone while the others listen, and a projected slide behind them asks about AI adoption, job losses, and responsibility for displaced workers."><p>With Prairie Dev Con (PrDC) 2026 complete, there was a clear theme in everything I managed to see and share—the future of software development is agentic. This doesn't necessarily mean it's all "doom and gloom," but rather an evolution has taken hold in our industry.</p><p>With Prairie Dev Con (PrDC) 2026 complete, there was a clear theme in everything I managed to see and share—the future of software development is agentic. This doesn't necessarily mean it's all "doom and gloom," but rather an evolution has taken hold in our industry.</p><h2>Three Perspectives on AI: My first conference panel</h2><p>I am always grateful to <a href="https://prairiedevcon.com/" target="_blank" rel="noopener">Prairie Dev Con</a> for accepting my wild ideas for talks and whatnot and allowing me the privilege of speaking to a fantastic audience. This year, I proposed something different—something unfamiliar to me.</p><p>I proposed a panel.</p><h3>The Hypothesis</h3><p><em>Why a panel?</em> you ask? Good question.</p><p>If I am being honest, there are two reasons:</p><ol><li>I wanted to try something different from a lecture-style conference submission.</li><li>I wanted to compare and contrast different viewpoints on the spot, rather than compile the data myself and share it.</li></ol><p>The hypothesis was that different perspectives will focus on different areas when it comes to AI, specifically with large language models (LLMs).</p><p>With that hypothesis, I found three different people who I have discussed AI with previously and brought them together. We had an enterprise CIO, an investor, and a small business founder.</p><p>And then came the panel.</p><h3>The Result</h3><p><img src="/images/david-wesst/2026-09-24-david-wesst-prairie-dev-con-2026-the-future-is-agentic/59fbf41b709a4816-prdc2026-panel.jpg" alt="Panel discussion on stage with three seated speakers and a moderator at a podium. One speaker is holding a microphone while the others listen, and a projected slide behind them asks about AI adoption, job losses, and responsibility for displaced workers."></p><p>I asked a series of questions, and each of them presented their own take on the subject, and the answers were very interesting in my humble and likely biased opinion (considering it was my panel). Some answers landed almost exactly the same and just complemented each other, while others focused on the pressing concerns from their unique space.</p><p>What was most interesting was when they all landed on or near the same answer—considering such different experiences and different problems they are trying to solve. It was really fascinating.</p><p>You can <a href="https://david.wes.st/talks/three-perspectives-on-ai/" target="_blank" rel="noopener">check out the slides</a> in the <a href="https://david.wes.st/talks/" target="_blank" rel="noopener">talks</a> section of the website. One of my panelists did manage to record the session, but it is going to take some audio cleanup, so I'll see what I can do and hopefully share that if I can make it usable.</p><h2>Takeaway 1: Agents aren't the future, they are the standard.</h2><p>I have been working with LLM-flavoured AI for quite some time. Even though I was very sure agentic workloads were coming, what PrDC made me realize is that not only are they here—but they are standard when it comes to software development.</p><p>A large majority of the sessions this year were flavoured with AI, including (but definitely not limited to):</p><ul><li>Generation Is Cheap. Judgment Isn't. (This was the keynote)</li><li>How product engineers at Microsoft are using AI</li><li>Agent Skills, Plugins &amp; Marketplaces</li><li>Building AI-Powered Business Intelligence: Transform Your Data Warehouse into an Intelligent Decision</li><li>Agentic AI Raises the Stakes for API Security</li><li>Build an MCP Data Source</li><li>AI Enablement in the SDLC: A Strategy Sampler for 2026</li><li>The Tokens are Coming From Inside the House</li><li>Securing the Software your Agent Wrote</li><li>Getting the Most Out of AI Coding Agents: Lessons From a Year in the Trenches</li></ul><p>The list continues but it is clear that AI and agents are the future based on these developers, one way or another. But it wasn't their words that really caught my interest, but the demonstrations and stories of "how" they are using agents today.</p><h3>Agents are the New Keyboard for Code</h3><p>I don't really know how else to put it, but from the demonstrations I saw, senior engineers were using coding agents as a replacement for their keyboards. So, rather than typing the code in themselves, they were getting the agents to write the code and then reviewing it.</p><p>This has made the new bottleneck the review process, which we see in <a href="https://godotengine.org/article/contribution-policy-2026/" target="_blank" rel="noopener">open source projects</a> ultimately reflecting what I saw at the conference.</p><h3>But isn't that a bad thing?</h3><p>I don't think so, no.</p><p>Although the software developers and senior folks were using agents as their primary way to change code, they were still actively using their skills to review and assess those changes. They were just doing multiple changes in parallel and reviewing them when they were ready.</p><p>It is still software development, but just in an accelerated form, not bound by hands at a keyboard.</p><h2>Takeaway 2: It is Still Changing</h2><p>Although it seems clear to me that agentic work is inevitable in software development, the future isn't written in stone. In fact, many developers dropped a line akin to "it will probably be different within the next 6–12 months."</p><p>That is <em>wild</em>.</p><p>The pace at which the <em>way we code</em> is changing, only to be accepted by thought leaders working (not owning) the space, is incredible.</p><p>When some new tech or standard would come along, it would take a year or two before people would really start adopting it en masse. But with agents? We literally don't use our code editors the same way anymore, and we know it is going to change more.</p><h2>Conclusion</h2><p>I love Prairie Dev Con, and this was no different.</p><p>I am grateful to have been selected, and also grateful to have been able to learn so much from so many experienced people.</p><p>In the end, I learned that LLMs and AI aren't just an evolution—but a revolution in how software engineering will work. The question is, will I (and you) be able to keep up?</p>]]></content>
    
    <summary type="html">
    
      With Prairie Dev Con (PrDC) 2026 complete, there was a clear theme in everything I managed to see and share—the future of software development is agentic. This doesn&#39;t…
    
    </summary>
    
      <category term="prdc-2026" scheme="https://westerndevs.com/categories/prdc-2026/"/>
    
    
      <category term="prairie-dev-con" scheme="https://westerndevs.com/tags/prairie-dev-con/"/>
    
      <category term="prdc-2026" scheme="https://westerndevs.com/tags/prdc-2026/"/>
    
      <category term="ai" scheme="https://westerndevs.com/tags/ai/"/>
    
      <category term="llm" scheme="https://westerndevs.com/tags/llm/"/>
    
      <category term="code-agent" scheme="https://westerndevs.com/tags/code-agent/"/>
    
  </entry>
  
  <entry>
    <title type="html">I Used Stacked PRs and Liked It</title>
    <link href="https://westerndevs.com/devops/david-wesst-i-used-stacked-prs-and-liked-it/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/devops/david-wesst-i-used-stacked-prs-and-liked-it/</id>
    <published>2026-08-12T16:00:00.000Z</published>
    <updated>2026-08-12T16:00:00.000Z</updated>
	<author>
	
	  
	  <name>David Wesst</name>
	  <email>questions@davidwesst.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<img src="/images/david-wesst/2026-08-12-david-wesst-i-used-stacked-prs-and-liked-it/4365f2207e3a1acb-i-used-stacked-prs-and-liked-it_banner.png" alt="GitHub's Octocat logo beside terminal output listing five branches in a stacked pull request"><p>Stacked pull requests helped me organize and merge a set of dependent changes to this website—and gave me a little faith in GitHub as a platform again.</p><p>GitHub introduced a non-AI-focused feature called <a href="https://docs.github.com/en/pull-requests/how-tos/stacked-pull-requests" target="_blank" rel="noopener">Stacked Pull Requests</a> that helps you merge multiple PRs at once. This is the first feature since the introduction of <a href="https://docs.github.com/en/issues/planning-and-tracking-with-projects" target="_blank" rel="noopener">Projects</a> that has interested me in years, and it has given me a bit of faith in GitHub as a platform again.</p><h2>Why Did I Use Stacked PRs?</h2><p>I heard about Stacked PRs through the <a href="https://www.westerndevs.com/" target="_blank" rel="noopener">Western Devs</a> (yes, we still talk, we just don't blog as much) and thought I would use them to "stack" a bunch of changes I wanted to apply to my website. My website has been something of a forever hobby project that I use to experiment with various technologies and keep my hands a little dirty when it comes to web tooling.</p><p>In any case, there were a number of features I wanted to apply to relaunch the site, but I knew it would be a massive change to the project. Between data migrations, new styling, and a new site structure, the change logs were going to be massive.</p><p>So, instead of one gigantic PR or having a bunch of PRs that I have to keep track of myself, I decided to try stacked PRs to keep track of the merge order for me.</p><p>Here are a couple of images of the CLI and Web UIs for stacked PRs from GitHub that I used:</p><p><img src="/images/david-wesst/2026-08-12-david-wesst-i-used-stacked-prs-and-liked-it/22173f6080b92dc6-gh-stack_terminal.png" alt="A black terminal window showing the CLI output for the gh stack view command, with five PRs ready to be merged"><img src="/images/david-wesst/2026-08-12-david-wesst-i-used-stacked-prs-and-liked-it/ebfedab31d401783-gh-stack_web.png" alt="The GitHub web interface for a PR showing the stacked PR section, with five green checkboxes lined up for five separate branches. A green &quot;Merge stack&quot; button is at the bottom of the widget, with a green &quot;Preview&quot; tag in the bottom-right corner."></p><h2>Were Stacked PRs Useful?</h2><p>Put simply: yes.</p><p>It sounds like a simple task, but even keeping my side project branches organized is difficult for me. I have tried keeping notes in the PRs themselves and organizing myself around milestones, but nothing really worked because it didn't tie everything together on the workflow side of things.</p><p>With my CI/CD pipelines in place, stacked PRs made it easy for me to order the branches properly, add new ones when necessary, and highlight where things would fail early when merging them altogether.</p><p><img src="/images/david-wesst/2026-08-12-david-wesst-i-used-stacked-prs-and-liked-it/7bb1014bcc3b38e2-gh-stack_cli_merging.png" alt="Terminal output with a black background and a text-based UI showing &quot;Select PRs&quot; highlighted, with green checked boxes for five branches being merged. The message &quot;Will merge 5 PRs into main&quot; appears in grey, with navigation commands at the bottom."></p><h2>This Isn't Groundbreaking Though-- Is It?</h2><p>No, of course not.</p><p>Honestly, I had been considering moving away from GitHub since they only seem to talk about generative AI coding. I'm not against AI coding, but that isn't the ONLY reason I use GitHub. In fact, I don't use GitHub Copilot; I use Codex for AI code assistance.</p><p>My point is that GitHub is a social coding platform with DevOps tooling, and if they aren't in that business anymore, then I should take the time to learn a toolset that does care about DevOps tooling.</p><p>I will save that for another post.</p>]]></content>
    
    <summary type="html">
    
      Stacked pull requests helped me organize and merge a set of dependent changes to this website—and gave me a little faith in GitHub as a platform again.
    
    </summary>
    
      <category term="devops" scheme="https://westerndevs.com/categories/devops/"/>
    
    
      <category term="github" scheme="https://westerndevs.com/tags/github/"/>
    
      <category term="devops" scheme="https://westerndevs.com/tags/devops/"/>
    
      <category term="pull-requests" scheme="https://westerndevs.com/tags/pull-requests/"/>
    
  </entry>
  
  <entry>
    <title type="html">Expensive Hosting for Small to Medium Projects</title>
    <link href="https://westerndevs.com/_/hosting-costs/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/hosting-costs/</id>
    <published>2026-01-15T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.900Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>If you happened to read my previous post about <a href="https://blog.simontimms.com/2025/12/24/where-is-this-ai-stuff-going/" target="_blank" rel="noopener">where AI is going</a> then you might have picked up on my concerns about the rising cost of memory and compute. For the last few years any time I've recommened hosting to a client I've recommended some variant of AWS, Azure, or GCP. I've done this because I remember how painful it used to be to get a server allocated from an overstressed IT department. When I was on internship years ago for a large petro-chemical company we planned had to plan for deployment 6 or even 12 months in advance. IT had to figure out capacity and plan, plan plan.</p><p>The cloud make this so much easier. We could spin up a server in minutes and with platform as a service we didn't have to worry about underlying compute or updates to operating systems, or patching. It was a dream. I'd tell clients &quot;You're an insurance company leave being an IT company to Microsoft/Amazon/Google&quot;.</p><p>Recently though I've been looking at the cost of hosting an application in Azure. Even for our small applications it is surprisngly expensive. We had a tendency to vastly overbuild the hosting when for applications that have only hundreds or low thousands of users. What really gets us is the cost of the database.</p><p><img src="/images/2026-01-31-hosting-costs/2026-03-22-10-11-01.png" alt=""></p><p>This isn't a huge or highly performant database, but it is still costing us almost $400/month. For a small application with only a few hundred users this is a huge cost. Add on dev and test environments and the cost is even higher. Painful.</p><p>I wanted to understand if I was still recommending the right thing to people so I started looking at alternatives. My first stop was looking at a VPS provider. I wanted something in Canada becasue this is where I live and I wanted to support local businesses. I found a provider called <a href="https://www.ovhcloud.com/en-ca/" target="_blank" rel="noopener">OVH</a> that had a VPS with 8 vCores, 24 Gig of memory and a 200 Gig SSD for $32 a month off contract.</p><p><img src="/images/2026-01-31-hosting-costs/2026-03-22-10-18-01.png" alt=""></p><p>Now obviously vCores aren't a standard measurement unit so it's hard to compares that with something from another offering like Azure but on the surface this feels similar to a D12 from Azure which is $380/month and that's in USD so closer to $500/month CAD. The OVH offering is about 1/15th the cost of Azure.</p><p><img src="/images/2026-01-31-hosting-costs/2026-03-22-10-21-19.png" alt=""></p><p>Yikes!</p><p>So I figured I'd sign up. Maybe the added cost here would be becasue it was really arduous to get signed up. I'll admit going through the process wasn't as polished as a cloud provier. Little things like this</p><p><img src="/images/2026-01-31-hosting-costs/2026-03-22-10-28-27.png" alt=""></p><p>Is that a last name field or a field for people who only have a single name? Is Madonna buying hosting a frequently? The 2FA setup, though, was probably the best I've ever done. It had the really nice feature of letting you record the name of the app you were using for 2FA. I have multiple apps for 2FA and this is a really nice feature.</p><p>I did spot this warning, though</p><p><img src="/images/2026-01-31-hosting-costs/2026-03-22-10-30-35.png" alt=""></p><p>Okay so there might be some latency in getting this server provisioned. That sucks, but perhaps I've gotten too used to the instant gratificaiton of demanding compute and getting it 5 minutes later. The email I got said that the server would be provisioned within the next 7 days. That's a long time to wait but whilte we do let's talk about why this VPS might not be a great idea. What are we losing by not using cloud hosting?</p><p>Well first off it's clear that scaling up and down is going to be so much harder. I can't scale the machine up instantly when there is a spike in traffic. Equally, if that spike subsites I can't scale down quickly. So I'm going to have to be comfortable knowing that I've got to pay for excess capacity. A lot of the line of business applications I work with have almost no traffic on the weekend and evenings but then predictable stable load during the day. I'm going to have to over provision to handle the maximum load. Maybe I can mine bitcoin or something on the weekends. Is that SETI@home project still a thing?</p><p>Next I'm losing the platform as a service benefits. I'm going to have to patch the operating system and database myself. I get to do backups, restores all that myself. So I'm going to trade my time for reduced hosting costs. Let's imagine that I'm setting up a database server and a web server which is kind of typical for a line of business application. This is going to run to $700/month in Azure(I'm going to use Candian dollars from here out). So that gives me $650 a month to play with for manual patching and updating and all that jazz. If I pay myself $200/hour (woo, massive pay raise) then I can spend 3 hours a month on that work and still come out ahead. I don't know if that scales well to dozens of servers but for a small application it might be worth it.</p><p>I'm likely going to have an increased cost for the initial set up of the server. I've got to figure out which web server to use, probably some default settings for PostgreSQL and tie it into a build pipeline. I suppose we'll be deploying via SFTP or something like that.</p><p>Finally, I'm losing the reliability and security of the cloud. I don't have the same level of redundancy and failover that I would get with a cloud provider. If my server goes down, it's on me to get it back up. If there is a security breach, it's on me to fix it. This is a big risk for any business, but especially for small businesses that may not have the resources to deal with these issues.</p><p>The other piece of the puzzle here is that the knowledge and expertise required to manage a VPS is higher than that required to use a cloud provider. I've been doing this computer stuff for year and I'm confident in my ability to teasily manage this but then I'm also still a bit sad about how Open Solaris failed to take off. I think that a lot of this gap can be closed using AI agents these days.</p><p>It's a calculated risk.</p><p>So, dear readers, I pulled the trigger and signed up for the VPS. Once it is provisioned then we'll return here and talk through setting up and deploying to it.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;If you happened to read my previous post about &lt;a href=&quot;https://blog.simontimms.com/2025/12/24/where-is-this-ai-stuff-going/&quot; target=&quot;_bl
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Jujutsu Cheat Sheet</title>
    <link href="https://westerndevs.com/_/jujutsu-cheat-sheet/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/jujutsu-cheat-sheet/</id>
    <published>2025-11-05T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.905Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>I've started playing around a bit with the source control tool <a href="https://github.com/jj-vcs/jj" target="_blank" rel="noopener">Jujutsu</a> which is commonly referred to as <code>jj</code>. Git has been my go to tool for what seems like decades now but in the before times I worked as a release engineer and made use of a huge stable of source control tools as our code base was spread over many versions and had been created from purchasing lots of other companies. For a while there I was working on a daily basis with</p><ul><li>ClearCase</li><li>Perforce</li><li>Subversion</li><li>CVS</li><li>Visual Source Safe</li><li>Mercurial</li><li>Git</li><li>CCC/Harvest</li></ul><p>I'm using Jujutsu at my day job now because it just layers transparently on top of git so I don't need to go seeking permission. I'm only a few days into using it and I'm not thoroughly convinced yet that it is better than git but I'm willing to keep trying.</p><p>Here are some of the commands I'm using so far:</p><p>Get the latest version of the code from a central repository locally</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">jj git fetch</span></span><br></pre></td></tr></table></figure><p>Start new work from the latest mainline</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jj <span class="keyword">new</span> main<span class="meta">@origin</span> -m <span class="string">"Whatever I'm going to work on"</span></span><br></pre></td></tr></table></figure><p>Bookmark the work with a name I'm going to use as a branch in git</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">jj </span><span class="keyword">bookmark </span>my-feature-<span class="keyword">branch</span></span><br></pre></td></tr></table></figure><p>Push my work up to Github</p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jj git <span class="keyword">push</span> --allow-<span class="keyword">new</span></span><br></pre></td></tr></table></figure><p>Create a new commit before my current one that I can squash into</p><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jj <span class="keyword">new</span> <span class="type"></span>-B @ -m <span class="string">"Some description of the work"</span></span><br></pre></td></tr></table></figure><p>Push individual files into the parent change</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jj squash <span class="type">path</span>/<span class="keyword">to</span>/file1 <span class="type">path</span>/<span class="keyword">to</span>/file2</span><br></pre></td></tr></table></figure><p>I'll keep expanding this document with new commands as I uncover them.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I&#39;ve started playing around a bit with the source control tool &lt;a href=&quot;https://github.com/jj-vcs/jj&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Juju
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Open API Generator for C#</title>
    <link href="https://westerndevs.com/_/open-api-generator/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/open-api-generator/</id>
    <published>2025-03-30T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>Every once in a while I run into the need to generate a C# client for some API which has been nice enough to provide me with OpenAPI specifications. But it's one of those things that I do so infrequently that I always forget how to do it. So I thought I would document it here.</p><p>The first thing to do is to install the OpenAPI generator. It's written in Java which is obviously a decision I'm solidly against. As I run a Mac most of the time I prefer to use brew since it's easier that trying to figure out how to build stuff with Maven.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install openapi-generator</span><br></pre></td></tr></table></figure><p>Now comes the fun part: figuring out the options to use. There are bunch of different generators for different languages and on top of that each generator has options. The C# generator is called <code>csharp</code> and the options are described in some detail here https://openapi-generator.tech/docs/generators/csharp. In my case I was looking to generator a client for a US Government API that they seem to be really snippy about people getting their hands on documentation about it without going through a heap of hoops so we'll just call it <code>USGovAPI</code> because this administration is not one I want to be on the wrong side of.</p><p>In addition I wanted to generate one for .NET 8 because the project is still running on the long term support version of .NET. So the command ended up being</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openapi-generator generate -i swagger.json -g csharp -o out/usgoveapi --additional-properties=packageName=USGovAPI,targetFramework=net8.0</span><br></pre></td></tr></table></figure><p>And with that we get a nice little C# client that we can use to call the USGovAPI. It even includes some unit tests to go along with it.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Every once in a while I run into the need to generate a C# client for some API which has been nice enough to provide me with OpenAPI spec
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Limit Dependabot to .NET 8</title>
    <link href="https://westerndevs.com/_/dependabot-ignore-dotnet-9/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/dependabot-ignore-dotnet-9/</id>
    <published>2024-11-20T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.904Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>Just last week .NET 9 was realeased to much fanfare. There are a ton of cool and exciting things in it but for my current project I want to stick to a long term support version of .NET which is .NET 8. We might update later but for now 8 is great. Unfortunately dependabot isn't able to read my mind so it was continually proposing updating to .NET 9 packages.</p><p>Fixing this is easy enough. I needed to add a couple of lines to my dependabot file to limit the sorts of updates it did to just minor and patch updates. Notice the <code>update-types</code> section.</p><figure class="highlight vim"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span><span class="variable">s:</span></span><br><span class="line">  - package-ecosystem: <span class="string">"nuget"</span></span><br><span class="line">    directory: <span class="string">"/."</span></span><br><span class="line">    group<span class="variable">s:</span></span><br><span class="line">      all_package<span class="variable">s:</span></span><br><span class="line">        <span class="keyword">update</span>-<span class="built_in">type</span><span class="variable">s:</span></span><br><span class="line">          - <span class="string">"minor"</span></span><br><span class="line">          - <span class="string">"patch"</span></span><br><span class="line">        pattern<span class="variable">s:</span></span><br><span class="line">          - <span class="string">"*"</span></span><br><span class="line">    <span class="keyword">open</span>-pull-requests-limi<span class="variable">t:</span> <span class="number">50</span></span><br><span class="line">    schedule:</span><br><span class="line">      interva<span class="variable">l:</span> <span class="string">"weekly"</span></span><br></pre></td></tr></table></figure><p>With this in place dependabot is only proposing minor and patch updates to my .NET packages. It does mean that if we see other major version updates to non-Microsoft packages we'll have to manually update them.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Just last week .NET 9 was realeased to much fanfare. There are a ton of cool and exciting things in it but for my current project I want 
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">RavenDB on Kubernetes</title>
    <link href="https://westerndevs.com/_/ravendb-on-k8s/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/ravendb-on-k8s/</id>
    <published>2024-11-14T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.908Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>I needed to get Particular Service Control up and running on our k8s cluster this week. Part of that is to get an instance of RavenDB running in the cluster and this actually caused me a bit of trouble. I kept running into problems where RavenDB would start up but then report that it couuld not access the data directory. What was up?</p><p>I tried overriding the entry point for the container and attaching to it to see what was going on but I couldn't see anything wrong. I was able to write to the directory without issue. Eventually I stumbled on a note in the <a href="https://ravendb.net/docs/article-page/6.0/csharp/migration/server/docker" target="_blank" rel="noopener">RavenDB documentation</a> which mentioned a change in the 6.x version of RavenDB which meant that Raven no longer ran as root inside the container.</p><p>K8S has the ability to change the ownership of the volume to the user that the container is running as. This is done by setting the <code>fsGroup</code> property in the pod spec. In this case Raven runs as UID 999. So I updated my tanka spec to include the <code>fsGroup</code> property and the problem was solved.</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><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"><span class="string">...</span></span><br><span class="line"><span class="attr">deployment:</span> <span class="string">deployment.new($._config.containers.ravendb.name)</span> <span class="string">&#123;</span></span><br><span class="line">      <span class="string">metadata+:</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">namespace:</span> <span class="string">'wigglepiggle-'</span> <span class="string">+</span> <span class="string">$._config.environment,</span></span><br><span class="line">        <span class="attr">labels:</span> <span class="string">&#123;</span></span><br><span class="line">          <span class="attr">app:</span> <span class="string">$._config.containers.ravendb.name,</span></span><br><span class="line">        <span class="string">&#125;,</span></span><br><span class="line">      <span class="string">&#125;,</span></span><br><span class="line">      <span class="string">spec+:</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">replicas:</span> <span class="number">1</span><span class="string">,</span></span><br><span class="line">        <span class="attr">selector:</span> <span class="string">&#123;</span></span><br><span class="line">          <span class="attr">matchLabels:</span> <span class="string">$._config.labels,</span></span><br><span class="line">        <span class="string">&#125;,</span></span><br><span class="line">        <span class="attr">template:</span> <span class="string">&#123;</span></span><br><span class="line">          <span class="string">metadata+:</span> <span class="string">&#123;</span></span><br><span class="line">            <span class="attr">labels:</span> <span class="string">$._config.labels,</span></span><br><span class="line">          <span class="string">&#125;,</span></span><br><span class="line">          <span class="string">spec+:</span> <span class="string">&#123;</span></span><br><span class="line">            <span class="attr">securityContext:</span> <span class="string">&#123;</span></span><br><span class="line">              <span class="attr">fsGroup:</span> <span class="number">999</span><span class="string">,</span></span><br><span class="line">              <span class="attr">fsGroupChangePolicy:</span> <span class="string">'OnRootMismatch'</span><span class="string">,</span></span><br><span class="line">            <span class="string">&#125;,</span></span><br><span class="line">            <span class="attr">containers:</span> <span class="string">[</span></span><br><span class="line">              <span class="string">&#123;</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">$._config.containers.ravendb.name,</span></span><br><span class="line">                <span class="attr">image:</span> <span class="string">$._config.containers.ravendb.image,</span></span><br><span class="line">                <span class="attr">ports:</span> <span class="string">[&#123;</span> <span class="attr">containerPort:</span> <span class="number">8080</span> <span class="string">&#125;,</span> <span class="string">&#123;</span> <span class="attr">containerPort:</span> <span class="number">38888</span> <span class="string">&#125;],</span></span><br><span class="line">                <span class="attr">volumeMounts:</span> <span class="string">[</span></span><br><span class="line">                  <span class="string">&#123;</span></span><br><span class="line">                    <span class="attr">name:</span> <span class="string">'data'</span><span class="string">,</span></span><br><span class="line">                    <span class="attr">mountPath:</span> <span class="string">'/var/lib/ravendb/data'</span><span class="string">,</span></span><br><span class="line">                  <span class="string">&#125;,</span></span><br><span class="line">                <span class="string">],</span></span><br><span class="line">                <span class="attr">env:</span> <span class="string">[</span></span><br><span class="line">                  <span class="string">&#123;</span></span><br><span class="line">                    <span class="attr">name:</span> <span class="string">'RAVEN_Setup_Mode'</span><span class="string">,</span></span><br><span class="line">                    <span class="attr">value:</span> <span class="string">'None'</span><span class="string">,</span></span><br><span class="line">                  <span class="string">&#125;,</span></span><br><span class="line">                  <span class="string">&#123;</span></span><br><span class="line">                    <span class="attr">name:</span> <span class="string">'RAVEN_License_Eula_Accepted'</span><span class="string">,</span></span><br><span class="line">                    <span class="attr">value:</span> <span class="string">'true'</span><span class="string">,</span></span><br><span class="line">                  <span class="string">&#125;,</span></span><br><span class="line">                  <span class="string">&#123;</span></span><br><span class="line">                    <span class="attr">name:</span> <span class="string">'RAVEN_ARGS'</span><span class="string">,</span></span><br><span class="line">                    <span class="attr">value:</span> <span class="string">'--log-to-console'</span><span class="string">,</span></span><br><span class="line">                  <span class="string">&#125;,</span></span><br><span class="line">                  <span class="string">&#123;</span></span><br><span class="line">                    <span class="attr">name:</span> <span class="string">'RAVEN_Security_UnsecuredAccessAllowed'</span><span class="string">,</span></span><br><span class="line">                    <span class="attr">value:</span> <span class="string">'PrivateNetwork'</span><span class="string">,</span></span><br><span class="line">                  <span class="string">&#125;,</span></span><br><span class="line">                <span class="string">],</span></span><br><span class="line">              <span class="string">&#125;,</span></span><br><span class="line">            <span class="string">],</span></span><br><span class="line">            <span class="attr">volumes:</span> <span class="string">[</span></span><br><span class="line">              <span class="string">&#123;</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">'data'</span><span class="string">,</span></span><br><span class="line">                <span class="attr">persistentVolumeClaim:</span> <span class="string">&#123;</span></span><br><span class="line">                  <span class="attr">claimName:</span> <span class="string">$._config.containers.ravendb.name,</span></span><br><span class="line">                <span class="string">&#125;,</span></span><br><span class="line">              <span class="string">&#125;,</span></span><br><span class="line">            <span class="string">],</span></span><br><span class="line">          <span class="string">&#125;,</span></span><br><span class="line">        <span class="string">&#125;,</span></span><br><span class="line">      <span class="string">&#125;,</span></span><br><span class="line">    <span class="string">&#125;,</span></span><br><span class="line"><span class="string">...</span></span><br></pre></td></tr></table></figure><p>This generated yml like</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><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">service-control-ravendb</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">service-control-ravendb</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">wigglepiggle-dev</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">service-control</span></span><br><span class="line">      <span class="attr">environment:</span> <span class="string">dev</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">service-control</span></span><br><span class="line">        <span class="attr">environment:</span> <span class="string">dev</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">RAVEN_Setup_Mode</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">None</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">RAVEN_License_Eula_Accepted</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">"true"</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">RAVEN_ARGS</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">--log-to-console</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">RAVEN_Security_UnsecuredAccessAllowed</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">PrivateNetwork</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">ravendb/ravendb:6.0-latest</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">service-control-ravendb</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">38888</span></span><br><span class="line">        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">mountPath:</span> <span class="string">/var/lib/ravendb/data</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">      <span class="attr">securityContext:</span></span><br><span class="line">        <span class="attr">fsGroup:</span> <span class="number">999</span></span><br><span class="line">        <span class="attr">fsGroupChangePolicy:</span> <span class="string">OnRootMismatch</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">        <span class="attr">persistentVolumeClaim:</span></span><br><span class="line">          <span class="attr">claimName:</span> <span class="string">service-control-ravendb</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I needed to get Particular Service Control up and running on our k8s cluster this week. Part of that is to get an instance of RavenDB run
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Where is the disk space?</title>
    <link href="https://westerndevs.com/_/where-is-the-space/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/where-is-the-space/</id>
    <published>2024-11-10T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.912Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>I had a report today from somebody unable to log into our Grafana instance. Super weird because this this has been running fine for months and we haven't touched it. So I jumped onto the machine to see what was up. First up was just looking at the logs from Grafana.</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker logs -f --tail <span class="number">10</span> <span class="number">5</span>efd3ee0074a</span><br></pre></td></tr></table></figure><p>There in the logs was the culprit <code>No space left on device</code>. Uh oh, what's going on here? Sure enough the disk was full.</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">df -h</span></span><br></pre></td></tr></table></figure><figure class="highlight angelscript"><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></pre></td><td class="code"><pre><span class="line">Filesystem                         Size  Used Avail Use% Mounted on</span><br><span class="line">tmpfs                              <span class="number">1.6</span>G  <span class="number">1.9</span>M  <span class="number">1.6</span>G   <span class="number">1</span>% /run</span><br><span class="line">/dev/mapper/ubuntu--vg-ubuntu--lv   <span class="number">96</span>G   <span class="number">93</span>G     <span class="number">0</span> <span class="number">100</span>% /</span><br><span class="line">tmpfs                              <span class="number">7.8</span>G     <span class="number">0</span>  <span class="number">7.8</span>G   <span class="number">0</span>% /dev/shm</span><br><span class="line">tmpfs                              <span class="number">5.0</span>M     <span class="number">0</span>  <span class="number">5.0</span>M   <span class="number">0</span>% /run/lock</span><br><span class="line">/dev/sda2                          <span class="number">2.0</span>G  <span class="number">182</span>M  <span class="number">1.7</span>G  <span class="number">10</span>% /boot</span><br><span class="line">tmpfs                              <span class="number">1.6</span>G   <span class="number">12</span>K  <span class="number">1.6</span>G   <span class="number">1</span>% /run/user/<span class="number">1001</span></span><br></pre></td></tr></table></figure><p>This stuff is always annoying because you get to a point where you can't run any commands because there is no space left. I started with cleaning up some small parts of docker</p><p><code>docker system prune -a</code></p><p>Then cleaned up docker logs</p><p><code>sudo truncate -s 0 /var/lib/docker/containers/**/*-json.log</code></p><p>This then gave me enough space to run <code>docker system df</code> and see where the space was being used. Containers were the culprit. So next was to run</p><p><code>docker ps --size</code></p><p>Which showed me the web scraper container had gone off the rails and was using over a 100GiB of space.</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">7</span>cf14084c56a   webscraper:latest       <span class="string">"wsce start -v -y"</span>       <span class="number">7</span> weeks ago   Up <span class="number">20</span> minutes               <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>:<span class="number">7002</span>-&gt;<span class="number">9924</span>/tcp, [::]:<span class="number">7002</span>-&gt;<span class="number">9924</span>/tcp                                webscraper       <span class="number">123</span>GB (virtual <span class="number">125</span>GB)</span><br></pre></td></tr></table></figure><p>This thing is supposed to be stateless so I just killed an removed it.</p><figure class="highlight properties"><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="attr">docker</span> <span class="string">kill 7cf14084c56a</span></span><br><span class="line"><span class="attr">docker</span> <span class="string">rm 7cf14084c56a</span></span><br><span class="line"><span class="attr">docker</span> <span class="string">compose up -d webscraper</span></span><br></pre></td></tr></table></figure><p>After a few minutes these completed and all was good again. So we'll keep an eye on that service and perhaps reboot it every few months to keep it in check.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I had a report today from somebody unable to log into our Grafana instance. Super weird because this this has been running fine for month
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Consuming Github Packages in Yarn</title>
    <link href="https://westerndevs.com/_/consuming-github-packges-in-yarn/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/consuming-github-packges-in-yarn/</id>
    <published>2024-11-05T05:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.903Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>My life is never without adventure but unfortunately it isn't the living on a beach sort of adventure. No it's the installing yarn packages. I wanted to have a package installed in my project which was one I'd published from another repository. In this case the package was called <code>@stimms/uicomponents</code>. There were a few tricks to getting Github actions to be able to pull the package: first I needed to create a .yarnrc.yml file. This gives yarn instructions about where it should look for packages.</p><figure class="highlight yml"><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"><span class="attr">nodeLinker:</span> <span class="string">node-modules</span></span><br><span class="line"></span><br><span class="line"><span class="attr">npmScopes:</span></span><br><span class="line">  <span class="attr">stimms:</span></span><br><span class="line">    <span class="attr">npmRegistryServer:</span> <span class="string">"https://npm.pkg.github.com"</span></span><br><span class="line">    </span><br><span class="line"><span class="attr">npmRegistries:</span></span><br><span class="line">  <span class="attr">"https://npm.pkg.github.com":</span></span><br><span class="line">    <span class="attr">npmAlwaysAuth:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Now in the build I needed to add a step in to populate the GITHUB_TOKEN which can be used for authentication. I found quite a bit of documentation which suggested that the .yarnrc.yml file would be able to read the environment variable but I had no luck with that approach. Instead I added a step to the build to populate the GITHUB_TOKEN in the .npmrc file.</p><figure class="highlight yml"><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"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">GitHub</span> <span class="string">Packages</span> <span class="string">Auth</span></span><br><span class="line">    <span class="attr">run:</span> <span class="string">echo</span> <span class="string">"//npm.pkg.github.com/:_authToken=$&#123;GITHUB_TOKEN&#125;"</span> <span class="string">&gt;</span> <span class="string">~/.npmrc</span></span><br><span class="line">    <span class="attr">env:</span></span><br><span class="line">    <span class="attr">GITHUB_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span></span><br><span class="line">    <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line">    <span class="string">yarn</span> <span class="string">install</span></span><br><span class="line">    <span class="string">yarn</span> <span class="string">lint</span></span><br><span class="line">    <span class="string">yarn</span> <span class="string">build</span></span><br></pre></td></tr></table></figure><p>The final thing to remember is that by default the GITHUB_TOKEN here doesn't have read permission over your packages. You'll need to go into the package settings and add the repository to the list of repositories which can use the package. You just need read access.  If you don't do this step you're going to see an error like <code>error Error: https://npm.pkg.github.com/@stimms%2fuicomponents: authentication token not provided</code></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;My life is never without adventure but unfortunately it isn&#39;t the living on a beach sort of adventure. No it&#39;s the installing yarn packag
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Fast Endpoints Listen Port</title>
    <link href="https://westerndevs.com/_/fast-endpoints-port/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/fast-endpoints-port/</id>
    <published>2024-10-12T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.905Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>In order to set the listening port for Fast Endpoints you can use the same mechanism as a regular ASP.NET application. This invovles setting the Urls setting in the appsettings.json file. My file looks like this:</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"Logging"</span>: &#123;</span><br><span class="line">    <span class="attr">"LogLevel"</span>: &#123;</span><br><span class="line">      <span class="attr">"Default"</span>: <span class="string">"Information"</span>,</span><br><span class="line">      <span class="attr">"Microsoft.AspNetCore"</span>: <span class="string">"Warning"</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">"Urls"</span>: <span class="string">"http://0.0.0.0:8080"</span>,</span><br><span class="line">  <span class="attr">"AllowedHosts"</span>: <span class="string">"*"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;In order to set the listening port for Fast Endpoints you can use the same mechanism as a regular ASP.NET application. This invovles sett
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">Update outdated Nuget packages</title>
    <link href="https://westerndevs.com/_/update-outdated-packages/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/update-outdated-packages/</id>
    <published>2024-09-15T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.911Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>If you're using Visual Studio Code to develop C# applications, you might need to update outdated Nuget packages. You can do that without having to do each one individually on the command line using <a href="https://github.com/dotnet-outdated/dotnet-outdated" target="_blank" rel="noopener">dotnet outdated</a></p><p>Install it with</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet tool install --global dotnet-outdated</span><br></pre></td></tr></table></figure><p>Then you can run it in the root of your project to list the packages which will be updated with</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet outdated</span><br></pre></td></tr></table></figure><p>Then, if you're happy, run it again with</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet outdated -u</span><br></pre></td></tr></table></figure><p>to actually get everything updated.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;If you&#39;re using Visual Studio Code to develop C# applications, you might need to update outdated Nuget packages. You can do that without 
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">NServiceBus Kata 6 - When things go wrong</title>
    <link href="https://westerndevs.com/_/nservicebus-kata-6/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/nservicebus-kata-6/</id>
    <published>2024-09-14T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>So far in this series things have been going pretty well. We've looked at sending messages, publishing messages, switching transports, long running processes, and timeouts. But what happens when things go wrong? In this kata we're going to look at how to handle errors in NServiceBus.</p><a id="more"></a><ul><li>Kata 1 - <a href="https://www.westerndevs.com/_/nservicebus-kata-1" target="_blank" rel="noopener">Sending a message</a></li><li>Kata 2 - <a href="https://www.westerndevs.com/_/nservicebus-kata-2" target="_blank" rel="noopener">Publishing a message</a></li><li>Kata 3 - <a href="https://www.westerndevs.com/_/nservicebus-kata-3" target="_blank" rel="noopener">Switching transports</a></li><li>Kata 4 - <a href="https://www.westerndevs.com/_/nservicebus-kata-4" target="_blank" rel="noopener">Long running processes</a></li><li>Kata 5 - <a href="https://www.westerndevs.com/_/nservicebus-kata-5" target="_blank" rel="noopener">Timeouts</a></li><li>Kata 6 - When things go wrong</li></ul><p>NServiceBus is built to be reliable. It is designed to be able to handle errors and to be able to recover from them. The most common reason for an error to show up in NServiceBus is that a message fails to process. This could be because the message is malformed, a resource which is needed to process the message is unavailable or because the handler throws an exception. Throwing exceptions in handlers is actually the preferred way to handle errors in NServiceBus.</p><p>When a message fails to process NServiceBus will attempt to reprocess the message. NServiceBus defines two different retry mechanisms: immediate and delayed retry. Immediate retries will reprocess at once and by default happen 5 times. Delayed retries fire at 10, 20 and 30 second after the failed retries and they too will fire immediate retries. So by the time a message has fully failed it's been attempted 20 times. If all these attempts fail then the message is shuffled off to an error queue. This error queue is not typically watched by NService Bus and recovering a message form it is a manual process.</p><p>In NServiceBus installations I've worked on we monitor the error queues fairly closely and work to make the application resilient to errors such that the queues are only populated by messages which have failed due to resources being unavailable. Don't just ignore these messages as they are likely important user interactions or business processes which will cost you money if you don't address them.</p><h1>The Kata</h1><p>Let's make one of our messages fail to process and watch what happens. In this scenario let's fail to properly sanitize our inputs to the messages and have them take on a value which makes no sense. We have a message <code>EatCake</code> which has a property <code>NumberOfCakes</code>. Let's make it so that the <code>NumberOfCakes</code> is set to -1 and that we have a validation in place in our message handler to throw when we see this problem. Let's also log a message so we can see the retries happening.</p><h1>My Solution</h1><ol><li>Modify the <code>EatCake</code> message hander to have a validation to make sure cakes are not eaten in the negative.</li></ol><figure class="highlight arduino"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> async <span class="built_in">Task</span> <span class="title">Handle</span><span class="params">(EatCake message, IMessageHandlerContext context)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (message.NumberOfCakes &lt; <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">Console</span>.WriteLine(<span class="string">"Negative cake? Really?"</span>);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Exception(<span class="string">"Negative cake? Really?"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (message.NumberOfCakes == <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">Console</span>.WriteLine(<span class="string">"No cake to eat"</span>);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Exception(<span class="string">"No cake to eat"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">Console</span>.WriteLine($<span class="string">"Cake eaten, NumberOfCakes = &#123;message.NumberOfCakes&#125;; Flavour = &#123;message.Flavour&#125;"</span>);</span><br><span class="line">        await context.Publish(<span class="keyword">new</span> CakeEaten</span><br><span class="line">        &#123;</span><br><span class="line">            EatingFinishedAt = DateTime.Now</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>Modify the sender to send a message with a negative number of cakes</li></ol><figure class="highlight arduino"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> (<span class="built_in">line</span>)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">"send"</span>:</span><br><span class="line">        await endpointInstance.Send(<span class="string">"NServiceBusKataReceiver"</span>, <span class="keyword">new</span> EatCake</span><br><span class="line">        &#123;</span><br><span class="line">            Flavour = <span class="string">"Coconut"</span>,</span><br><span class="line">            NumberOfCakes = <span class="number">-1</span></span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">        ...</span><br></pre></td></tr></table></figure><ol start="3"><li>You may need to configure the error queue in the receiver endpoint.</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">endpointConfiguration.SendFailedMessagesTo(<span class="string">"NServiceBusKataReceiverError"</span>)<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>And build the error queue using the handy dandy rabbitmq-transport tool</p><figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">rabbitmq</span>-trans<span class="keyword">port</span> endpoint create NServiceBusKataAnotherReceiver -c "host=localhost" <span class="comment">--errorQueue NServiceBusKataAnotherReceiverError</span></span><br></pre></td></tr></table></figure><p>Things to try now</p><ol><li>Run the sender and produce some messages with negative numbers of cakes</li><li>Observe the logs for the receiver and see the retries happening</li><li>Wait a bit for the retries to be exhausted and see the message end up in the error queue</li><li>Open up the rabbitmq management console and look at the error queue. Pay special attention to the headers of the message. You'll see the number of retries and the time the message was first sent as well as the exception message.</li></ol><h1>Things to think about</h1><p>Looking at messages in the RabbitMQ management console is great for a single message but in the aggregate there is often a need for better tooling. Particular provide some tooling in the form of ServiceInsight which can be used to monitor the flow of messages through the system. This can be used to see where messages are failing and to help diagnose the problem.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;So far in this series things have been going pretty well. We&#39;ve looked at sending messages, publishing messages, switching transports, long running processes, and timeouts. But what happens when things go wrong? In this kata we&#39;re going to look at how to handle errors in NServiceBus.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">NServiceBus Kata 5 - Timeouts</title>
    <link href="https://westerndevs.com/_/nservicebus-kata-5/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/nservicebus-kata-5/</id>
    <published>2024-09-09T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>In the previous kata we looked at sagas which are a way to coordinate long running processes. In this kata we're going to look at another tool in the NServiceBus toolbox: timeouts. Timeouts are a way to schedule a message to be sent at some point in the future. This is a powerful tool for building out complex processes.</p><a id="more"></a><ul><li>Kata 1 - <a href="https://www.westerndevs.com/_/nservicebus-kata-1" target="_blank" rel="noopener">Sending a message</a></li><li>Kata 2 - <a href="https://www.westerndevs.com/_/nservicebus-kata-2" target="_blank" rel="noopener">Publishing a message</a></li><li>Kata 3 - <a href="https://www.westerndevs.com/_/nservicebus-kata-3" target="_blank" rel="noopener">Switching transports</a></li><li>Kata 4 - <a href="https://www.westerndevs.com/_/nservicebus-kata-4" target="_blank" rel="noopener">Long running processes</a></li><li>Kata 5 - Timeouts</li><li>Kata 6 - <a href="https://www.westerndevs.com/_/nservicebus-kata-6" target="_blank" rel="noopener">When things go wrong</a></li></ul><p>I think we're pretty used to the idea that a timeout is something we want to avoid - it usually means that a server isn't available or that we've run a query that is taking too long. But there are lots of places in business processes where timeout are just part of the process we're modeling and aren't an error at all. As an example consider a shopping cart: if a user adds an item to the cart and then doesn't check out within a certain period of time we might want to send them a reminder and even offer them a discount on their purchases. If we've modeled the checkout process as a saga then this reminder can be set up as a timeout.</p><h1>The Kata</h1><p>We just implemented a cake order saga in the last kata. Let's extend that saga to include a timeout. If the cake isn't shipped within 2 minutes of the order being placed then we should send a message to the bakery to ask them to check on the order. Obviously 2 minutes is a pretty quick turn around on a cake but I don't imagine you want to hang around for hours writing this kata.</p><h1>My Solution</h1><ol><li>Add a new message to the messages project</li></ol><figure class="highlight csharp"><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"><span class="keyword">namespace</span> <span class="title">messages</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeOrderStalled</span> : <span class="title">IMessage</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Guid OrderId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>Modify the saga to start a timeout when an order is placed. The CakeOrderPlaced handler becomes</li></ol><figure class="highlight csharp"><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="function"><span class="keyword">public</span> Task <span class="title">Handle</span>(<span class="params">CakeOrderPlaced message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;message.OrderId&#125;</span> placed"</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> RequestTimeout&lt;CakeOrderStalled&gt;(context, TimeSpan.FromMinutes(<span class="number">2</span>), <span class="keyword">new</span> CakeOrderStalled&#123; OrderId = message.OrderId &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>Add a handler for the timeout message</li></ol><figure class="highlight csharp"><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="function"><span class="keyword">public</span> Task <span class="title">Timeout</span>(<span class="params">CakeOrderStalled state, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;Data.OrderId&#125;</span> stalled!"</span>);</span><br><span class="line">    <span class="keyword">return</span> Task.CompletedTask;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>Have the saga implement</li></ol><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IHandleTimeouts&lt;CakeOrderStalled&gt;</span><br></pre></td></tr></table></figure><ol start="4"><li>We missed it previously but we need to have canceling the order or completing the order mark the saga as complete. To do that add <code>MarkAsComplete</code> to the handlers for those messages</li></ol><figure class="highlight csharp"><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="function"><span class="keyword">public</span> Task <span class="title">Handle</span>(<span class="params">CakeOrderCanceled message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;message.OrderId&#125;</span> canceled"</span>);</span><br><span class="line">    MarkAsComplete();</span><br><span class="line">    <span class="keyword">return</span> Task.CompletedTask;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Things to try now</p><ol><li>Run the applications and in the sender app try starting the saga with <code>o</code> to start an order. Wait a couple of minutes and see that the timeout fires.</li><li>Try starting the saga and then canceling it with <code>o</code> followed by <code>c</code> and see what happens when the timeout fires.</li></ol><h1>Things to think about</h1><p>Once a timeout is registered there is no way to cancel it. How come? What would you do if you needed to cancel a timeout? If messages are delayed is it possible that timeouts could fire when an expected action has actually happened? How would you mitigate that risk?</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;In the previous kata we looked at sagas which are a way to coordinate long running processes. In this kata we&#39;re going to look at another tool in the NServiceBus toolbox: timeouts. Timeouts are a way to schedule a message to be sent at some point in the future. This is a powerful tool for building out complex processes.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">NServiceBus Kata 4 - Long Running Processes</title>
    <link href="https://westerndevs.com/_/nservicebus-kata-4/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/nservicebus-kata-4/</id>
    <published>2024-09-02T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>We now have a pretty solid way to send messages, publish messages and we've got those messages flowing over a reliable transport mechanism. Sending and publishing individual messages only gets us so far. We often need a way to coordinate complex processes which involve multiple services. For this NServiceBus has the concept of sagas which some might call process managers.</p><a id="more"></a><ul><li>Kata 1 - <a href="https://www.westerndevs.com/_/nservicebus-kata-1" target="_blank" rel="noopener">Sending a message</a></li><li>Kata 2 - <a href="https://www.westerndevs.com/_/nservicebus-kata-2" target="_blank" rel="noopener">Publishing a message</a></li><li>Kata 3 - <a href="https://www.westerndevs.com/_/nservicebus-kata-3" target="_blank" rel="noopener">Switching transports</a></li><li>Kata 4 - Long running processes</li><li>Kata 5 - <a href="https://www.westerndevs.com/_/nservicebus-kata-5" target="_blank" rel="noopener">Timeouts</a></li><li>Kata 6 - <a href="https://www.westerndevs.com/_/nservicebus-kata-6" target="_blank" rel="noopener">When things go wrong</a></li></ul><p>Consider the bakery which is creating the cake for me to eat: when it starts making a new cake because I ate the last one it has a bunch of things it needs to coordinate. They need to preheat ovens, gather ingredients, mix ingredients, grease pans, fill pans, put pans in the oven, remember to take the cake out, cool it, ice it... the list goes on and on - no wonder cake is so expensive. Coordinating all these activities is complex in a distributed system, really in any system. There are a lot of corner cases that we usually fail to consider in a non-distributed system which become much more apparent when building out a process manager. What if we preheat the oven but then discover that we're all out of flour? In that monolithic system we might throw an exception and hope that somebody is monitoring for it in a log file somewhere. Realistically that's never going to happen. In the meantime nobody has shut the oven off and the bakery burns down.</p><p>A saga allows us to store the state of a process, to react to messages as they come in and send new messages. We use this to coordinate the activities of the bakery. In our example above we can probably call the process &quot;BakeCakeSaga&quot;. When messages come in relating to the order then we need to be able to find a way to look up the state and make modifications to it. NServiceBus implements this through a method called <code>ConfigureHowToFindSaga</code>. This function will provide a mapping from every message that interacts with the saga to find the saga data. For our example we'd probably use something like an order id.</p><p>Let's build out a very simple saga which responds to just a few messages in our system so we can see how it works. Saga can get pretty complex but they are quite testable so that's nice.</p><h1>The Kata</h1><p>Create a saga which handles the messages <code>CakeOrderPlaced</code>, <code>CakeOrderCanceled</code>, <code>CakeOrderShipped</code>. Each of these messages will contain an <code>OrderId</code>, a GUID, which will be used to identify the saga as well as whatever information might be associated with those messages. For now just write out to the console when each of these messages is received - unless you want to bake me a cake which I will accept.</p><h1>The Solution</h1><ol><li>Add some mechanism to handle the persistence of saga data. For now we'll just use the in learning persistence. In the various program.cs files add</li></ol><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> persistence = endpointConfiguration.UsePersistence&lt;LearningPersistence&gt;();</span><br></pre></td></tr></table></figure><ol start="2"><li>Create messages classes in the messages project</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">messages</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeOrderPlaced</span> : <span class="title">IEvent</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Guid OrderId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> Guid CustomerId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> Date OrderDate &#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="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeOrderCanceled</span> : <span class="title">IEvent</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Guid OrderId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">string</span> Reason &#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="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeOrderShipped</span> : <span class="title">IEvent</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Guid OrderId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">string</span> ShippingReferenceNumber &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>Create a saga class in the receiver project</li></ol><figure class="highlight csharp"><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="keyword">using</span> NServiceBus;</span><br><span class="line"><span class="keyword">using</span> messages;</span><br><span class="line">public class CakeOrderSaga : Saga&lt;CakeOrderSagaData&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;CakeOrderCanceled&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;CakeOrderPlaced&gt;,</span><br><span class="line">    IAmStartedByMessages&lt;CakeOrderShipped&gt;</span><br><span class="line">&#123;</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;CakeOrderSagaData&gt; mapper</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        mapper.MapSaga(sagaData =&gt; sagaData.OrderId)</span><br><span class="line">            .ToMessage&lt;CakeOrderCanceled&gt;(x =&gt; x.OrderId)</span><br><span class="line">            .ToMessage&lt;CakeOrderPlaced&gt;(x =&gt; x.OrderId)</span><br><span class="line">            .ToMessage&lt;CakeOrderShipped&gt;(x =&gt; x.OrderId);</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">CakeOrderPlaced message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;message.OrderId&#125;</span> placed"</span>);</span><br><span class="line">        <span class="keyword">return</span> Task.CompletedTask;</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">CakeOrderCanceled message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;message.OrderId&#125;</span> canceled"</span>);</span><br><span class="line">        Data.OrderCanceled = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">return</span> Task.CompletedTask;</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">CakeOrderShipped message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Order <span class="subst">&#123;message.OrderId&#125;</span> shipped"</span>);</span><br><span class="line">        Data.OrderShipped = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">return</span> Task.CompletedTask;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>Add a saga data class to the receiver project</li></ol><figure class="highlight csharp"><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"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeOrderSagaData</span> : <span class="title">ContainSagaData</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Guid OrderId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">bool</span> OrderCanceled &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">bool</span> OrderShipped &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>Modify the sender project's program.cs to send the messages adding a loop which sends all the different messages involved in the saga.</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">bool</span> continueMessages = <span class="literal">true</span>;</span><br><span class="line">Guid orderId = Guid.NewGuid();</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (continueMessages)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> line = Console.ReadLine();</span><br><span class="line">    <span class="keyword">switch</span> (line)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"p"</span>:</span><br><span class="line">            <span class="keyword">await</span> endpointInstance.Publish(<span class="keyword">new</span> CakeOrderPlaced &#123; OrderId = orderId &#125;);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"c"</span>:</span><br><span class="line">            <span class="keyword">await</span> endpointInstance.Publish(<span class="keyword">new</span> CakeOrderCanceled &#123; OrderId = orderId &#125;);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"s"</span>:</span><br><span class="line">            <span class="keyword">await</span> endpointInstance.Publish(<span class="keyword">new</span> CakeOrderShipped &#123; OrderId = orderId &#125;);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"q"</span>:</span><br><span class="line">            continueMessages = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Things to try now</p><ol><li>Run the applications and in the sender app try pressing some keys like <code>p</code> or <code>c</code> or <code>s</code> to see the messages being handled by the saga.</li><li>Try starting the applications in different orders and see how the saga handles the messages.</li></ol><h1>Things to Notice</h1><p>Notice that the Saga can be started by 3 different events. Why would a saga be started by a cancel message? You can't cancel an order which hasn't even been placed yet - right? Well it turns out you can. Without some serious hoop jumping through the order of message delivery it not guaranteed. So in fact orders can be canceled or shipped before we get the message telling us the order has been placed. It is sort of mind-blowing.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;We now have a pretty solid way to send messages, publish messages and we&#39;ve got those messages flowing over a reliable transport mechanism. Sending and publishing individual messages only gets us so far. We often need a way to coordinate complex processes which involve multiple services. For this NServiceBus has the concept of sagas which some might call process managers.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">NServiceBus Kata 3 - Switching transports</title>
    <link href="https://westerndevs.com/_/nservicebus-kata-3/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/nservicebus-kata-3/</id>
    <published>2024-09-01T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>In the previous article we looked at publishing messages and the one before that sending messages. But in both cases we cheated a little bit: we used the LearningTransport. This is effectively just a directory on disk. It cannot be used as real world transport. Let's change out this transport for something more production ready.</p><a id="more"></a><ul><li>Kata 1 - <a href="https://www.westerndevs.com/_/nservicebus-kata-1" target="_blank" rel="noopener">Sending a message</a></li><li>Kata 2 - <a href="https://www.westerndevs.com/_/nservicebus-kata-2/" target="_blank" rel="noopener">Publishing a message</a></li><li>Kata 3 - Switching transports</li><li>Kata 4 - <a href="https://www.westerndevs.com/_/nservicebus-kata-4/" target="_blank" rel="noopener">Long running processes</a></li><li>Kata 5 - <a href="https://www.westerndevs.com/_/nservicebus-kata-5" target="_blank" rel="noopener">Timeouts</a></li><li>Kata 6 - <a href="https://www.westerndevs.com/_/nservicebus-kata-6" target="_blank" rel="noopener">When things go wrong</a></li></ul><p>One of the really nice things about NServiceBus is that it abstracts away a lot of the mess of dealing with transports. You can spend less time fiddling with plumbing and more time getting to the nitty gritty of building business value. We have a lot of options for transport and the ability to write new ones if needed. The official supported transports are</p><ul><li>Azure Service Bus</li><li>Azure Storage Queues</li><li>Amazon SQS</li><li>RabbitMQ</li><li>SQL Server</li><li>MSMQ</li></ul><p>Each of these has advantages and disadvantages which you can read about in some detail in the <a href="https://docs.particular.net/transports/" target="_blank" rel="noopener">documentation</a>. In a production environment I'd push heavily towards Azure Service Bus but it has a major disadvantage: no local emulator. There is a github issue open to <a href="https://github.com/Azure/azure-service-bus/issues/223" target="_blank" rel="noopener">add an emulator</a> which has been open since 2018. Normally I'd grumble a bit about how this is never going to be solved but actually the team has dedicated resources to building one out and think they'll have one ready by the end of 2024. Only you, dear readers from the future, know if this came to fruition.</p><p>For now we're going to take advantage of the interchangeability of transports and switch out the LearningTransport for the RabbitMQ transport. This can be run with a container on docker.</p><h1>The Kata</h1><p>Take the solution developed already and switch out the learning transport for the RabbitMQ transport running inside of a container. Everything should continue to work as it did before but now using RabbitMQ.</p><h1>My Solution</h1><ol><li>Download and run the RabbitMQ container</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --hostname nsbkata --name nsbkata-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management</span><br></pre></td></tr></table></figure><ol start="2"><li>Install the RabbitMQ transport package in all the projects</li></ol><figure class="highlight bash"><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">dotnet add sender package NServiceBus.RabbitMQ</span><br><span class="line">dotnet add receiver package NServiceBus.RabbitMQ</span><br><span class="line">dotnet add anotherReceiver package NServiceBus.RabbitMQ</span><br></pre></td></tr></table></figure><ol start="3"><li>Modify the endpoint configurations across all the projects swapping</li></ol><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> transport = endpointConfiguration.UseTransport&lt;LearningTransport&gt;();</span><br></pre></td></tr></table></figure><p>with</p><figure class="highlight csharp"><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="keyword">var</span> transport = endpointConfiguration.UseTransport&lt;RabbitMQTransport&gt;();</span><br><span class="line">transport.ConnectionString(<span class="string">"host=localhost"</span>);</span><br><span class="line">transport.UseConventionalRoutingTopology(QueueType.Quorum);</span><br></pre></td></tr></table></figure><ol start="4"><li>It is generally best that you take control of creating queues for NServiceBus but tooling is provided. Install that tooling</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet tool install -g NServiceBus.Transport.RabbitMQ.CommandLine</span><br></pre></td></tr></table></figure><p>Next create the queues for the endpoints (and the delays queues)</p><figure class="highlight bash"><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">rabbitmq-transport delays create  -c <span class="string">"host=localhost"</span></span><br><span class="line">rabbitmq-transport endpoint create NServiceBusKataAnotherReceiver -c <span class="string">"host=localhost"</span></span><br><span class="line">rabbitmq-transport endpoint create NServiceBusKataReceiver -c <span class="string">"host=localhost"</span></span><br><span class="line">rabbitmq-transport endpoint create NServiceBusKataSender -c <span class="string">"host=localhost"</span></span><br></pre></td></tr></table></figure><p>Things to try now</p><ol><li><p>Run the sender, receiver and anotherReceiver projects. You should see the messages being sent and received as before.</p></li><li><p>Log into the RabbitMQ management console(running at http://localhost:15672/ with credentials <code>guest</code>/<code>guest</code>) you should see the queues created and in fact a message processed</p></li></ol><p><img src="../images/nservicebus-kata-3/2024-09-01-12-09-20.png" alt="A RabbitMQ queue"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;In the previous article we looked at publishing messages and the one before that sending messages. But in both cases we cheated a little bit: we used the LearningTransport. This is effectively just a directory on disk. It cannot be used as real world transport. Let&#39;s change out this transport for something more production ready.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title type="html">NServiceBus Kata 2</title>
    <link href="https://westerndevs.com/_/nservicebus-kata-2/" rel="alternate" type="text/html"/>
    <id>https://westerndevs.com/_/nservicebus-kata-2/</id>
    <published>2024-08-31T04:00:00.000Z</published>
    <updated>2026-11-04T22:25:01.907Z</updated>
	<author>
	
	  
	  <name>Simon Timms</name>
	  <email>stimms@gmail.com</email>
	
	  <uri>https://westerndevs.com</uri>
	</author>
    
    <content type="html"><![CDATA[<p>In the previous kata we sent a message from one application to another. This is a common pattern in messaging systems. In this kata we're going to look at a different pattern: publishing a message.</p><a id="more"></a><ul><li>Kata 1 - <a href="https://blog.simontimms.com/2024/08/30/nservicebus-kata-1" target="_blank" rel="noopener">Sending a message</a></li><li>Kata 2 - Publishing a message</li><li>Kata 3 - <a href="https://www.westerndevs.com/_/nservicebus-kata-3/" target="_blank" rel="noopener">Switching transports</a></li><li>Kata 4 - <a href="https://www.westerndevs.com/_/nservicebus-kata-4/" target="_blank" rel="noopener">Long running processes</a></li><li>Kata 5 - <a href="https://www.westerndevs.com/_/nservicebus-kata-5" target="_blank" rel="noopener">Timeouts</a></li><li>Kata 6 - <a href="https://www.westerndevs.com/_/nservicebus-kata-6" target="_blank" rel="noopener">When things go wrong</a></li></ul><h1>The Problem</h1><p>It is great being able to send a message from one system to another - you can instruct a remote system to take some action which you don't know how to do. For instance sending an email. In a large system lots of processes will likely result in an email but if you can centralize the logic around how to send an email it makes it very easy to do things like change email providers as that functionality is isolated and independent.</p><p>If you looked at my solution for the first Kata then you might have notice that I named my message <code>EatCake</code> this is named in the imperative form. This is a common pattern in messaging systems and is, I think, remarkably helpful to understand the command pattern. When you issue a command you know exactly who or what you're commanding and in the same way the command is sent to a known endpoint.</p><p>Sometimes, however, you don't have a particular target in mind and you just want to let other systems know that you have done something. In this case you can publish a message. Unlike a command you don't know who is going to act on it - it may be nobody or it may be multiple people. In a messaging system we call this an <code>Event</code>.</p><p>Let's extend our cake eating example to imagine some consequences of what might happen if I happily reported that I had eaten cake: <code>CakeEaten</code>. Finding out that the cake had been eaten the bakery might start baking more cake assuming that I'd want more (very likely). My gym might free up a treadmill knowing that my cake-induced guilt might drive me to run until I'd burned off the delicious butter-cream. It might also be of interest to my wife who can text me and see if I enjoyed the cake. I don't actually know who would be interested in the event so it is the responsibility of those who are interested to subscribe to the event.</p><h1>The Kata</h1><p>Using the solution to the first kata as a starting point extend the system to publish a <code>CakeEaten</code> message. Subscribe to the message in the sender and write out a message to the console. Also create another new project similar to the others and have it subscribe to the message.</p><h1>My Solution</h1><ol><li>Create a new message in the messages assembly</li></ol><figure class="highlight csharp"><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"><span class="keyword">namespace</span> <span class="title">messages</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeEaten</span> : <span class="title">IEvent</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> DateTime EatingFinishedAt &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>Modify the Hander in the receiver to publish the new event. Notice that we have access to a <code>IMessageHandlerContext</code> which we can use to publish messages.</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> messages;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">PlaceOrderHandler</span> :</span><br><span class="line">    IHandleMessages&lt;EatCake&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Handle</span>(<span class="params">EatCake message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Cake eaten, NumberOfCakes = <span class="subst">&#123;message.NumberOfCakes&#125;</span>; Flavour = <span class="subst">&#123;message.Flavour&#125;</span>"</span>);</span><br><span class="line">        <span class="keyword">await</span> context.Publish(<span class="keyword">new</span> CakeEaten</span><br><span class="line">        &#123;</span><br><span class="line">            EatingFinishedAt = DateTime.Now</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>Modify the sender to subscribe to the event by adding a Handler to the project.</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> messages;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeEatenHandler</span> :</span><br><span class="line">    IHandleMessages&lt;CakeEaten&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Task <span class="title">Handle</span>(<span class="params">CakeEaten message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Uh oh, somebody ate a cake at <span class="subst">&#123;message.EatingFinishedAt&#125;</span>"</span>);</span><br><span class="line">        <span class="keyword">return</span> Task.CompletedTask;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>Optionally modify the sender to remain running so that it can receive the message. This is done by adding a <code>Console.ReadLine()</code> at the end of the <code>Main</code> method.</li></ol><figure class="highlight csharp"><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"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line">Console.Title = <span class="string">"NServiceBusKata - Sender"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> endpointConfiguration = <span class="keyword">new</span> EndpointConfiguration(<span class="string">"NServiceBusKataReceiver"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Choose JSON to serialize and deserialize messages</span></span><br><span class="line">endpointConfiguration.UseSerialization&lt;SystemJsonSerializer&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> transport = endpointConfiguration.UseTransport&lt;LearningTransport&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> endpointInstance = <span class="keyword">await</span> Endpoint.Start(endpointConfiguration);</span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="string">"Press Enter to exit..."</span>);</span><br><span class="line">Console.ReadLine();</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> endpointInstance.Stop();</span><br></pre></td></tr></table></figure><p>Pause here and ensure that when running the sender and receiver that you see both a message sent and published.</p><ol start="5"><li>Create a new project to act as a second subscriber or receiver</li></ol><figure class="highlight routeros"><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">dotnet new<span class="built_in"> console </span>-o anotherReceiver</span><br><span class="line">dotnet <span class="builtin-name">add</span> anotherReceiver reference <span class="built_in">..</span>/messages</span><br><span class="line">dotnet <span class="builtin-name">add</span> anotherReceiver package NServiceBus</span><br></pre></td></tr></table></figure><ol start="6"><li>Add a subscriber class to the project</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> messages;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CakeEatenHandler</span> :</span><br><span class="line">    IHandleMessages&lt;CakeEaten&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Task <span class="title">Handle</span>(<span class="params">CakeEaten message, IMessageHandlerContext context</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$"Awesome, somebody ate a cake at <span class="subst">&#123;message.EatingFinishedAt&#125;</span>. Time to bake another"</span>);</span><br><span class="line">        <span class="keyword">return</span> Task.CompletedTask;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="7"><li>Update the Program.cs to initiate NServiceBus</li></ol><figure class="highlight csharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> messages;</span><br><span class="line"><span class="keyword">using</span> NServiceBus;</span><br><span class="line"></span><br><span class="line">Console.Title = <span class="string">"NServiceBusKata - AnotherPublisher"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> endpointConfiguration = <span class="keyword">new</span> EndpointConfiguration(<span class="string">"NServiceBusKataAnotherReceiver"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Choose JSON to serialize and deserialize messages</span></span><br><span class="line">endpointConfiguration.UseSerialization&lt;SystemJsonSerializer&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> transport = endpointConfiguration.UseTransport&lt;LearningTransport&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> endpointInstance = <span class="keyword">await</span> Endpoint.Start(endpointConfiguration);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Console.ReadLine();</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> endpointInstance.Stop();</span><br></pre></td></tr></table></figure><p>Things to try now:</p><ol><li>Start the receiver and the another receiver then the sender - ensure you see messages flowing correctly</li><li>Start services in different orders and see which messages might be lost</li></ol><p>Sending and receiving messages and publishing messages should now be working great. But to get this example going we've made some simplifications which we'll have to address in the next kata.</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;In the previous kata we sent a message from one application to another. This is a common pattern in messaging systems. In this kata we&#39;re going to look at a different pattern: publishing a message.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
</feed>
