<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Viktor</title>
    <description>The latest articles on DEV Community by Oleksandr Viktor (@ukrguru).</description>
    <link>https://dev.to/ukrguru</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2645949%2Fc57b0952-c7b2-4419-b654-d898c8c54abf.jpg</url>
      <title>DEV Community: Oleksandr Viktor</title>
      <link>https://dev.to/ukrguru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ukrguru"/>
    <language>en</language>
    <item>
      <title>WJb Resilient Actions: Designing Fault-Tolerant Background Jobs</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Fri, 16 Oct 2026 06:02:19 +0000</pubDate>
      <link>https://dev.to/ukrguru/wjb-resilient-actions-designing-fault-tolerant-background-jobs-hof</link>
      <guid>https://dev.to/ukrguru/wjb-resilient-actions-designing-fault-tolerant-background-jobs-hof</guid>
      <description>&lt;h1&gt;
  
  
  Resilient Actions
&lt;/h1&gt;

&lt;p&gt;One night, Cron was generating jobs as expected. The queue was accepting them as expected. The Worker was running as expected.&lt;/p&gt;

&lt;p&gt;Then a single Action got stuck.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron continued generating new jobs;&lt;/li&gt;
&lt;li&gt;the queue continued growing;&lt;/li&gt;
&lt;li&gt;other Actions stopped executing;&lt;/li&gt;
&lt;li&gt;the issue was resolved only after a restart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a classic example of how system reliability is determined not by how it behaves in a perfect world, but by how it handles failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Action Must Be Designed for Failure
&lt;/h2&gt;

&lt;p&gt;Every Action operates in a hostile environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the network may stop responding;&lt;/li&gt;
&lt;li&gt;the database may become slow;&lt;/li&gt;
&lt;li&gt;an external API may hang;&lt;/li&gt;
&lt;li&gt;the server may restart;&lt;/li&gt;
&lt;li&gt;the action may be cancelled by the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this reason, an Action should be designed as if failure is inevitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Use CancellationToken
&lt;/h2&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an operation does not support cancellation, the Worker cannot stop the Action gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Define Timeouts
&lt;/h2&gt;

&lt;p&gt;Even when a CancellationToken is used.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;External resources are not required to respond quickly.&lt;/p&gt;

&lt;p&gt;Sometimes they do not respond at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distinguish Timeouts from Cancellations
&lt;/h2&gt;

&lt;p&gt;A timeout and a cancellation are not the same thing.&lt;/p&gt;

&lt;p&gt;A timeout usually means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the operation took too long and may be safe to retry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A cancellation usually means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;execution was intentionally stopped and should not be retried.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In .NET both situations may appear as a &lt;code&gt;TaskCanceledException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TaskCanceledException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerException&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request timed out"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Action was cancelled"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this distinction, logs may show both situations as simple cancellations even though the retry decision is often the opposite.&lt;/p&gt;

&lt;p&gt;A timeout is typically a transient failure.&lt;/p&gt;

&lt;p&gt;A cancellation is typically an intentional stop request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log Execution Stages
&lt;/h2&gt;

&lt;p&gt;Often the most useful log messages are the simplest ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;ReportProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Loading data"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;ReportProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Downloaded data"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;ReportProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Saving data"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;ReportProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Completed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an Action gets stuck, the last message immediately identifies where execution stopped.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Loading data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It becomes obvious that the problem occurred before the database save phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Actions Idempotent
&lt;/h2&gt;

&lt;p&gt;After a failure, an Action may execute again.&lt;/p&gt;

&lt;p&gt;If re-execution creates duplicate data, the system is not resilient.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Tokens&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;MERGE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A repeated execution should be safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Not Store State in Memory
&lt;/h2&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Processed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value will be lost after a restart.&lt;/p&gt;

&lt;p&gt;Instead, store state in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a database;&lt;/li&gt;
&lt;li&gt;a queue;&lt;/li&gt;
&lt;li&gt;a persistent state store.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An Action should survive process restarts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break Large Operations into Smaller Actions
&lt;/h2&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;DownloadAllHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;ProcessAllHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;SaveAllHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;download
process
aggregate
publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smaller Actions are easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancel;&lt;/li&gt;
&lt;li&gt;retry;&lt;/li&gt;
&lt;li&gt;troubleshoot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verify Behavior After a Timeout
&lt;/h2&gt;

&lt;p&gt;It is not enough to verify that an Action was cancelled.&lt;/p&gt;

&lt;p&gt;You must prove that the queue continues to operate.&lt;/p&gt;

&lt;p&gt;A good integration test is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first job becomes stuck.&lt;/li&gt;
&lt;li&gt;A timeout occurs.&lt;/li&gt;
&lt;li&gt;The second job executes successfully.&lt;/li&gt;
&lt;li&gt;The queue continues processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This scenario closely matches real production behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Rule
&lt;/h2&gt;

&lt;p&gt;Whenever an Action contains an:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens if this operation hangs forever?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nothing good.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then the Action is not production-ready yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A simple, resilient Action has six characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supports &lt;code&gt;CancellationToken&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;defines reasonable timeouts;&lt;/li&gt;
&lt;li&gt;distinguishes timeouts from cancellations;&lt;/li&gt;
&lt;li&gt;logs key execution stages;&lt;/li&gt;
&lt;li&gt;is idempotent;&lt;/li&gt;
&lt;li&gt;safely handles re-execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an ideal world, failures never happen.&lt;/p&gt;

&lt;p&gt;In production, they happen every day.&lt;/p&gt;

&lt;p&gt;A good Action is not one that never fails.&lt;/p&gt;

&lt;p&gt;A good Action is one after which the system continues to operate. 🚀🐈‍⬛&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>backend</category>
      <category>programming</category>
      <category>csharp</category>
    </item>
    <item>
      <title>1M End2End: WJb vs Quartz vs Hangfire</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:24:05 +0000</pubDate>
      <link>https://dev.to/ukrguru/1m-end2end-wjb-vs-quartz-vs-hangfire-2l00</link>
      <guid>https://dev.to/ukrguru/1m-end2end-wjb-vs-quartz-vs-hangfire-2l00</guid>
      <description>&lt;h1&gt;
  
  
  Large Scale End-to-End Benchmark
&lt;/h1&gt;

&lt;p&gt;Real-world processing benchmark using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000,000 jobs
1 worker/thread
In-memory storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benchmark Environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.NET 10.0
Windows 11
Intel Core i7-12700K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workload
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EchoPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Job implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Jobs/sec&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WJb&lt;/td&gt;
&lt;td&gt;4.80 sec&lt;/td&gt;
&lt;td&gt;208,344&lt;/td&gt;
&lt;td&gt;983.72 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quartz.NET&lt;/td&gt;
&lt;td&gt;23.84 sec&lt;/td&gt;
&lt;td&gt;41,940&lt;/td&gt;
&lt;td&gt;1,292.79 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hangfire&lt;/td&gt;
&lt;td&gt;299.00 sec&lt;/td&gt;
&lt;td&gt;3,344&lt;/td&gt;
&lt;td&gt;2,518.56 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Throughput
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WJb       208,344 jobs/sec
Quartz     41,940 jobs/sec
Hangfire    3,344 jobs/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Processing Time
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000,000 Jobs

WJb          4.80 sec
Quartz      23.84 sec
Hangfire   299.00 sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Source Code &amp;amp; Reproducibility
&lt;/h3&gt;

&lt;p&gt;The complete large-scale benchmark suite, including all benchmark runners and source code, is available here:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/UkrGuru/WJb.Demo/tree/main/benchmark/LargeScale" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo/tree/main/benchmark/LargeScale&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WJb processed 1,000,000 jobs approximately 5× faster than Quartz.NET.&lt;/li&gt;
&lt;li&gt;WJb processed 1,000,000 jobs approximately 62× faster than Hangfire.&lt;/li&gt;
&lt;li&gt;WJb used approximately 31% less memory than Quartz.NET.&lt;/li&gt;
&lt;li&gt;WJb used approximately 61% less memory than Hangfire.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In-memory storage only.&lt;/li&gt;
&lt;li&gt;Single worker/thread for all frameworks.&lt;/li&gt;
&lt;li&gt;End-to-end execution includes enqueue, scheduling, execution, and completion.&lt;/li&gt;
&lt;li&gt;Results were measured using standalone console applications.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>WJb vs Quartz vs Hangfire Benchmarks</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Sat, 03 Oct 2026 08:07:50 +0000</pubDate>
      <link>https://dev.to/ukrguru/wjb-vs-quartz-vs-hangfire-benchmarks-2ohi</link>
      <guid>https://dev.to/ukrguru/wjb-vs-quartz-vs-hangfire-benchmarks-2ohi</guid>
      <description>&lt;h1&gt;
  
  
  WJb vs Quartz vs Hangfire Benchmarks
&lt;/h1&gt;

&lt;p&gt;Performance comparison of &lt;strong&gt;WJb&lt;/strong&gt;, &lt;strong&gt;Quartz.NET&lt;/strong&gt;, and &lt;strong&gt;Hangfire&lt;/strong&gt; using validated benchmarks with normalized concurrency (1 worker/thread per framework).&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark Environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BenchmarkDotNet v0.15.8
.NET 10.0
Windows 11
Intel Core i7-12700K
20 Logical Cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;Each framework processes the same payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EchoPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Job implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation tests verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All jobs complete successfully.&lt;/li&gt;
&lt;li&gt;Completion counts match expectations.&lt;/li&gt;
&lt;li&gt;Enqueue-only benchmarks do not execute jobs.&lt;/li&gt;
&lt;li&gt;End-to-end benchmarks execute every job exactly once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Concurrency Configuration
&lt;/h2&gt;

&lt;p&gt;To ensure an apples-to-apples comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WJb        : 1 worker
Hangfire   : WorkerCount = 1
Quartz.NET : MaxConcurrency = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Enqueue Benchmark
&lt;/h1&gt;

&lt;p&gt;Measures only job submission cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No workers running.&lt;/li&gt;
&lt;li&gt;No job execution.&lt;/li&gt;
&lt;li&gt;Clean storage created for every iteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JobCount&lt;/th&gt;
&lt;th&gt;WJb&lt;/th&gt;
&lt;th&gt;Quartz.NET&lt;/th&gt;
&lt;th&gt;Hangfire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;24.12 µs&lt;/td&gt;
&lt;td&gt;30.63 µs&lt;/td&gt;
&lt;td&gt;379.25 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;330.59 µs&lt;/td&gt;
&lt;td&gt;482.30 µs&lt;/td&gt;
&lt;td&gt;31.53 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;3.68 ms&lt;/td&gt;
&lt;td&gt;4.39 ms&lt;/td&gt;
&lt;td&gt;233.22 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;25.54 ms&lt;/td&gt;
&lt;td&gt;33.88 ms&lt;/td&gt;
&lt;td&gt;2,381.42 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Enqueue Memory Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JobCount&lt;/th&gt;
&lt;th&gt;WJb&lt;/th&gt;
&lt;th&gt;Quartz.NET&lt;/th&gt;
&lt;th&gt;Hangfire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.59 KB&lt;/td&gt;
&lt;td&gt;4.58 KB&lt;/td&gt;
&lt;td&gt;26.19 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;138.1 KB&lt;/td&gt;
&lt;td&gt;371.4 KB&lt;/td&gt;
&lt;td&gt;2.14 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1.35 MB&lt;/td&gt;
&lt;td&gt;3.77 MB&lt;/td&gt;
&lt;td&gt;21.41 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;13.41 MB&lt;/td&gt;
&lt;td&gt;36.24 MB&lt;/td&gt;
&lt;td&gt;213.83 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqf38gp4t1le6mxhpdbm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqf38gp4t1le6mxhpdbm.jpg" alt="Enqueue Benchmark" width="799" height="264"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  End-to-End Benchmark
&lt;/h1&gt;

&lt;p&gt;Measures the complete pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enqueue → Schedule → Execute → Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JobCount&lt;/th&gt;
&lt;th&gt;WJb&lt;/th&gt;
&lt;th&gt;Quartz.NET&lt;/th&gt;
&lt;th&gt;Hangfire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;668.6 µs&lt;/td&gt;
&lt;td&gt;11.24 ms&lt;/td&gt;
&lt;td&gt;40.86 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;7.44 ms&lt;/td&gt;
&lt;td&gt;26.41 ms&lt;/td&gt;
&lt;td&gt;259.72 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;39.94 ms&lt;/td&gt;
&lt;td&gt;89.09 ms&lt;/td&gt;
&lt;td&gt;2,598.00 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  End-to-End Memory Usage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JobCount&lt;/th&gt;
&lt;th&gt;WJb&lt;/th&gt;
&lt;th&gt;Quartz.NET&lt;/th&gt;
&lt;th&gt;Hangfire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;235.6 KB&lt;/td&gt;
&lt;td&gt;762.9 KB&lt;/td&gt;
&lt;td&gt;7.23 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;2.37 MB&lt;/td&gt;
&lt;td&gt;7.53 MB&lt;/td&gt;
&lt;td&gt;71.50 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;23.92 MB&lt;/td&gt;
&lt;td&gt;70.92 MB&lt;/td&gt;
&lt;td&gt;714.67 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkh0ytznu74loredj6nkd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkh0ytznu74loredj6nkd.jpg" alt="End-to-End Benchmark" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;In-memory storage only.&lt;/li&gt;
&lt;li&gt;Startup and shutdown costs are excluded.&lt;/li&gt;
&lt;li&gt;Validation tests are included in the benchmark suite.&lt;/li&gt;
&lt;li&gt;Concurrency is normalized across frameworks.&lt;/li&gt;
&lt;li&gt;Results reflect lightweight job workloads and framework overhead rather than heavy business logic execution.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>Six Months, One Developer, Too Much Coffee</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Thu, 01 Oct 2026 07:08:14 +0000</pubDate>
      <link>https://dev.to/ukrguru/six-months-one-developer-too-much-coffee-3fid</link>
      <guid>https://dev.to/ukrguru/six-months-one-developer-too-much-coffee-3fid</guid>
      <description>&lt;h1&gt;
  
  
  Solo Developers Can Still Build Big Things in 2026 🚀
&lt;/h1&gt;

&lt;p&gt;Every few weeks I see a post claiming something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Modern software is too complex for a solo developer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You need funding, a team, product managers, a designer, a DevOps engineer, and at least three weekly meetings to build anything serious."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a solo developer, I decided to perform a highly scientific experiment.&lt;/p&gt;

&lt;p&gt;The experiment lasted six months.&lt;/p&gt;

&lt;p&gt;The results were unexpected.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Beginning
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       💡
        │
        ▼
       WJb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months ago, WJb was just an idea.&lt;/p&gt;

&lt;p&gt;Not a company.&lt;/p&gt;

&lt;p&gt;Not a startup.&lt;/p&gt;

&lt;p&gt;Not a venture-backed product.&lt;/p&gt;

&lt;p&gt;Just a simple goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a modern background job and workflow framework for .NET that is easy to use and easy to extend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds small enough.&lt;/p&gt;

&lt;p&gt;Famous last words.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Feature Expansion Loop
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build Feature
      │
      ▼
 "Wouldn't it be nice..."
      │
      ▼
 Build Another Feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months later, the original idea had evolved into an ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  WJb Core
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             WJb
               │
     ┌─────────┼─────────┐
     ▼         ▼         ▼
   Actions   Jobs     Workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today WJb provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background jobs&lt;/li&gt;
&lt;li&gt;Delayed execution&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Recurring jobs&lt;/li&gt;
&lt;li&gt;Workflows&lt;/li&gt;
&lt;li&gt;Action pipelines&lt;/li&gt;
&lt;li&gt;Queues&lt;/li&gt;
&lt;li&gt;Retry handling&lt;/li&gt;
&lt;li&gt;Progress reporting&lt;/li&gt;
&lt;li&gt;Dependency Injection integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NuGet Packages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjdabjvz6fyi7rmo4pdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjdabjvz6fyi7rmo4pdz.png" alt="WJb Free" width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk26cq0we7l4rm1353lyx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk26cq0we7l4rm1353lyx.png" alt="WJb Commercial" width="800" height="877"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.nuget.org/packages?q=wjb" rel="noopener noreferrer"&gt;https://www.nuget.org/packages?q=wjb&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  WJb.UI.Blazor
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│ Dashboard                    │
├──────────────────────────────┤
│ ✅ Completed                 │
│ 🟡 Running                   │
│ ❌ Failed                    │
│ 🔄 Retrying                  │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard brings visibility to jobs, queues, workflows, progress, payloads, errors, and execution history.&lt;/p&gt;

&lt;p&gt;All inside the .NET ecosystem.&lt;/p&gt;

&lt;p&gt;Just Blazor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp27t7abr7hqc811urvmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp27t7abr7hqc811urvmj.png" alt="WJb.UI.Blazor" width="799" height="772"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Supporting Real Projects Means Supporting Real Databases
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ IndexedDB
✅ SQLite
✅ SQL Server
✅ PostgreSQL
✅ MySQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means WJb can be used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blazor WebAssembly applications&lt;/li&gt;
&lt;li&gt;Desktop applications&lt;/li&gt;
&lt;li&gt;Small services&lt;/li&gt;
&lt;li&gt;Enterprise systems&lt;/li&gt;
&lt;li&gt;Cloud environments&lt;/li&gt;
&lt;li&gt;Self-hosted deployments&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Documentation Is a Feature
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      Code
        │
        ▼
     Examples
        │
        ▼
 Documentation
        │
        ▼
      Demos
        │
        ▼
      wjb.pro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F291dhzoo44jtbotqn2ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F291dhzoo44jtbotqn2ec.png" alt="WJb website" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://wjb.pro" rel="noopener noreferrer"&gt;https://wjb.pro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The site contains documentation, getting-started guides, examples, and interactive demos.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing: Conversations With Future Me
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Past Me:
"This implementation is perfect."

Future Me:
"What is this?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you check Git history.&lt;/p&gt;

&lt;p&gt;Author: You.&lt;/p&gt;

&lt;p&gt;Awkward.&lt;/p&gt;

&lt;p&gt;That's why a large amount of effort went into unit tests, integration tests, regression tests, and benchmarks.&lt;/p&gt;




&lt;h1&gt;
  
  
  Modern Solo Development
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           💡 Idea
              │
              ▼
       Alexander
              │
              ▼
        "Let's build it"
              │
              ▼
         Copilot/Grok
              │
              ▼
       Lots Of Code
              │
              ▼
            WJb 🚀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI doesn't replace developers.&lt;/p&gt;

&lt;p&gt;It helps remove friction.&lt;/p&gt;

&lt;p&gt;The vision, architecture, debugging, and persistence still come from the developer.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Six Months Produced
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         WJb
                          │
         ┌────────────────┼────────────────┐
         │                │                │
         ▼                ▼                ▼
      Core           UI.Blazor         Documentation
         │
         ▼
      Providers
         │
 ┌───────┼───────┬───────┬───────┐
 ▼       ▼       ▼       ▼       ▼
IDB   SQLite   SQL   PostgreSQL MySQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ WJb Core&lt;/p&gt;

&lt;p&gt;✅ WJb.UI.Blazor&lt;/p&gt;

&lt;p&gt;✅ IndexedDB Provider&lt;/p&gt;

&lt;p&gt;✅ SQLite Provider&lt;/p&gt;

&lt;p&gt;✅ SQL Server Provider&lt;/p&gt;

&lt;p&gt;✅ PostgreSQL Provider&lt;/p&gt;

&lt;p&gt;✅ MySQL Provider&lt;/p&gt;

&lt;p&gt;✅ Documentation&lt;/p&gt;

&lt;p&gt;✅ Interactive Demos&lt;/p&gt;

&lt;p&gt;✅ Examples&lt;/p&gt;

&lt;p&gt;✅ Tests&lt;/p&gt;

&lt;p&gt;✅ Benchmarks&lt;/p&gt;

&lt;p&gt;✅ NuGet Packages&lt;/p&gt;

&lt;p&gt;✅ Website&lt;/p&gt;




&lt;h1&gt;
  
  
  Useful Links
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Website
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://wjb.pro" rel="noopener noreferrer"&gt;https://wjb.pro&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NuGet Packages
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://www.nuget.org/packages?q=wjb" rel="noopener noreferrer"&gt;https://www.nuget.org/packages?q=wjb&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/UkrGuru/WJb.Demo" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      One Developer
             +
       Clear Vision
             +
       Modern Tools
             +
         Six Months
             =
      Entire Ecosystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months ago, WJb was just an idea.&lt;/p&gt;

&lt;p&gt;Today it includes a framework, a dashboard, multiple providers, documentation, demos, tests, benchmarks, NuGet packages, and a public website.&lt;/p&gt;

&lt;p&gt;So the next time someone says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A solo developer can't build something serious."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just smile.&lt;/p&gt;

&lt;p&gt;Then ship another release. 🚀&lt;/p&gt;

</description>
      <category>backend</category>
      <category>buildinpublic</category>
      <category>dotnet</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>WJb v1.2: From WJb Action to WJb Job</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Thu, 01 Oct 2026 05:55:25 +0000</pubDate>
      <link>https://dev.to/ukrguru/wjb-v11-from-wjb-action-to-wjb-job-4ol0</link>
      <guid>https://dev.to/ukrguru/wjb-v11-from-wjb-action-to-wjb-job-4ol0</guid>
      <description>&lt;h1&gt;
  
  
  WJb v1.2: From WJb Action to WJb Job
&lt;/h1&gt;

&lt;p&gt;Most developers discover WJb through Actions.&lt;/p&gt;

&lt;p&gt;An Action is easy to understand.&lt;/p&gt;

&lt;p&gt;It is just a class that performs work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For many scenarios, that is enough.&lt;/p&gt;

&lt;p&gt;So why would we introduce Jobs?&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1. Execute the Action Directly
&lt;/h2&gt;

&lt;p&gt;Suppose we already have an Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ActionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendEmailAction&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JobAction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmailInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;EmailInput&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;EmailSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can execute it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SendEmailAction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmailInput&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Action runs.&lt;/p&gt;

&lt;p&gt;The email is sent.&lt;/p&gt;

&lt;p&gt;The work is complete.&lt;/p&gt;

&lt;p&gt;For small applications, nothing more is required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2. The Action Has a Limitation
&lt;/h2&gt;

&lt;p&gt;Imagine the application crashes halfway through processing.&lt;/p&gt;

&lt;p&gt;What happened?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Action
 ↓
Execute
 ↓
Crash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Questions immediately appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the Action start?&lt;/li&gt;
&lt;li&gt;Did it finish?&lt;/li&gt;
&lt;li&gt;Did it fail?&lt;/li&gt;
&lt;li&gt;Should it be retried?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Action itself does not answer these questions.&lt;/p&gt;

&lt;p&gt;An Action performs work.&lt;/p&gt;

&lt;p&gt;It does not track work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3. Introduce a Job
&lt;/h2&gt;

&lt;p&gt;A Job represents a request to execute an Action.&lt;/p&gt;

&lt;p&gt;Instead of running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we create a Job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InMemoryStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WJbBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendEmailAction&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnqueueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmailInput&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteLoopAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now something new exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Action
 ↓
Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Job becomes a record of the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4. Why Is This Useful?
&lt;/h2&gt;

&lt;p&gt;A Job can answer questions that an Action cannot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Was it created?
Was it started?
Was it completed?
Did it fail?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Action still sends the email.&lt;/p&gt;

&lt;p&gt;The Job tracks the execution.&lt;/p&gt;

&lt;p&gt;Responsibilities become clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Action
 ↓
Performs work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Tracks work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5. What Actually Changed?
&lt;/h2&gt;

&lt;p&gt;The business logic did not change.&lt;/p&gt;

&lt;p&gt;The Action did not change.&lt;/p&gt;

&lt;p&gt;Only the execution model changed.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
 ↓
Action
 ↓
Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
 ↓
Job
 ↓
Action
 ↓
Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Action remains focused on the task.&lt;/p&gt;

&lt;p&gt;The Job adds execution tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6. Build on Top of Jobs
&lt;/h2&gt;

&lt;p&gt;Once work is represented by Jobs, additional capabilities become possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Scheduling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These features become possible because the execution is now represented by an object that can be stored, queried, and managed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Main Idea
&lt;/h2&gt;

&lt;p&gt;An Action answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How is the work performed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Job answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What work should be performed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The evolution looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business Logic
      ↓
Action
      ↓
Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Action
 ↓
ActionResult
 ↓
Step
 ↓
Next Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actions execute work.&lt;/p&gt;

&lt;p&gt;Jobs represent work.&lt;/p&gt;

&lt;p&gt;WJb combines both concepts to make execution reliable, observable, and scalable.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>WJb v1.2: From Console Application to WJb Action</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Wed, 30 Sep 2026 10:50:21 +0000</pubDate>
      <link>https://dev.to/ukrguru/wjb-v11-from-console-application-to-wjb-action-246n</link>
      <guid>https://dev.to/ukrguru/wjb-v11-from-console-application-to-wjb-action-246n</guid>
      <description>&lt;h1&gt;
  
  
  From Console Application to WJb Action
&lt;/h1&gt;

&lt;p&gt;Most developers do not start with WJb.&lt;/p&gt;

&lt;p&gt;They start with a task.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Send an email.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before introducing Actions, Jobs, Workflows, or Scheduling, let's solve the problem using plain .NET code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1. Make It Work
&lt;/h2&gt;

&lt;p&gt;The first goal is not to build a workflow.&lt;/p&gt;

&lt;p&gt;The first goal is to solve the problem.&lt;/p&gt;

&lt;p&gt;A typical implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;EmailSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;smtpSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmailInput&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point there is no WJb.&lt;/p&gt;

&lt;p&gt;Just working code.&lt;/p&gt;

&lt;p&gt;The most important rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make the business logic work before introducing infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the email cannot be sent from a simple application, adding WJb will not fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2. Move the Logic into a Reusable Library
&lt;/h2&gt;

&lt;p&gt;Once the solution works, the next step is usually extracting it into reusable code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSdk&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;SmtpSettings&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;EmailInput&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// send email&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the email functionality can be reused from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;console applications;&lt;/li&gt;
&lt;li&gt;ASP.NET applications;&lt;/li&gt;
&lt;li&gt;worker services;&lt;/li&gt;
&lt;li&gt;unit tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business logic is no longer tied to a specific application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3. Wrap the Library as an Action
&lt;/h2&gt;

&lt;p&gt;Suppose we now want WJb to execute the same operation.&lt;/p&gt;

&lt;p&gt;We do not rewrite the email logic.&lt;/p&gt;

&lt;p&gt;We simply wrap it inside an Action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ActionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendEmailAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SmtpSettings&lt;/span&gt; &lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JobAction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmailInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;EmailInput&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;EmailSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what changed.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;EmailSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business logic remains exactly the same.&lt;/p&gt;

&lt;p&gt;The Action becomes a WJb entry point for code that already works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4. Execute the Action Directly
&lt;/h2&gt;

&lt;p&gt;An Action is still just a class.&lt;/p&gt;

&lt;p&gt;You can execute it directly without creating Jobs, Workflows, or Schedules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SendEmailAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtpSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmailInput&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no Job is required;&lt;/li&gt;
&lt;li&gt;no Workflow is required;&lt;/li&gt;
&lt;li&gt;no Scheduler is required;&lt;/li&gt;
&lt;li&gt;no Store is required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You are simply executing an Action.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5. Let WJb Execute It
&lt;/h2&gt;

&lt;p&gt;Once the Action is registered, WJb can execute it on your behalf.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InMemoryStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WJbBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendEmailAction&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtpSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnqueueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"send-email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmailInput&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;wjb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteLoopAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WJb
 ↓
Job (Enqueue)
 ↓
SendEmailAction
 ↓
EmailSdk.SendAsync()
 ↓
ActionResult
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The email logic has not changed.&lt;/p&gt;

&lt;p&gt;Only the execution model has changed.&lt;/p&gt;

&lt;p&gt;The same Action can now be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;executed immediately;&lt;/li&gt;
&lt;li&gt;scheduled for later;&lt;/li&gt;
&lt;li&gt;retried after failure;&lt;/li&gt;
&lt;li&gt;executed on another machine;&lt;/li&gt;
&lt;li&gt;included in a workflow via &lt;code&gt;.Next(...)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of continuing the workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$"Email sent to &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Main Idea
&lt;/h2&gt;

&lt;p&gt;Many frameworks require developers to learn the framework before solving the problem.&lt;/p&gt;

&lt;p&gt;WJb takes the opposite approach.&lt;/p&gt;

&lt;p&gt;Start with working code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem
   ↓
Working Code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extract reusable business logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem
   ↓
Working Code
   ↓
Reusable Library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap it as an Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem
   ↓
Working Code
   ↓
Reusable Library
   ↓
Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let WJb execute it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem
   ↓
Working Code
   ↓
Reusable Library
   ↓
Action
   ↓
WJb Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
 ↓
Action
 ↓
ActionResult
 ↓
Step
 ↓
Next Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business logic remains independent.&lt;/p&gt;

&lt;p&gt;WJb does not replace the business logic.&lt;/p&gt;

&lt;p&gt;WJb provides a consistent way to execute, schedule, retry, and orchestrate code that already works.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>softwaredevelopment</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NuGet WJb v1.2 🎉</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Fri, 11 Sep 2026 19:20:05 +0000</pubDate>
      <link>https://dev.to/ukrguru/nuget-wjb-v0120-v10-3m22</link>
      <guid>https://dev.to/ukrguru/nuget-wjb-v0120-v10-3m22</guid>
      <description>&lt;p&gt;NuGet WJb v0.120 → WJb v1.2 🎉&lt;/p&gt;

&lt;p&gt;Behind this single version change are hundreds of hours of development, countless design decisions, testing sessions, bug fixes, and improvements.&lt;/p&gt;

&lt;p&gt;Reaching 1.0 doesn't mean the journey is over.&lt;/p&gt;

&lt;p&gt;It means the foundation is solid, the vision is clear, and the best is yet to come.&lt;/p&gt;

&lt;p&gt;Thank you for being part of the journey.&lt;/p&gt;

&lt;p&gt;Welcome to WJb 1.2 🚀&lt;/p&gt;

&lt;p&gt;🔗 Website: &lt;a href="https://wjb.pro" rel="noopener noreferrer"&gt;https://wjb.pro&lt;/a&gt;&lt;br&gt;
🔗 GitHub: &lt;a href="https://github.com/UkrGuru/WJb.Demo" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo&lt;/a&gt;&lt;br&gt;
🔗 NuGet: &lt;a href="https://www.nuget.org/packages?q=wjb" rel="noopener noreferrer"&gt;https://www.nuget.org/packages?q=wjb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzhzz7ys33gfyv6y58cha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzhzz7ys33gfyv6y58cha.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚀 WJb vs Popular Alternatives</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Tue, 25 Aug 2026 16:02:28 +0000</pubDate>
      <link>https://dev.to/ukrguru/wjb-vs-popular-alternatives-59ed</link>
      <guid>https://dev.to/ukrguru/wjb-vs-popular-alternatives-59ed</guid>
      <description>&lt;p&gt;🚀 WJb vs Popular Alternatives&lt;/p&gt;

&lt;p&gt;Over the last months I've been building WJb, a lightweight .NET job and workflow framework focused on simplicity, explicitness, and developer control.&lt;/p&gt;

&lt;p&gt;Today, WJb includes:&lt;/p&gt;

&lt;p&gt;✅ Background jobs&lt;br&gt;
✅ Delayed execution&lt;br&gt;
✅ Cron scheduling&lt;br&gt;
✅ Workflow chaining&lt;br&gt;
✅ Dependency injection&lt;br&gt;
✅ JSON-based action registration&lt;br&gt;
✅ Strongly typed actions&lt;br&gt;
✅ Pluggable storage&lt;/p&gt;

&lt;p&gt;Unlike many alternatives, WJb is designed to work without external infrastructure and without forcing your business logic into framework-specific patterns.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;Write background jobs as regular C# code and keep full control over how everything works.&lt;/p&gt;

&lt;p&gt;How does it compare?&lt;/p&gt;

&lt;p&gt;Compared to Hangfire, Quartz.NET, and MassTransit, WJb focuses on:&lt;/p&gt;

&lt;p&gt;lightweight deployment&lt;br&gt;
explicit workflows&lt;br&gt;
strongly typed action architecture&lt;br&gt;
low learning curve&lt;br&gt;
no broker requirements&lt;br&gt;
full author control&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphzcwd6ax5mcxqyusjax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphzcwd6ax5mcxqyusjax.png" alt=" " width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is it production ready?&lt;/p&gt;

&lt;p&gt;The framework now includes a growing integration test suite covering:&lt;/p&gt;

&lt;p&gt;Actions&lt;br&gt;
DI&lt;br&gt;
Scheduling&lt;br&gt;
Storage&lt;br&gt;
Workflows&lt;br&gt;
Results&lt;br&gt;
Queries&lt;br&gt;
Serialization&lt;br&gt;
Extensions&lt;/p&gt;

&lt;p&gt;and continues to evolve based on real-world usage.&lt;/p&gt;

&lt;p&gt;Looking for early adopters&lt;/p&gt;

&lt;p&gt;WJb has reached the point where I'd love to get feedback from other .NET developers.&lt;/p&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;p&gt;use Hangfire,&lt;br&gt;
use Quartz.NET,&lt;br&gt;
built your own background worker,&lt;br&gt;
need lightweight workflows,&lt;/p&gt;

&lt;p&gt;please give WJb a try and let me know what works, what doesn't, and what is missing.&lt;/p&gt;

&lt;p&gt;Real feedback is far more valuable than assumptions.&lt;/p&gt;

&lt;p&gt;🔗 Website: &lt;a href="https://wjb.pro" rel="noopener noreferrer"&gt;https://wjb.pro&lt;/a&gt;&lt;br&gt;
🔗 GitHub: &lt;a href="https://github.com/UkrGuru/WJb.Demo" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo&lt;/a&gt;&lt;br&gt;
🔗 NuGet: &lt;a href="https://www.nuget.org/packages?q=wjb" rel="noopener noreferrer"&gt;https://www.nuget.org/packages?q=wjb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your feedback will directly influence the roadmap.&lt;/p&gt;

&lt;h1&gt;
  
  
  dotnet #csharp #opensource #aspnetcore #backgroundjobs #workflow #hangfire #quartznet #masstransit #nuget #softwarearchitecture #dotnetdev
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>🚀Nearly 300 Integration Tests Completed</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:48:51 +0000</pubDate>
      <link>https://dev.to/ukrguru/nearly-300-integration-tests-completed-d92</link>
      <guid>https://dev.to/ukrguru/nearly-300-integration-tests-completed-d92</guid>
      <description>&lt;p&gt;🚀 Nearly 300 Integration Tests Completed&lt;/p&gt;

&lt;p&gt;Over the past weeks we've been building something that goes beyond traditional unit testing.&lt;/p&gt;

&lt;p&gt;WJb now includes nearly 300 integration tests covering real-world scenarios across the entire framework.&lt;/p&gt;

&lt;p&gt;These tests validate:&lt;/p&gt;

&lt;p&gt;✅ Background Jobs&lt;br&gt;
 ✅ Workflows&lt;br&gt;
 ✅ Retries&lt;br&gt;
 ✅ Scheduling&lt;br&gt;
 ✅ Monitoring&lt;br&gt;
 ✅ Storage Providers&lt;br&gt;
 ✅ SQL Server Persistence&lt;br&gt;
 ✅ Worker Infrastructure&lt;br&gt;
 ✅ Action Factories&lt;br&gt;
 ✅ Configuration &amp;amp; Definitions&lt;/p&gt;

&lt;p&gt;But we're taking it one step further.&lt;/p&gt;

&lt;p&gt;Every integration test is being transformed into executable documentation, allowing developers to see:&lt;/p&gt;

&lt;p&gt;the scenario,&lt;br&gt;
the source code,&lt;br&gt;
the execution result,&lt;br&gt;
and the verified behavior,&lt;/p&gt;

&lt;p&gt;all in one place.&lt;/p&gt;

&lt;p&gt;📊 Current status:&lt;/p&gt;

&lt;p&gt;296 Integration Tests&lt;br&gt;
38 Test Suites&lt;br&gt;
100% Executable Documentation&lt;/p&gt;

&lt;p&gt;We're currently publishing the documentation and test explorer here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://wjb.pro/tests" rel="noopener noreferrer"&gt;https://wjb.pro/tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More suites will be published over the coming days as we continue converting the entire test suite into browsable documentation.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;If a feature exists in WJb, it should have a real integration test, and that test should be part of the documentation.&lt;/p&gt;

&lt;h1&gt;
  
  
  dotnet #csharp #aspnetcore #blazor #opensource #testing #backgroundjobs #workflows #wjb
&lt;/h1&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>sqlserver</category>
      <category>wjb</category>
    </item>
    <item>
      <title>🚀 Interactive WJB Demos</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Tue, 18 Aug 2026 18:11:00 +0000</pubDate>
      <link>https://dev.to/ukrguru/interactive-wjb-demos-are-live-4pnh</link>
      <guid>https://dev.to/ukrguru/interactive-wjb-demos-are-live-4pnh</guid>
      <description>&lt;p&gt;🚀 Interactive WJB Demos&lt;/p&gt;

&lt;p&gt;Want to see WJB in action before adding it to your project?&lt;/p&gt;

&lt;p&gt;The new interactive demos are now available online:&lt;/p&gt;

&lt;p&gt;🔹 Background jobs&lt;br&gt;
🔹 Workflow orchestration&lt;br&gt;
🔹 Scheduling and execution monitoring&lt;br&gt;
🔹 Live examples with source code explanations&lt;/p&gt;

&lt;p&gt;Try it now:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://wjb.pro/demos" rel="noopener noreferrer"&gt;https://wjb.pro/demos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WJB is a lightweight .NET library for background jobs and workflows that runs wherever .NET runs, without requiring Redis, message brokers, or external infrastructure.&lt;/p&gt;

&lt;p&gt;(#dotnet #csharp #blazor #workflow #backgroundjobs #scheduler #sqlserver #wjb)&lt;/p&gt;

&lt;p&gt;⚠️ This article was written during the WJb preview stage. Some APIs have changed since publication. Please refer to the latest documentation:&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://wjb.pro" rel="noopener noreferrer"&gt;https://wjb.pro&lt;/a&gt;&lt;br&gt;
NuGet: &lt;a href="https://www.nuget.org/packages?q=wjb" rel="noopener noreferrer"&gt;https://www.nuget.org/packages?q=wjb&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/UkrGuru/WJb.Demo" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>blazor</category>
      <category>scheduler</category>
    </item>
    <item>
      <title>Background Jobs Running Directly Inside Blazor WebAssembly? 🤔</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Tue, 11 Aug 2026 06:00:13 +0000</pubDate>
      <link>https://dev.to/ukrguru/background-jobs-running-directly-inside-blazor-webassembly-54i0</link>
      <guid>https://dev.to/ukrguru/background-jobs-running-directly-inside-blazor-webassembly-54i0</guid>
      <description>&lt;p&gt;Most job frameworks are built for servers.&lt;/p&gt;

&lt;p&gt;This demo shows WJb running background jobs entirely in the browser, with real-time progress tracking, status updates, chained actions, and a live dashboard.&lt;/p&gt;

&lt;p&gt;✅ Pure Blazor WebAssembly&lt;br&gt;
✅ Real-time progress&lt;br&gt;
✅ Chained workflows&lt;br&gt;
✅ Success / Failure handling&lt;br&gt;
✅ Live demo&lt;/p&gt;

&lt;p&gt;🎬 Video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/shorts/Tu3TD2Rk37E" rel="noopener noreferrer"&gt;https://www.youtube.com/shorts/Tu3TD2Rk37E&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/UkrGuru/WJb.Demo/tree/main/samples/WJb.Demo.Wasm" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo/tree/main/samples/WJb.Demo.Wasm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would you run job workflows in the browser? What use cases come to mind?&lt;/p&gt;

&lt;h1&gt;
  
  
  dotnet #csharp #blazor #webassembly #workflow #backgroundjobs #opensource
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>Running Background Jobs in Blazor WebAssembly with WJb</title>
      <dc:creator>Oleksandr Viktor</dc:creator>
      <pubDate>Mon, 10 Aug 2026 17:59:05 +0000</pubDate>
      <link>https://dev.to/ukrguru/running-background-jobs-in-blazor-webassembly-with-wjb-hik</link>
      <guid>https://dev.to/ukrguru/running-background-jobs-in-blazor-webassembly-with-wjb-hik</guid>
      <description>&lt;p&gt;Most background job frameworks are designed for server applications.&lt;/p&gt;

&lt;p&gt;As an experiment, I built a demo showing the WJb background job engine running entirely inside a Blazor WebAssembly app. The demo executes jobs in the browser, tracks progress, updates UI in real time, supports chained actions, and demonstrates success, failure, and cancellation scenarios.&lt;/p&gt;

&lt;p&gt;Key things demonstrated:&lt;/p&gt;

&lt;p&gt;✅ Background job execution in a WASM environment&lt;br&gt;
✅ Real-time progress tracking&lt;br&gt;
✅ Chained actions&lt;br&gt;
✅ Status handling (Completed / Failed)&lt;br&gt;
✅ Live dashboard&lt;br&gt;
✅ Decoupled action architecture&lt;br&gt;
✅ Job cleanup support&lt;/p&gt;

&lt;p&gt;One of my goals with WJb is to keep workflows and jobs as regular C# code instead of introducing DSLs, XML, or designers.&lt;/p&gt;

&lt;p&gt;Source and demo:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/UkrGuru/WJb.Demo/tree/main/samples/WJb.Demo.Wasm" rel="noopener noreferrer"&gt;https://github.com/UkrGuru/WJb.Demo/tree/main/samples/WJb.Demo.Wasm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd be interested to hear from other .NET developers:&lt;/p&gt;

&lt;p&gt;Would you ever run a job/workflow engine in the browser, and what use cases would justify it?&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>backend</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
