<?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: Roman Chudov</title>
    <description>The latest articles on DEV Community by Roman Chudov (@roman_chudov_aae89a53d641).</description>
    <link>https://dev.to/roman_chudov_aae89a53d641</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png</url>
      <title>DEV Community: Roman Chudov</title>
      <link>https://dev.to/roman_chudov_aae89a53d641</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roman_chudov_aae89a53d641"/>
    <language>en</language>
    <item>
      <title>ChaosKit: Code-Level Chaos Engineering framework for Go</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Fri, 28 Nov 2025 03:50:13 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/chaoskit-code-level-chaos-engineering-for-go-3dki</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/chaoskit-code-level-chaos-engineering-for-go-3dki</guid>
      <description>&lt;p&gt;Chaos engineering tools usually target infrastructure: pods, nodes, networks, CPU pressure. But many real failures happen &lt;strong&gt;inside the application logic&lt;/strong&gt; - in retries, compensations, concurrency, or state transitions.&lt;/p&gt;

&lt;p&gt;Infrastructure tools can’t simulate a panic inside a function, a 15ms delay at the wrong moment, or an internal invariant break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChaosKit&lt;/strong&gt; solves that by bringing chaos engineering directly into Go code.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rom8726/chaoskit" rel="noopener noreferrer"&gt;https://github.com/rom8726/chaoskit&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Code-Level Chaos Matters
&lt;/h2&gt;

&lt;p&gt;While building the workflow engine, it became clear that most critical failures were &lt;em&gt;logic-level&lt;/em&gt;, not cluster-level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a compensation step panics;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a goroutine leak accumulates over time;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a tiny delay causes a race condition.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ChaosKit is designed to expose these failures early - inside your unit and integration tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  How ChaosKit Works
&lt;/h2&gt;

&lt;p&gt;ChaosKit provides controlled "chaos points" you can insert into your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaybeDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaybeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaybePanic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, these are &lt;strong&gt;no-ops&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Chaos injectors activate &lt;strong&gt;only&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the binary is built with &lt;code&gt;-tags=chaos&lt;/code&gt;, and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you attach a chaos context:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AttachChaos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that chaos &lt;strong&gt;never triggers accidentally&lt;/strong&gt; in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Injectors, Validators, Scenarios
&lt;/h2&gt;

&lt;p&gt;ChaosKit consists of three core building blocks:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Injectors&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simulate failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;delays&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;random errors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;panic injection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ToxiProxy-based network faults&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;custom logic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"latency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;injectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RandomDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Validators&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Check invariants after each step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;goroutine count&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;recursion depth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no infinite loops&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;custom validation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"goroutines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GoroutineLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Scenarios&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Define steps, injectors, validators, and repetition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ExecuteWorkflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"panic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;injectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PanicWithProbability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GoroutineLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Chaos Inside &lt;code&gt;go test&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;ChaosKit integrates directly with Go’s &lt;code&gt;testing&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;chaostest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunChaos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScenarioBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScenarioBuilder&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;chaoskit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaybeDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TestTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"delay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;injectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RandomDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;chaostest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRepeat&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;chaostest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDefaultThresholds&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c"&gt;// 95% success required&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows developers to run chaos experiments &lt;strong&gt;during normal test runs&lt;/strong&gt;, without extra infrastructure.&lt;/p&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CI-friendly probabilistic chaos tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invariant checks under failure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;catching race conditions and leaks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;validating retry/compensation logic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Runtime behavior
&lt;/h2&gt;

&lt;p&gt;ChaosKit is engineered to avoid accidental activation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build tag isolation (&lt;code&gt;\-tags=chaos&lt;/code&gt;)&lt;/strong&gt; Chaos code is excluded from production binaries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicit chaos context&lt;/strong&gt; Without &lt;code&gt;AttachChaos&lt;/code&gt;, everything is a no-op.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network chaos is external&lt;/strong&gt; ToxiProxy never touches production traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monkey patching disabled by default&lt;/strong&gt; Used only in tests, only if explicitly enabled.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  When ChaosKit Is Valuable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Great fit for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;workflow/Saga engines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;stateful services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;concurrency-heavy components&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;libraries with retry logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;business logic with invariants&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Not ideal for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;pure infrastructure chaos (use Chaos Mesh, Litmus)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tests that can’t integrate &lt;code&gt;Maybe*&lt;/code&gt; calls&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;ChaosKit focuses on the part of the system that is hardest to test and easiest to break: &lt;strong&gt;your code’s internal failure paths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your Go project relies on workflows, compensations, or complex state transitions, ChaosKit helps ensure correctness under unpredictable conditions - without requiring Kubernetes, Docker, or cluster chaos tools.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/rom8726/chaoskit" rel="noopener noreferrer"&gt;https://github.com/rom8726/chaoskit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
    </item>
    <item>
      <title>Floxy — a lightweight workflow engine for GoLang with saga-style compensation</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Thu, 23 Oct 2025 04:10:47 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/floxy-a-lightweight-workflow-engine-for-golang-with-saga-style-compensation-2a2m</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/floxy-a-lightweight-workflow-engine-for-golang-with-saga-style-compensation-2a2m</guid>
      <description>&lt;h1&gt;
  
  
  Floxy — a lightweight workflow engine for Go with saga-style compensation
&lt;/h1&gt;

&lt;p&gt;When building distributed systems or long-running business processes, we often need workflows that can retry, rollback, and recover — without introducing the complexity of Temporal.&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href="https://github.com/rom8726/floxy" rel="noopener noreferrer"&gt;Floxy&lt;/a&gt; fits in.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Floxy?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Floxy&lt;/strong&gt; is a lightweight workflow engine for Go.&lt;br&gt;&lt;br&gt;
It provides a simple builder API and a deterministic runtime that supports &lt;strong&gt;saga-style compensation&lt;/strong&gt;, &lt;strong&gt;save points&lt;/strong&gt;, and &lt;strong&gt;idempotency control&lt;/strong&gt; — all without external dependencies or background daemons.&lt;/p&gt;

&lt;p&gt;It’s designed to be small, composable, and transparent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic state machine execution&lt;/li&gt;
&lt;li&gt;Retry and compensation policies&lt;/li&gt;
&lt;li&gt;Partial rollback via save points&lt;/li&gt;
&lt;li&gt;Idempotency control (&lt;code&gt;WithStepNoIdempotent&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Parallel, fork, and join support&lt;/li&gt;
&lt;li&gt;Event-driven persistence for observability&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;wf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;floxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_saga"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reserve_funds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ReserveFunds"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;OnFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"refund_funds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"RefundFunds"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ship_order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ShipOrder"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;OnFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cancel_shipping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"CancelShipping"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"notify_user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Notify"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;ship_order&lt;/code&gt; fails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Floxy marks it as &lt;code&gt;failed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Executes its compensation handler &lt;code&gt;cancel_shipping&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rolls back previous steps (&lt;code&gt;refund_funds&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Marks the workflow as &lt;code&gt;failed&lt;/code&gt; deterministically.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Retry and Idempotency
&lt;/h2&gt;

&lt;p&gt;In Floxy, &lt;code&gt;MaxRetries&lt;/code&gt; defines the &lt;strong&gt;total number of allowed handler calls&lt;/strong&gt;, including the first one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MaxRetries = 1&lt;/code&gt; → one call only (no retries).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MaxRetries = 3&lt;/code&gt; → up to three total executions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, steps are &lt;strong&gt;idempotent&lt;/strong&gt;.&lt;br&gt;
To mark a step as &lt;strong&gt;non-idempotent&lt;/strong&gt;, use:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;go&lt;br&gt;
Step("charge_card", "ChargeCard", floxy.WithStepNoIdempotent())&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This ensures the handler runs exactly once, even if it fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deterministic State Model
&lt;/h2&gt;

&lt;p&gt;Floxy models workflows as a finite state machine:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
pending → running → completed&lt;br&gt;
             ↓&lt;br&gt;
            failed → compensation → rolled_back&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each state transition is explicit and persisted in storage, which makes recovery and auditing reliable and predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use Floxy
&lt;/h2&gt;

&lt;p&gt;Floxy is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transactional workflows with rollback logic (saga pattern)&lt;/li&gt;
&lt;li&gt;ETL or provisioning pipelines&lt;/li&gt;
&lt;li&gt;Payment, booking, and inventory systems&lt;/li&gt;
&lt;li&gt;Temporal-like semantics without Temporal’s infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rom8726/floxy" rel="noopener noreferrer"&gt;github.com/rom8726/floxy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository includes a detailed &lt;code&gt;docs/ENGINE_SPEC.md&lt;/code&gt; describing the internal runtime model, retry semantics, and compensation flow.&lt;/p&gt;

&lt;p&gt;If you find Floxy useful, give it a star and share feedback.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Floxy: deterministic workflows for real systems, built the Go way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>saga</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Goodbye, Sentry!</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Wed, 23 Jul 2025 09:51:36 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/goodbuy-sentry-2ke7</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/goodbuy-sentry-2ke7</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/roman_chudov_aae89a53d641" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png" alt="roman_chudov_aae89a53d641"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Introducing Warden: A Self-Hosted Error Monitoring Platform&lt;/h2&gt;
      &lt;h3&gt;Roman Chudov ・ Jul 21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#monitoring&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Tue, 22 Jul 2025 20:42:12 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/-4f7i</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/-4f7i</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a" class="crayons-story__hidden-navigation-link"&gt;Introducing Warden: A Self-Hosted Error Monitoring Platform&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roman_chudov_aae89a53d641" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png" alt="roman_chudov_aae89a53d641 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roman_chudov_aae89a53d641" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roman Chudov
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roman Chudov
                
              
              &lt;div id="story-author-preview-content-2710982" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roman_chudov_aae89a53d641" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roman Chudov&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 21 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a" id="article-link-2710982"&gt;
          Introducing Warden: A Self-Hosted Error Monitoring Platform
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/monitoring"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;monitoring&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>programming</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Introducing Warden: A Self-Hosted Error Monitoring Platform</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Mon, 21 Jul 2025 13:29:15 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/introducing-warden-a-self-hosted-error-monitoring-platform-2j8a</guid>
      <description>&lt;p&gt;Today I’m releasing the first version of &lt;strong&gt;Warden&lt;/strong&gt;, a self-hosted error tracking and analytics platform designed as a private, Sentry-compatible alternative.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://warden-project.tech" rel="noopener noreferrer"&gt;https://warden-project.tech&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Warden?
&lt;/h2&gt;

&lt;p&gt;Warden helps developers and teams monitor, analyze, and respond to application errors in real time. It is designed for companies that require full control over their observability stack and need to operate in isolated or secure environments.&lt;/p&gt;

&lt;p&gt;Warden is compatible with Sentry SDKs, making integration seamless with minimal changes to existing applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Error tracking using Sentry-compatible protocols&lt;/li&gt;
&lt;li&gt;Two-Factor Authentication (2FA)&lt;/li&gt;
&lt;li&gt;Role-Based Access Control (RBAC) with team and project isolation&lt;/li&gt;
&lt;li&gt;Release analytics (new issues, regressions, fix time statistics)&lt;/li&gt;
&lt;li&gt;Notification system supporting Email and Telegram&lt;/li&gt;
&lt;li&gt;Admin dashboard with superuser controls&lt;/li&gt;
&lt;li&gt;LDAP integration for user and group synchronization&lt;/li&gt;
&lt;li&gt;SAML SSO support (including Keycloak)&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Warden?
&lt;/h2&gt;

&lt;p&gt;Warden is built for teams that cannot rely on cloud-based SaaS platforms due to compliance, legal, or security constraints. It provides observability infrastructure that runs entirely under your control, without external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Planned improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer assignments and resolution workflows&lt;/li&gt;
&lt;li&gt;GitHub and GitLab integration&lt;/li&gt;
&lt;li&gt;Advanced analytics dashboards&lt;/li&gt;
&lt;li&gt;Multi-tenant support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Learn more at &lt;a href="https://warden-project.tech" rel="noopener noreferrer"&gt;https://warden-project.tech&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Thu, 10 Jul 2025 13:15:04 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/-3779</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/-3779</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75" class="crayons-story__hidden-navigation-link"&gt;testy: YAML-Based Functional Tests for Go HTTP APIs&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roman_chudov_aae89a53d641" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png" alt="roman_chudov_aae89a53d641 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roman_chudov_aae89a53d641" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roman Chudov
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roman Chudov
                
              
              &lt;div id="story-author-preview-content-2674924" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roman_chudov_aae89a53d641" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2669210%2F5fa70eae-6677-48a8-825a-43e5f1422ace.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roman Chudov&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 10 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75" id="article-link-2674924"&gt;
          testy: YAML-Based Functional Tests for Go HTTP APIs
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>go</category>
      <category>tests</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>testy: YAML-Based Functional Tests for Go HTTP APIs</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Thu, 10 Jul 2025 12:43:52 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/testy-yaml-based-functional-tests-for-go-http-apis-2i75</guid>
      <description>&lt;p&gt;If you've ever written functional tests for a Go HTTP server, you’ve probably found yourself stuck between two extremes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing black-box tests with curl or Postman clones (slow, no debug, hard to maintain)&lt;/li&gt;
&lt;li&gt;Writing verbose Go test code for every API call, mocking, assertions, cleanup, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why I created &lt;a href="https://github.com/rom8726/testy" rel="noopener noreferrer"&gt;&lt;code&gt;testy&lt;/code&gt;&lt;/a&gt; — a &lt;strong&gt;declarative testing framework for Go HTTP APIs&lt;/strong&gt;, built on top of YAML and your actual &lt;code&gt;http.Handler&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;testy&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Here’s what makes &lt;code&gt;testy&lt;/code&gt; different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declarative tests&lt;/strong&gt; written in plain &lt;strong&gt;YAML&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real &lt;code&gt;http.Handler&lt;/code&gt; execution&lt;/strong&gt; — your actual app code, not a subprocess&lt;/li&gt;
&lt;li&gt;Full PostgreSQL fixture support via &lt;a href="https://github.com/rom8726/pgfixtures" rel="noopener noreferrer"&gt;&lt;code&gt;pgfixtures&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Powerful mocking, assertions, and SQL checks&lt;/li&gt;
&lt;li&gt;Great for &lt;strong&gt;debugging&lt;/strong&gt; and &lt;strong&gt;stepping through real code&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Debug Your Tests Like Real Code
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;testy&lt;/code&gt; runs requests through your actual Go &lt;code&gt;http.Handler&lt;/code&gt;, you can &lt;strong&gt;set breakpoints in your API handlers&lt;/strong&gt; and run tests in your IDE’s debug mode (&lt;code&gt;go test -v -run &amp;lt;TestName&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This makes test runs feel like regular request handling — not like opaque black-box e2e scripts.&lt;/p&gt;

&lt;p&gt;You don’t need to spin up your app on a port. &lt;code&gt;testy&lt;/code&gt; hits your router directly — whether it’s from net/http, Gin, Chi, Echo or anything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Declarative Testing With YAML
&lt;/h2&gt;

&lt;p&gt;Test scenarios are written as YAML lists of steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get_user&lt;/span&gt;
  &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/users/123&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"id": 123,&lt;/span&gt;
        &lt;span class="s"&gt;"name": "Alice"&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Go code. No boilerplate. Just requests and expectations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dynamic Placeholders and Context
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;testy&lt;/code&gt; supports smart templating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{{step.response.field}}&lt;/code&gt; — reference values from earlier steps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{{ENV_VAR}}&lt;/code&gt; — use environment variables (like &lt;code&gt;UUID&lt;/code&gt;, tokens, etc.)&lt;/li&gt;
&lt;li&gt;Used in URLs, headers, bodies, SQL queries, everywhere&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get user&lt;/span&gt;
  &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/users/{{create_user.response.id}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Works with Your Real Code
&lt;/h2&gt;

&lt;p&gt;A minimal Go test using &lt;code&gt;testy&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;testy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;testy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;            &lt;span class="c"&gt;// your real app router&lt;/span&gt;
  &lt;span class="n"&gt;ConnStr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TEST_DB"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c"&gt;// PostgreSQL&lt;/span&gt;
  &lt;span class="n"&gt;CasesDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"./tests/cases"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c"&gt;// YAML test cases&lt;/span&gt;
  &lt;span class="n"&gt;FixturesDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"./tests/fixtures"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c"&gt;// pgfixtures&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;You can run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And debug it with your IDE as usual. Put a breakpoint in your handler — it just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-Life Example: Creating a Project
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;create.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create project with team&lt;/span&gt;
  &lt;span class="na"&gt;fixtures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;empty_db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;developers_team&lt;/span&gt;

  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;login&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/auth&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warden123!"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add_project&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/projects/add&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{login.response.access_token}}"&lt;/span&gt;
          &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Project&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;With&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Team"&lt;/span&gt;
          &lt;span class="na"&gt;team_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
      &lt;span class="na"&gt;dbChecks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SELECT name FROM projects WHERE team_id = &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Project&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;With&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Team"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No test logic in Go. It’s all in the scenario.&lt;/p&gt;




&lt;h2&gt;
  
  
  Powered by &lt;code&gt;pgfixtures&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Each scenario can load fixtures into the DB before it starts — using &lt;a href="https://github.com/rom8726/pgfixtures" rel="noopener noreferrer"&gt;&lt;code&gt;pgfixtures&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fixtures are described in YAML, and support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Truncating tables&lt;/li&gt;
&lt;li&gt;Resetting sequences&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$eval(SELECT ...)&lt;/code&gt; expressions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;public.users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice"&lt;/span&gt;
    &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$eval(SELECT NOW())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mocks for Outgoing Requests
&lt;/h2&gt;

&lt;p&gt;You can define HTTP mocks directly in the test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mockServers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/send&lt;/span&gt;
        &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;202&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"status":"queued"}'&lt;/span&gt;

&lt;span class="na"&gt;mockCalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/send&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Joseph"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mocks are verified and auto-spun for you. No third-party servers needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure YAML&lt;/strong&gt;: easily readable, editable by devs and QA alike&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real code path&lt;/strong&gt;: debug API as usual — same context, same stacktrace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in mocks&lt;/strong&gt;: test integrations and verify outbound calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB fixtures &amp;amp; assertions&lt;/strong&gt;: validate everything inside and out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast, IDE-friendly, minimal&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Perfect for:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Testing REST API behavior end-to-end&lt;/li&gt;
&lt;li&gt;Validating JSON responses and DB state&lt;/li&gt;
&lt;li&gt;Reproducing edge cases and regression bugs&lt;/li&gt;
&lt;li&gt;Debugging logic by stepping through real code paths&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/rom8726/testy@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up your test directory like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/tests
  ├─ cases/     # YAML test scenarios
  ├─ fixtures/  # YAML DB fixtures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;go test&lt;/code&gt; — and debug away.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;testy&lt;/code&gt; is Apache-2.0 licensed and open to contributions.&lt;br&gt;
If you like writing tests like a human, check it out:&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rom8726/testy" rel="noopener noreferrer"&gt;github.com/rom8726/testy&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Ansible Playbook for PostgreSQL Patroni Cluster</title>
      <dc:creator>Roman Chudov</dc:creator>
      <pubDate>Wed, 09 Jul 2025 03:56:03 +0000</pubDate>
      <link>https://dev.to/roman_chudov_aae89a53d641/ansible-playbook-for-postgresql-patroni-cluster-366</link>
      <guid>https://dev.to/roman_chudov_aae89a53d641/ansible-playbook-for-postgresql-patroni-cluster-366</guid>
      <description>&lt;p&gt;I've recently published an Ansible playbook for deploying a PostgreSQL high-availability cluster powered by Patroni, with all the essential components included. It's a ready-to-go setup for development and testing environments.&lt;/p&gt;

&lt;p&gt;🔧 &lt;strong&gt;What’s inside&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 16 with Patroni&lt;/strong&gt; for automatic failover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;etcd&lt;/strong&gt; for distributed state management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PgBouncer&lt;/strong&gt; for connection pooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HAProxy&lt;/strong&gt; for routing between master and replicas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keepalived&lt;/strong&gt; for floating Virtual IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node Exporter&lt;/strong&gt; + &lt;strong&gt;Postgres Exporter&lt;/strong&gt; for monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🧩 &lt;strong&gt;Architecture&lt;/strong&gt;:&lt;br&gt;
Client → Keepalived VIP → HAProxy → PgBouncer → PostgreSQL&lt;/p&gt;

&lt;p&gt;🚀 The setup is simple: define your servers in inventory.ini, set a few variables, and run make up. In just a few minutes, you’ll have a fully working Patroni cluster ready for testing or experimentation.&lt;/p&gt;

&lt;p&gt;⚠️ Disclaimer: This is a dev/test configuration. Don’t use it in production without proper hardening (passwords, TLS, firewalls, limited SSH, etc).&lt;/p&gt;

&lt;p&gt;👉 GitHub: &lt;a href="https://github.com/rom8726/ansible-patroni" rel="noopener noreferrer"&gt;https://github.com/rom8726/ansible-patroni&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>patroni</category>
      <category>ansible</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
