<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Neenad Ingole | Hashnode]]></title><description><![CDATA[Follow my tech journey through my blog post.]]></description><link>https://hashnode.iamninad.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 07:04:18 GMT</lastBuildDate><atom:link href="https://hashnode.iamninad.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Secret For Testing UUID Generation in Golang]]></title><description><![CDATA[In today’s fast-paced world of software development, generating unique identifiers is an absolute necessity. In golang, there are various packages available that can help you achieve this. One such package is the Google UUID package.
Click to follow ...]]></description><link>https://hashnode.iamninad.com/secret-for-testing-uuid-generation-in-golang</link><guid isPermaLink="true">https://hashnode.iamninad.com/secret-for-testing-uuid-generation-in-golang</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Tue, 09 May 2023 18:24:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1K9T5YiZ2WU/upload/f96b97667ab8f8f6de1b54abe467bfc8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today’s fast-paced world of software development, generating unique identifiers is an absolute necessity. In golang, there are various packages available that can help you achieve this. One such package is the <a target="_blank" href="https://github.com/google/uuid">Google UUID package</a>.</p>
<p><a target="_blank" href="https://hashnode.com/@neenadingole"><strong>Click to follow me on hashnode and read all my upcoming stories!</strong></a></p>
<p>To get started, you first need to import the Google UUID library into your Golang project.</p>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get -u github.com/google/uuid
</code></pre>
<p>Once you have imported the library, you could use the <a target="_blank" href="http://uuid.New"><code>uuid.New</code></a><code>()</code> method to generate a random UUID. This method takes no arguments and is the easiest one to get started. 🆒</p>
<p>The challenge with using this is how to create a Unit Test for the code 👊. In the Unit Test, we expect to get a constant known behaviour from our code. But, the behaviour of <a target="_blank" href="http://uuid.New"><code>uuid.New</code></a><code>()</code> method is to generate random sequences on each execution.</p>
<p>Let me show you what I mean by using a small code. Below, I am writing a very simple test. I will run this test multiple times and we will check the output. All the generated UUIDs for each different run will be random.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_UUIDGeneration</span><span class="hljs-params">(t *testing.T)</span></span> {
 uuid := uuid.New()

 fmt.Println(fmt.Sprintf(<span class="hljs-string">"UUID: %s"</span>, uuid))
}

------------ OUTPUT ---------------

UUID: c26a1010-a207<span class="hljs-number">-47</span>cb-b399<span class="hljs-number">-9</span>de8acad3bba
UUID: e4c1776f-cd94<span class="hljs-number">-4710</span>-b5eb<span class="hljs-number">-710</span>c081df916
UUID: <span class="hljs-number">923</span>c129c-ba0b<span class="hljs-number">-4534</span>-aadf-db98470fd99c
</code></pre>
<blockquote>
<p><em>Note: If you run the above test locally with go test make sure to disable the cache. Use</em><code>--count=1</code> <em>in the command to disable caching otherwise the test will generate same uuids</em></p>
</blockquote>
<p>Consider you are writing a service layer that generates the UUID and persist it in the DB. You write a Unit Test but cannot assert the value of the generated UUID. Because, on every new execution on the CI, the value will be different. How will you test the code and avoid randomness?</p>
<p>Here is a small code to mimic a service layer and the test:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Employee <span class="hljs-keyword">struct</span> {
 ID        uuid.UUID <span class="hljs-string">`json:"id"`</span>
 FirstName <span class="hljs-keyword">string</span>    <span class="hljs-string">`json:"first_name"`</span>
 LastName  <span class="hljs-keyword">string</span>    <span class="hljs-string">`json:"last_name"`</span>
}

<span class="hljs-comment">// Storage interface is used to persist employee to DB</span>
<span class="hljs-keyword">type</span> Storage <span class="hljs-keyword">interface</span> {
 Save(employee Employee) error
}

<span class="hljs-comment">// Employee service orchestrate the creation of employee</span>
<span class="hljs-keyword">type</span> EmployeeService <span class="hljs-keyword">struct</span> {
 storage Storage
}

<span class="hljs-comment">// For simplicity the layer only calls the storage layer to persist the value.</span>
<span class="hljs-comment">// However, in a real world the service layer will do more than this.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *EmployeeService)</span> <span class="hljs-title">Create</span><span class="hljs-params">(firstName, lastName <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
 employee := Employee{
  ID:        uuid.New(),
  FirstName: firstName,
  LastName:  lastName,
 }

 <span class="hljs-keyword">return</span> e.storage.Save(employee)
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> mockStorage <span class="hljs-keyword">struct</span> {
 mock.Mock
}

<span class="hljs-comment">// We mock the storage layer to see if the value and type is correctly passed</span>
<span class="hljs-comment">// to the method</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *mockStorage)</span> <span class="hljs-title">Save</span><span class="hljs-params">(employee Employee)</span> <span class="hljs-title">error</span></span> {
 args := m.Called(employee)
 <span class="hljs-keyword">return</span> args.Error(<span class="hljs-number">0</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestEmployeeService_Create</span><span class="hljs-params">(t *testing.T)</span></span> {
 t.Parallel()

 expectedEmployee := Employee{
  ID:        uuid.New(), <span class="hljs-comment">// This will always generate new random uuid</span>
  FirstName: <span class="hljs-string">"John"</span>,
  LastName:  <span class="hljs-string">"Doe"</span>,
 }

 storage := &amp;mockStorage{}
 employeeService := EmployeeService{storage: storage}

 storage.On(<span class="hljs-string">"Save"</span>, mock.MatchedBy(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">bool</span></span> {
  employee, ok := x.(Employee)

  <span class="hljs-keyword">if</span> !ok {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">return</span> assert.EqualValues(t, expectedEmployee, employee)
 })).Return(<span class="hljs-literal">nil</span>)

 err := employeeService.Create(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>)

 assert.NoError(t, err)
 storage.AssertExpectations(t)
}
</code></pre>
<p>If I run the above test it will <strong>fail</strong> 🔴 :</p>
<pre><code class="lang-go">=== RUN   TestEmployeeService_Create
=== PAUSE TestEmployeeService_Create
=== CONT  TestEmployeeService_Create
/tests/data_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">89</span>:
      Error Trace:    /tests/data_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">89</span>
                      /tests/value.<span class="hljs-keyword">go</span>:<span class="hljs-number">586</span>
                      /tests/value.<span class="hljs-keyword">go</span>:<span class="hljs-number">370</span>
                      /tests/data_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">66</span>
                      /tests/data_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">58</span>
                      /tests/data_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">92</span>
      Error:          Not equal:
                      expected: tests.Employee{ID:uuid.UUID{<span class="hljs-number">0xeb</span>, <span class="hljs-number">0x5</span>, <span class="hljs-number">0x6f</span>, <span class="hljs-number">0x11</span>, <span class="hljs-number">0x47</span>, <span class="hljs-number">0x8</span>, <span class="hljs-number">0x42</span>, <span class="hljs-number">0x2d</span>, <span class="hljs-number">0xb5</span>, <span class="hljs-number">0x23</span>, <span class="hljs-number">0x47</span>, <span class="hljs-number">0x2</span>, <span class="hljs-number">0x40</span>, <span class="hljs-number">0x18</span>, <span class="hljs-number">0x85</span>, <span class="hljs-number">0x94</span>}, FirstName:<span class="hljs-string">"John"</span>, LastName:<span class="hljs-string">"Doe"</span>}
                      actual  : tests.Employee{ID:uuid.UUID{<span class="hljs-number">0xb4</span>, <span class="hljs-number">0xb7</span>, <span class="hljs-number">0x8f</span>, <span class="hljs-number">0x21</span>, <span class="hljs-number">0x36</span>, <span class="hljs-number">0xc8</span>, <span class="hljs-number">0x4e</span>, <span class="hljs-number">0x3e</span>, <span class="hljs-number">0x9a</span>, <span class="hljs-number">0xa3</span>, <span class="hljs-number">0x96</span>, <span class="hljs-number">0x79</span>, <span class="hljs-number">0xd3</span>, <span class="hljs-number">0xcc</span>, <span class="hljs-number">0xbc</span>, <span class="hljs-number">0xdc</span>}, FirstName:<span class="hljs-string">"John"</span>, LastName:<span class="hljs-string">"Doe"</span>}

                      Diff:
                      --- Expected
                      +++ Actual
                      @@ <span class="hljs-number">-2</span>,<span class="hljs-number">3</span> +<span class="hljs-number">2</span>,<span class="hljs-number">3</span> @@
                        ID: (uuid.UUID) (<span class="hljs-built_in">len</span>=<span class="hljs-number">16</span>) {
                      -  <span class="hljs-number">00000000</span>  eb <span class="hljs-number">05</span> <span class="hljs-number">6</span>f <span class="hljs-number">11</span> <span class="hljs-number">47</span> <span class="hljs-number">08</span> <span class="hljs-number">42</span> <span class="hljs-number">2</span>d  b5 <span class="hljs-number">23</span> <span class="hljs-number">47</span> <span class="hljs-number">02</span> <span class="hljs-number">40</span> <span class="hljs-number">18</span> <span class="hljs-number">85</span> <span class="hljs-number">94</span>  |..o.G.B-.#G.@...|
                      +  <span class="hljs-number">00000000</span>  b4 b7 <span class="hljs-number">8</span>f <span class="hljs-number">21</span> <span class="hljs-number">36</span> c8 <span class="hljs-number">4</span>e <span class="hljs-number">3</span>e  <span class="hljs-number">9</span>a a3 <span class="hljs-number">96</span> <span class="hljs-number">79</span> d3 cc bc dc  |...!<span class="hljs-number">6.</span>N&gt;...y....|
                        },
      Test:           TestEmployeeService_Create
</code></pre>
<p>You could see that the UUIDs were generated by the service layer in the logs. To use <code>assert.EqualValues</code> or <code>assert.Equals</code> on a struct, all the attribute values should match. The UUID in the <code>expected</code> employee instance is different. <code>assert.Equals</code> would need the UUIDs that are known to the test when you write it, randomness here is the big problem.</p>
<h2 id="heading-what-would-you-do-to-avoid-this"><strong>What would you do to avoid this?</strong></h2>
<p>Many people to avoid the problem will think of not writing the test 😅. Let’s see the different ways to overcome this problem. But please DO NOT AVOID WRITING TESTS!!!! 😃</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjNkYWI1OGI2MzRkNzk5MjQwNDNkYzc5ODc0MjA5Yjk2NTgyODJjOCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/w89ak63KNl0nJl80ig/giphy.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-1-manually-check-all-the-struct-attributes-and-avoid-the-uuid-check"><strong>1. Manually check all the struct attributes and avoid the UUID check</strong></h2>
<p>Our assumption is that this library is open-source and well-maintained by the community. We can rely on the fact that things will work fine. We skip the UUID check part but check other attributes eg:</p>
<pre><code class="lang-go">storage.On(<span class="hljs-string">"Save"</span>, mock.MatchedBy(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">bool</span></span> {
  employee, ok := x.(Employee)

  <span class="hljs-keyword">if</span> !ok {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">return</span> employee.ID != uuid.Nil &amp;&amp; employee.FirstName == expectedEmployee.FirstName &amp;&amp; employee.LastName == expectedEmployee.LastName
 })).Return(<span class="hljs-literal">nil</span>)
</code></pre>
<p>This is ok if you have a struct with few attributes. But, this will become a headache if you have 15–20 attributes. This indeed is not a scalable approach.</p>
<h2 id="heading-2-parse-the-uuid-to-check-for-error"><strong>2. Parse the UUID To Check For Error</strong></h2>
<p>Parse the UUID to check if the service generates the UUID and if there is no issue. For other attributes do exactly what we did in the previous example. This will also suffer from the same problem as the previous solution</p>
<pre><code class="lang-go">_, err := uuid.Parse(employee.ID.String())

<span class="hljs-keyword">return</span> err == <span class="hljs-literal">nil</span> &amp;&amp; employee.FirstName == expectedEmployee.FirstName &amp;&amp; employee.LastName == expectedEmployee.LastName
</code></pre>
<h2 id="heading-3-pass-a-uuid-generator-function-type"><strong>3. Pass a UUID Generator Function Type</strong></h2>
<p>A separate uuid generator function is passed to the struct to generate the uuid. A test generator is then used in the unit test to mock and pass a static UUID. An assertion on this static value will verify the behaviour of the code. Eg:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> UUIDGenerator <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span> <span class="hljs-title">uuid</span>.<span class="hljs-title">UUID</span></span>

<span class="hljs-keyword">var</span> DefaultUUIDGenerator = <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span> <span class="hljs-title">uuid</span>.<span class="hljs-title">UUID</span></span> {
 <span class="hljs-keyword">return</span> uuid.New()
}

<span class="hljs-keyword">type</span> EmployeeService <span class="hljs-keyword">struct</span> {
 storage       Storage
 uuidGenerator UUIDGenerator
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *EmployeeService)</span> <span class="hljs-title">Create</span><span class="hljs-params">(firstName, lastName <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
 employee := Employee{
  ID:        e.uuidGenerator(),
  FirstName: firstName,
  LastName:  lastName,
 }

 <span class="hljs-keyword">return</span> e.storage.Save(employee)
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestEmployeeService_Create</span><span class="hljs-params">(t *testing.T)</span></span> {
 t.Parallel()

 expectedID := uuid.New()

 expectedEmployee := Employee{
  ID:        expectedID,
  FirstName: <span class="hljs-string">"John"</span>,
  LastName:  <span class="hljs-string">"Doe"</span>,
 }

 storage := &amp;mockStorage{}

 uuidGenerator := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span> <span class="hljs-title">uuid</span>.<span class="hljs-title">UUID</span></span> {
  <span class="hljs-keyword">return</span> expectedID
 }

 employeeService := EmployeeService{storage: storage, uuidGenerator: uuidGenerator}

 storage.On(<span class="hljs-string">"Save"</span>, mock.MatchedBy(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">bool</span></span> {
  employee, ok := x.(Employee)

  <span class="hljs-keyword">if</span> !ok {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">return</span> assert.EqualValues(t, expectedEmployee, employee)
 })).Return(<span class="hljs-literal">nil</span>)

 err := employeeService.Create(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>)

 assert.NoError(t, err)
 storage.AssertExpectations(t)
}
</code></pre>
<p>This solution is also good. It will allow us to assert the entire entity and don’t have to check every field like in the previous two examples. This may become a problem if you have to use it at many places in your code base.</p>
<p>One could also add an <code>interface</code> to generate the UUID and mock the interface in the test to provide the static UUID. This is also an alternative to the function type and is acceptable.</p>
<p>Both the function type and interface will help to reuse and it will work fine. But what if I tell you there is a <strong><em>secret</em></strong> way and you don’t have to do all these shenanigans? 👇</p>
<h2 id="heading-4-use-setrand"><strong>4. Use SetRand 🚀</strong></h2>
<p>Let’s look at the <a target="_blank" href="https://pkg.go.dev/github.com/google/uuid#SetRand">documentation of the library</a>. There is a way to generate deterministic UUID, using <code>uuid.SetRand()</code> . If we use the correct Seed Source to the<code>SetRand</code> function it will allow us to get deterministic UUIDs.</p>
<p>Let’s see how:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_uuidTest</span><span class="hljs-params">(t *testing.T)</span></span> {
 uuid.SetRand(rand.New(rand.NewSource(<span class="hljs-number">1</span>)))

 val1 := uuid.New()
 val2 := uuid.New()

 assert.EqualValues(t, uuid.MustParse(<span class="hljs-string">"52fdfc07-2182-454f-963f-5f0f9a621d72"</span>), val1, fmt.Sprintf(<span class="hljs-string">"generated %v"</span>, val1))
 assert.EqualValues(t, uuid.MustParse(<span class="hljs-string">"9566c74d-1003-4c4d-bbbb-0407d1e2c649"</span>), val2, fmt.Sprintf(<span class="hljs-string">"generated %v"</span>, val2))
}
</code></pre>
<p>I add another test above to show how to use the <code>SetRand</code> method. I pass a new <code>*rand.Rand</code> instance by setting the seed value to the <code>rand.NewSource(1)</code> . When you do this the UUIDs generated will be deterministic in nature. They will be exactly the same on each run irrespective of the platform you run on.</p>
<p>To verify this, I ran the same example on the go playground <a target="_blank" href="https://go.dev/play/p/Eaa5OR0P9f6">here</a>. You will see below that the generated UUIDs match our assertion value.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683656177131/7ae26d35-807c-4f37-a3dc-500eabe2c6c9.webp" alt="UUID generated from the go-playground example" class="image--center mx-auto" /></p>
<p>So let’s update our existing test to see how to use this new trick.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestEmployeeService_Create</span><span class="hljs-params">(t *testing.T)</span></span> {
 uuid.SetRand(rand.New(rand.NewSource(<span class="hljs-number">1</span>)))

 expectedEmployee := Employee{
  ID:        uuid.MustParse(<span class="hljs-string">"52fdfc07-2182-454f-963f-5f0f9a621d72"</span>),
  FirstName: <span class="hljs-string">"John"</span>,
  LastName:  <span class="hljs-string">"Doe"</span>,
 }

 storage := &amp;mockStorage{}

 employeeService := EmployeeService{storage: storage}

 storage.On(<span class="hljs-string">"Save"</span>, mock.MatchedBy(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">bool</span></span> {
  employee, ok := x.(Employee)

  <span class="hljs-keyword">if</span> !ok {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">return</span> assert.EqualValues(t, expectedEmployee, employee)
 })).Return(<span class="hljs-literal">nil</span>)

 err := employeeService.Create(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>)

 assert.NoError(t, err)
 storage.AssertExpectations(t)
}
</code></pre>
<p>There is one caveat with this approach. The <code>math/rand</code> source that we have used is not thread-safe. As per the documentation:</p>
<pre><code class="lang-plaintext">// NewSource returns a new pseudo-random Source seeded with the given value.
// Unlike the default Source used by top-level functions, this source is not
// safe for concurrent use by multiple goroutines.
// The returned Source implements Source64.
</code></pre>
<p>So having multiple tests running parallel would cause a data race issue. I tried to overcome this but there’s no straightforward way. I don’t want to add another layer of complexity to avoid the first one 😅 it’s not productive at all.</p>
<p>I looked into how these libraries did the unit test. I realised that both <a target="_blank" href="https://github.com/google/uuid/blob/master/uuid_test.go#L501">google/uuid</a> and <a target="_blank" href="https://github.com/golang/go/blob/master/src/math/rand/rand_test.go#L371">golang math/rand package</a> do not use <code>t.Parallel()</code> for the tests. So, The simplest way to fix the issue would be to remove <code>t.Parallel()</code> from all such unit tests ✅.</p>
<h2 id="heading-5-use-googlego-cmp"><strong>5. Use google/go-cmp</strong></h2>
<p>After publishing this post, my awesome readers reached out to me with one more approach. I am really thankful for all the feedback. The <a target="_blank" href="https://medium.com/itnext/github.com/google/go-cmp">google/go-cmp</a> library allows to skip/ignore fields from comparison.</p>
<p>There are two kinds of people, one who wants to compare all the fields of the struct and some would prefer to ignore the UUID check because the google/uuid library is battle tested. So based on which category you belong to you could use the previous approach or you could use this approach.</p>
<p>Let’s rewrite our existing test to use the <code>cmpopts.IgnoreFields</code></p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestEmployeeService_Create</span><span class="hljs-params">(t *testing.T)</span></span> {
 expectedEmployee := Employee{
  FirstName: <span class="hljs-string">"John"</span>,
  LastName:  <span class="hljs-string">"Doe"</span>,
 }

 storage := &amp;mockStorage{}

 employeeService := EmployeeService{storage: storage}

 storage.On(<span class="hljs-string">"Save"</span>, mock.MatchedBy(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">bool</span></span> {
  actual, ok := x.(Employee)

  <span class="hljs-keyword">if</span> !ok {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">var</span> diff <span class="hljs-keyword">string</span>
  <span class="hljs-keyword">if</span> diff = cmp.Diff(expectedEmployee, actual, cmpopts.IgnoreFields(Employee{}, <span class="hljs-string">"ID"</span>)); diff != <span class="hljs-string">""</span> {
   t.Errorf(<span class="hljs-string">"Save() mismatch (-want +got):\n%s"</span>, diff)
  }

  <span class="hljs-keyword">return</span> diff == <span class="hljs-string">""</span>
 })).Return(<span class="hljs-literal">nil</span>)

 err := employeeService.Create(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Doe"</span>)

 assert.NoError(t, err)
 storage.AssertExpectations(t)
}
</code></pre>
<p>On failure ☠️ the test will show a diff comparison of the fields that didn’t match like below</p>
<pre><code class="lang-go">=== RUN   TestEmployeeService_Create
    /Users/neenadingole/codes/opensource/gotest-ls/main_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">275</span>: Save() mismatch (-want +got):
          main.Employee{
                ... <span class="hljs-comment">// 1 ignored field</span>
        -       FirstName: <span class="hljs-string">"Face"</span>,
        +       FirstName: <span class="hljs-string">"John"</span>,
                LastName:  <span class="hljs-string">"Doe"</span>,
          }
    /Users/neenadingole/codes/opensource/gotest-ls/main_test.<span class="hljs-keyword">go</span>:<span class="hljs-number">275</span>: Save() mismatch (-want +got):
          main.Employee{
                ... <span class="hljs-comment">// 1 ignored field</span>
        -       FirstName: <span class="hljs-string">"Face"</span>,
        +       FirstName: <span class="hljs-string">"John"</span>,
                LastName:  <span class="hljs-string">"Doe"</span>,
          }
--- FAIL: TestEmployeeService_Create (<span class="hljs-number">0.00</span>s)
<span class="hljs-built_in">panic</span>:

mock: Unexpected Method Call
-----------------------------

Save(main.Employee)
                <span class="hljs-number">0</span>: main.Employee{ID:uuid.UUID{<span class="hljs-number">0x4d</span>, <span class="hljs-number">0x8e</span>, <span class="hljs-number">0x29</span>, <span class="hljs-number">0x66</span>, <span class="hljs-number">0x29</span>, <span class="hljs-number">0xdd</span>, <span class="hljs-number">0x4b</span>, <span class="hljs-number">0xbe</span>, <span class="hljs-number">0xa7</span>, <span class="hljs-number">0x30</span>, <span class="hljs-number">0x34</span>, <span class="hljs-number">0xa0</span>, <span class="hljs-number">0x7a</span>, <span class="hljs-number">0xe7</span>, <span class="hljs-number">0xb3</span>, <span class="hljs-number">0x94</span>}, FirstName:<span class="hljs-string">"John"</span>, LastName:<span class="hljs-string">"Doe"</span>}

The closest call I have is:

Save(mock.argumentMatcher)
                <span class="hljs-number">0</span>: mock.argumentMatcher{fn:reflect.Value{typ:(*reflect.rtype)(<span class="hljs-number">0x12fd9e0</span>), ptr:(unsafe.Pointer)(<span class="hljs-number">0xc00010ce40</span>), flag:<span class="hljs-number">0x13</span>}}


Diff: <span class="hljs-number">0</span>: FAIL:  (main.Employee={<span class="hljs-number">4</span>d8e2966<span class="hljs-number">-29</span>dd<span class="hljs-number">-4</span>bbe-a730<span class="hljs-number">-34</span>a07ae7b394 John Doe}) not matched by <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(<span class="hljs-keyword">interface</span> {})</span> <span class="hljs-title">bool</span> [<span class="hljs-title">recovered</span>]</span>
        <span class="hljs-built_in">panic</span>:
</code></pre>
<p>It becomes easy to navigate the test and fix the issue and you could ignore multiple fields from the struct comparison.</p>
<p>I am going to use this library in my projects. This is also a great learning for me by writing and sharing this blog post.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>I hope this helps you to write better tests for your code when using the google uuid package. I am very keen on testing code and finding ways to ease testing for developers. Having proper tests helps the team move faster. It also provides a safety net to avoid accidental bug leaks or any dev mistakes.</p>
<p>There are different pros and cons to each of the methods discussed. It will depend on what works for you and what doesn’t. For a small codebase, I don’t like using the generator function approach. I would use the <code>SetRand</code> for such cases. For larger struct or ignoring the field comparison for some then I would also prefer the go-cmp library which does a really good job.</p>
<p>I was curious to see if there is any simple way for unit testing. This post is my research on this topic, but I know there may be some approaches I missed. So, I would like to hear those from others. I will definitely be happy to update my findings with your thoughts 🙏.</p>
<hr />
<p><strong>Thank you so much for taking the time to read my blog and sharing it with others. Your ❤️ support means a lot to me, and I truly appreciate it. It’s people like you who inspire me to keep writing and sharing my thoughts with the world. Thank you again for your kindness and support.</strong></p>
<h3 id="heading-supporting-information"><strong>Supporting Information</strong></h3>
<p>A Sample Test From google/uuid package:</p>
<pre><code class="lang-go"><span class="hljs-comment">// No t.Parallel() used</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestSetRand</span><span class="hljs-params">(t *testing.T)</span></span> {
 myString := <span class="hljs-string">"805-9dd6-1a877cb526c678e71d38-7122-44c0-9b7c-04e7001cc78783ac3e82-47a3-4cc3-9951-13f3339d88088f5d685a-11f7-4078-ada9-de44ad2daeb7"</span>

 SetRand(strings.NewReader(myString))
 uuid1 := New()
 uuid2 := New()

 SetRand(strings.NewReader(myString))
 uuid3 := New()
 uuid4 := New()

 <span class="hljs-keyword">if</span> uuid1 != uuid3 {
  t.Errorf(<span class="hljs-string">"expected duplicates, got %q and %q"</span>, uuid1, uuid3)
 }
 <span class="hljs-keyword">if</span> uuid2 != uuid4 {
  t.Errorf(<span class="hljs-string">"expected duplicates, got %q and %q"</span>, uuid2, uuid4)
 }
}
</code></pre>
<p>A sample test from golang <code>math/rand</code> package:</p>
<pre><code class="lang-go"><span class="hljs-comment">// No t.Parallel() used</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">testReadUniformity</span><span class="hljs-params">(t *testing.T, n <span class="hljs-keyword">int</span>, seed <span class="hljs-keyword">int64</span>)</span></span> {
 r := New(NewSource(seed))
 buf := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, n)
 nRead, err := r.Read(buf)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  t.Errorf(<span class="hljs-string">"Read err %v"</span>, err)
 }
 <span class="hljs-keyword">if</span> nRead != n {
  t.Errorf(<span class="hljs-string">"Read returned unexpected n; %d != %d"</span>, nRead, n)
 }

 <span class="hljs-comment">// Expect a uniform distribution of byte values, which lie in [0, 255].</span>
 <span class="hljs-keyword">var</span> (
  mean       = <span class="hljs-number">255.0</span> / <span class="hljs-number">2</span>
  stddev     = <span class="hljs-number">256.0</span> / math.Sqrt(<span class="hljs-number">12.0</span>)
  errorScale = stddev / math.Sqrt(<span class="hljs-keyword">float64</span>(n))
 )

 expected := &amp;statsResults{mean, stddev, <span class="hljs-number">0.10</span> * errorScale, <span class="hljs-number">0.08</span> * errorScale}

 <span class="hljs-comment">// Cast bytes as floats to use the common distribution-validity checks.</span>
 samples := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">float64</span>, n)
 <span class="hljs-keyword">for</span> i, val := <span class="hljs-keyword">range</span> buf {
  samples[i] = <span class="hljs-keyword">float64</span>(val)
 }
 <span class="hljs-comment">// Make sure that the entire set matches the expected distribution.</span>
 checkSampleDistribution(t, samples, expected)
}

<span class="hljs-comment">// No t.Parallel() used</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestReadUniformity</span><span class="hljs-params">(t *testing.T)</span></span> {
 testBufferSizes := []<span class="hljs-keyword">int</span>{
  <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">7</span>, <span class="hljs-number">64</span>, <span class="hljs-number">1024</span>, <span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">16</span>, <span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">20</span>,
 }
 <span class="hljs-keyword">for</span> _, seed := <span class="hljs-keyword">range</span> testSeeds {
  <span class="hljs-keyword">for</span> _, n := <span class="hljs-keyword">range</span> testBufferSizes {
   testReadUniformity(t, n, seed)
  }
 }
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[I Bet You Didn't Know This IntelliJ Feature]]></title><description><![CDATA[Are you an Intellij, Goland or DataGrip User? Like me, are you running the SQL statements every time you want to see the change reflected on the table? 
If Yes, I found a small trick in Intellij and DataGrip that could watch your SQL query results. I...]]></description><link>https://hashnode.iamninad.com/i-bet-you-didnt-know-this-intellij-feature</link><guid isPermaLink="true">https://hashnode.iamninad.com/i-bet-you-didnt-know-this-intellij-feature</guid><category><![CDATA[Jetbrains]]></category><category><![CDATA[intellij]]></category><category><![CDATA[datagrip]]></category><category><![CDATA[Databases]]></category><category><![CDATA[goland]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Tue, 18 Apr 2023 06:45:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lRoX0shwjUQ/upload/c64c451ef1cc75acc62119c24036baaf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you an Intellij, Goland or DataGrip User? Like me, are you running the SQL statements every time you want to see the change reflected on the table? </p>
<p>If Yes, I found a small trick in Intellij and DataGrip that could watch your SQL query results. It was always there but I overlooked it. Once I discovered it, it changed my life for debugging or development.</p>
<p>It’s not a trick actually 😅 but a feature built within the IntelliJ products. It could easily go ignored but let’s see how to use it</p>
<p>When you run any query you will see the Result window below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681799874219/730f4320-eac9-471d-8250-b5184b6c3f5d.png" alt class="image--center mx-auto" /></p>
<p>You would see this clock icon in the toolbar that does all the trick. It was always there but I never thought it would be so useful 😍</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681799898650/3aef8424-0d9e-4af1-9f70-16bca687d73e.png" alt class="image--center mx-auto" /></p>
<p>When you click on it, the below options would be available. Each choice you select will refresh the query results after the selected delay.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681799913086/724978c4-1dcb-40ad-8300-2111e53206be.png" alt class="image--center mx-auto" /></p>
<p>A small tip to remember is to make sure you <strong>“Pin” 📌</strong> the result window. When you do it, you could run other queries along with the watch window. </p>
<p>The below video shows a small demo of this feature. In the demo, I am watching the result of a <code>SELECT</code> query. When I update a record it is shown in the result window without running the <code>SELECT</code> query again.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681799932798/ec868ac1-7a06-420d-9d14-a46a7be25765.gif" alt class="image--center mx-auto" /></p>
<p><code>5s</code> seem long? What if I want to keep the watch period lower than 5s? You could use the <code>Custom</code> option provided. With this, you could also monitor your query changes at a 1s rate interval</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681799964607/fee8610a-77fb-49bd-b6cc-c822ba4c830c.png" alt class="image--center mx-auto" /></p>
<p>Well, that's all for this post. I hope next time this small utility will come in handy for you. Don’t run the query every time to see the change. IntelliJ tools are a powerhouse of such small utilities which are very easily ignored by us.</p>
<p>Thanks! and Happy Coding! Don’t forget to follow me, like (❤️) for motivation and comment on some IntelliJ utilities that helped you! 🍻</p>
]]></content:encoded></item><item><title><![CDATA[Preventing DB Connection Leaks in Golang: Lesson from a Billion Dollar Mistake]]></title><description><![CDATA[This post is about why we need a separate transaction layer? How to extract it, and test it using Unit and Integration Test.

In my previous blog post, “A Billion Dollar Go Mistake,”. I discussed a common but naive mistake that developers make in Gol...]]></description><link>https://hashnode.iamninad.com/preventing-db-connection-leaks-in-golang-lesson-from-a-billion-dollar-mistake</link><guid isPermaLink="true">https://hashnode.iamninad.com/preventing-db-connection-leaks-in-golang-lesson-from-a-billion-dollar-mistake</guid><category><![CDATA[golang]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Databases]]></category><category><![CDATA[software development]]></category><category><![CDATA[Golang developer]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Mon, 03 Apr 2023 09:05:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/KGzXJB6zneM/upload/b6bed54a15bee236593b7155e271cfcf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This post is about why we need a separate transaction layer? How to extract it, and test it using Unit and Integration Test.</p>
</blockquote>
<p>In my previous blog post, “<a target="_blank" href="https://blog.iamninad.com/a-billion-dollar-go-mistake">A Billion Dollar Go Mistake</a>,”. I discussed a common but naive mistake that developers make in Golang, which can lead to connection leaks. Although I offered several ways to fix this problem, one issue still bothered both myself and my readers. We can solve this problem by abstracting the transaction mechanism into a different layer. Many people reached out to me to suggest this solution.</p>
<p>So, my first curiosity question I want to reason:</p>
<p><mark>"</mark><strong><mark>Do we really need a Transaction layer?"</mark></strong></p>
<p>I read several other blogs and GitHub codes. I noticed that many of them were great but did not include tests to prove that the abstraction layer works. But, we cannot confirm that the transaction layer works without tests. A connection leak may occur, and we cannot detect it by examining the code.</p>
<p>This led me to another important question:</p>
<p><strong><mark>"How can I prove that the new layer has no connection leaks?"</mark></strong></p>
<p>Testing is the only way to verify whether something works as expected. I decided to start with two straightforward options that came to my mind:</p>
<ol>
<li><p>I will run some queries that change the table and verify that the code commits the data to the database.</p>
</li>
<li><p>I will use some mechanisms that expose the connection information. I could verify that the code closes the connections at the end.</p>
</li>
</ol>
<p>The first approach is simple, and I have used it many times in my projects 😅. But, the second approach is completely unknown to me 👾. When I face difficulties in understanding how things work, I usually look at the source code of the language.</p>
<p>To resolve my problem, I looked for help in the standard database library code of Golang. I found something that could help me: the <code>DBStats</code> struct.</p>
<pre><code class="lang-go"><span class="hljs-comment">// DBStats contains database statistics.</span>
<span class="hljs-keyword">type</span> DBStats <span class="hljs-keyword">struct</span> {
 MaxOpenConnections <span class="hljs-keyword">int</span> <span class="hljs-comment">// Maximum number of open connections to the database.</span>

 <span class="hljs-comment">// Pool Status</span>
 OpenConnections <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of established connections both in use and idle.</span>
 InUse           <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of connections currently in use.</span>
 Idle            <span class="hljs-keyword">int</span> <span class="hljs-comment">// The number of idle connections.</span>

 <span class="hljs-comment">// Counters</span>
 WaitCount         <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections waited for.</span>
 WaitDuration      time.Duration <span class="hljs-comment">// The total time blocked waiting for a new connection.</span>
 MaxIdleClosed     <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetMaxIdleConns.</span>
 MaxIdleTimeClosed <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetConnMaxIdleTime.</span>
 MaxLifetimeClosed <span class="hljs-keyword">int64</span>         <span class="hljs-comment">// The total number of connections closed due to SetConnMaxLifetime.</span>
}
</code></pre>
<p>This is precisely the solution I was searching for. Upon completion of the transaction, the <code>MaxOpenConnections</code> and <code>InUse</code> counts should be <code>0</code>. If this is not the case, it indicates a potential leak in the abstraction layer. </p>
<p>I am thrilled to have found the ideal solution to address the second scenario 🎉</p>
<p>To access the <code>DBStats</code>, we can use the <code>Stats()</code> method on the <code>sql.DB</code> instance, as shown below:</p>
<pre><code class="lang-go">db, _ := sqlx.Open(<span class="hljs-string">"postgres"</span>, <span class="hljs-string">"postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"</span>)
db.Stats()
</code></pre>
<p>The source code for the <code>Stats()</code> method is shown below. You could read more about it’s working and how go code records the information by following the source code <a target="_blank" href="https://github.com/golang/go/blob/master/src/database/sql/sql.go#L1155">here</a></p>
<pre><code class="lang-go"><span class="hljs-comment">// Stats returns database statistics.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(db *DB)</span> <span class="hljs-title">Stats</span><span class="hljs-params">()</span> <span class="hljs-title">DBStats</span></span> {
 wait := db.waitDuration.Load()

 db.mu.Lock()
 <span class="hljs-keyword">defer</span> db.mu.Unlock()

 stats := DBStats{
  MaxOpenConnections: db.maxOpen,

  Idle:            <span class="hljs-built_in">len</span>(db.freeConn),
  OpenConnections: db.numOpen,
  InUse:           db.numOpen - <span class="hljs-built_in">len</span>(db.freeConn),

  WaitCount:         db.waitCount,
  WaitDuration:      time.Duration(wait),
  MaxIdleClosed:     db.maxIdleClosed,
  MaxIdleTimeClosed: db.maxIdleTimeClosed,
  MaxLifetimeClosed: db.maxLifetimeClosed,
 }
 <span class="hljs-keyword">return</span> stats
}
</code></pre>
<p>All this is enough for me to implement the Transaction Layer. So, Let’s delve into the code now. But before we get started, let me introduce you to some of the libraries that I will be using in the code: </p>
<ul>
<li><p><a target="_blank" href="https://github.com/jmoiron/sqlx">Sqlx</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ory/dockertest">dockertest</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/stretchr/testify">testify</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/DATA-DOG/go-sqlmock">go-sqlmock</a></p>
</li>
</ul>
<blockquote>
<p>Interestingly, it’s hard to reproduce the connection leak scenario in a transaction layer. Trust me, I’ve tried and failed. 😂</p>
</blockquote>
<p>I’ll show how to test the old code using <code>DBStats</code> assertions. With this example, people who will not abstract the transaction layer could update their tests to avoid any connection leaks. Later on, we’ll explore how to extract the transaction layer and test it.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> apptest

<span class="hljs-keyword">import</span> (
 <span class="hljs-string">"context"</span>
 <span class="hljs-string">"database/sql"</span>
 <span class="hljs-string">"github.com/jmoiron/sqlx"</span>
)

<span class="hljs-keyword">type</span> Subscription <span class="hljs-keyword">struct</span> {
 ID         <span class="hljs-keyword">int64</span>        <span class="hljs-string">`db:"id"`</span>
 Status     <span class="hljs-keyword">string</span>       <span class="hljs-string">`db:"status"`</span>
 CanceledAt sql.NullTime <span class="hljs-string">`db:"canceled_at"`</span>
}

<span class="hljs-comment">// ------------------------------ Repository ------------------------------</span>

<span class="hljs-keyword">type</span> srepo <span class="hljs-keyword">struct</span> {
}

<span class="hljs-comment">// GetSubscription fetches the subscription by id</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *srepo)</span> <span class="hljs-title">GetSubscription</span><span class="hljs-params">(tx *sqlx.Tx, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(Subscription, error)</span></span> {
 <span class="hljs-keyword">var</span> sub Subscription
 err := tx.Get(&amp;sub, <span class="hljs-string">"SELECT * FROM subscription WHERE id = $1"</span>, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> sub, err
 }

 <span class="hljs-keyword">return</span> sub, <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// CancelSubscription cancels a given subscription by setting canceled_at to now()</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *srepo)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(tx *sqlx.Tx, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(Subscription, error)</span></span> {
 <span class="hljs-keyword">var</span> sub Subscription
 err := tx.Get(&amp;sub, <span class="hljs-string">"UPDATE subscription SET canceled_at = NOW(), status='canceled' WHERE id = $1 RETURNING *"</span>, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> sub, err
 }

 <span class="hljs-keyword">return</span> sub, <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// ------------------------------ Service ------------------------------</span>

<span class="hljs-keyword">type</span> Service <span class="hljs-keyword">struct</span> {
 db   *sqlx.DB
 repo *srepo
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewService</span><span class="hljs-params">(db *sqlx.DB, repo *srepo)</span> *<span class="hljs-title">Service</span></span> {
 <span class="hljs-keyword">return</span> &amp;Service{repo: repo, db: db}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *Service)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(*Subscription, error)</span></span> {
 tx, err := s.db.BeginTxx(ctx, <span class="hljs-literal">nil</span>)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
 }

 <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
  <span class="hljs-comment">// !!! This would not work if the subscriptions is already canceled </span>
  <span class="hljs-comment">// and the error is not returned</span>
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
   _ = tx.Rollback()
   <span class="hljs-keyword">return</span>
  }
 }()

 sub, err := s.repo.GetSubscription(tx, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
 }

 <span class="hljs-keyword">if</span> sub.Status != <span class="hljs-string">"active"</span> {
  <span class="hljs-keyword">return</span> &amp;sub, <span class="hljs-literal">nil</span>
 }

 <span class="hljs-keyword">if</span> sub.CanceledAt.Valid {
  <span class="hljs-keyword">return</span> &amp;sub, <span class="hljs-literal">nil</span>
 }

 sub, err = s.repo.CancelSubscription(tx, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
 }

 err = tx.Commit()

 <span class="hljs-keyword">return</span> &amp;sub, err
}
</code></pre>
<h2 id="heading-whats-wrong-with-the-above-code">What's wrong with the above code?</h2>
<p>When the subscription is already cancelled it will return without error. When the function returns without error the connection neither rollback nor commits. This causes the connection leak.</p>
<p>Below is the integration test to see how we can catch the leak.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_ConnectionLeak</span><span class="hljs-params">(t *testing.T)</span></span> {
 pg, err := apptest.StartTestPostgres(t) <span class="hljs-comment">// Please use the source code to learn more about this code</span>
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"CREATE TABLE IF NOT EXISTS subscription (id serial PRIMARY KEY, status varchar(25) NOT NULL, canceled_at timestamp NULL)"</span>)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"INSERT INTO subscription (status, canceled_at) VALUES ('active', NULL)"</span>)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"INSERT INTO subscription (status, canceled_at) VALUES ('canceled', '2023-02-02 01:00:00')"</span>)
 require.NoError(t, err)

 subscription, err := NewService(pg.DB, &amp;srepo{}).CancelSubscription(context.Background(), <span class="hljs-number">2</span>)
 require.NoError(t, err)

 stats := pg.DB.Stats()
 require.Equal(t, <span class="hljs-number">0</span>, stats.InUse, <span class="hljs-string">"expected no connections in use"</span>)
 require.Equal(t, <span class="hljs-number">0</span>, stats.MaxOpenConnections, <span class="hljs-string">"expected no max open connection"</span>)

 require.Equal(t, <span class="hljs-string">"canceled"</span>, subscription.Status)
 require.Equal(t, <span class="hljs-string">"2023-02-02 01:00:00 +0000 +0000"</span>, subscription.CanceledAt.Time.String())
}
</code></pre>
<p>I connect to a Postgres DB instance in docker and run the test. From the test result below you could see that the code has a transaction problem. The actual count <code>InUse</code> is <code>1</code>. The connection is not closed at the end of the function call. This is what we will solve using the transaction abstraction layer in the next section.</p>
<pre><code class="lang-bash">=== RUN   Test_ConnectionLeak
    service-connection-leak_test.go:27: 
         Error Trace: /pkg/service-connection-leak_test.go:27
         Error:       Not equal: 
                      expected: 0
                      actual  : 1
         Test:        Test_ConnectionLeak
         Messages:    expected no connections <span class="hljs-keyword">in</span> use
--- FAIL: Test_ConnectionLeak (6.69s)

Expected :0
Actual   :1
&lt;Click to see difference&gt;

FAIL
</code></pre>
<h2 id="heading-extracting-the-transaction-layer">Extracting the Transaction Layer</h2>
<p>To address the connection leak issue, one way could be to fix what is failing. Close the connection by manually testing the service and then reviewing the code. But that same issue could creep in again in future. The right way is to extract a transaction layer. The idea behind this extraction is simple: we provide a public method</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InTx</span><span class="hljs-params">(ctx context.Context, db *sqlx.DB, txFunc <span class="hljs-keyword">func</span>(*TxWrap)</span> <span class="hljs-title">error</span>) <span class="hljs-params">(err error)</span></span>
</code></pre>
<p>which accepts the transaction-based business logic in the <code>txFunc</code> parameter.</p>
<p>With this approach, the developer no longer has to manually handle transactions, as the <code>InTx</code> method abstracts the transaction mechanism away from the business logic. By passing the <code>txFunc</code> parameter to <code>InTx</code>, the developer can focus on the actual business operations, without worrying about the underlying transaction management.</p>
<blockquote>
<p>I am keeping the code simple and avoiding any complications for blogging purposes. </p>
</blockquote>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> db

<span class="hljs-keyword">import</span> (
 <span class="hljs-string">"context"</span>
 <span class="hljs-string">"database/sql"</span>
 <span class="hljs-string">"github.com/jmoiron/sqlx"</span>
)

<span class="hljs-comment">// TxWrap is a wrapper around sqlx.Tx that adds a context</span>
<span class="hljs-comment">// and redirects calls to methods like Get, Select to GetContext and SelectContext</span>
<span class="hljs-comment">// with the context it wraps.</span>
<span class="hljs-keyword">type</span> TxWrap <span class="hljs-keyword">struct</span> {
 tx  *sqlx.Tx        <span class="hljs-comment">// underlying transaction</span>
 ctx context.Context <span class="hljs-comment">// context to use for all calls</span>
}

<span class="hljs-comment">// Get is a wrapper around sqlx.Tx.GetContext</span>
<span class="hljs-comment">// that uses the context it wraps.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">Get</span><span class="hljs-params">(dest <span class="hljs-keyword">interface</span>{}, query <span class="hljs-keyword">string</span>, args ...<span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">error</span></span> {
 <span class="hljs-keyword">return</span> tx.tx.GetContext(tx.ctx, dest, query, args...)
}

<span class="hljs-comment">// Select is a wrapper around sqlx.Tx.SelectContext</span>
<span class="hljs-comment">// that uses the context it wraps.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">Select</span><span class="hljs-params">(dest <span class="hljs-keyword">interface</span>{}, query <span class="hljs-keyword">string</span>, args ...<span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">error</span></span> {
 <span class="hljs-keyword">return</span> tx.tx.SelectContext(tx.ctx, dest, query, args...)
}

<span class="hljs-comment">// IMPLEMENT OTHER RELATED sqlx Methods to use wrapped context</span>

<span class="hljs-comment">// InTx executes a function in a transaction.</span>
<span class="hljs-comment">// If the function returns an error, the transaction is rolled back.</span>
<span class="hljs-comment">// If the function panics, the transaction is rolled back and the panic is re-raised.</span>
<span class="hljs-comment">// If the function returns nil, the transaction is committed.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InTx</span><span class="hljs-params">(ctx context.Context, db *sqlx.DB, txFunc <span class="hljs-keyword">func</span>(*TxWrap)</span> <span class="hljs-title">error</span>) <span class="hljs-title">error</span></span> {
 <span class="hljs-keyword">var</span> err error
 ctx, cancel := context.WithCancel(ctx)
 <span class="hljs-keyword">defer</span> cancel()

 tx, err := db.BeginTxx(ctx, <span class="hljs-literal">nil</span>)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> err
 }

 txWrap := &amp;TxWrap{
  tx:  tx,
  ctx: ctx,
 }

 <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">if</span> p := <span class="hljs-built_in">recover</span>(); p != <span class="hljs-literal">nil</span> {
   _ = txWrap.tx.Rollback()
   <span class="hljs-built_in">panic</span>(p)
  }
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
   _ = txWrap.tx.Rollback()
   <span class="hljs-keyword">return</span>
  }
  err = txWrap.tx.Commit()
 }()

 <span class="hljs-keyword">return</span> txFunc(txWrap)
}
</code></pre>
<p>The <code>InTx</code> wraps around the actual <code>sqlx.Tx</code> in <code>TxWrap</code> struct and encapsulates the transaction management logic within the method. The <code>InTx</code> takes care of calling <code>Begin, Commit and Rollback</code>. </p>
<p>The <code>TxWrap</code> struct also contains a derived cancel context (<code>ctx</code>), which was discussed in my previous post to ensure the context cancellation happens at the end of the method call. Inside the <code>InTx</code> method, we have a <code>defer</code> block that handles three possible outcomes of the transaction logic:</p>
<ol>
<li><p>Panic: In case of any unhandled exception, the transaction will automatically roll back.</p>
</li>
<li><p>Error: If any error occurs during the transaction, it will also be rolled back.</p>
</li>
<li><p>Success: If everything goes as expected, the transaction will be committed.</p>
</li>
</ol>
<p>To provide further safety, we also implement the same <code>Get</code> and <code>Select</code> functions as in the <code>sqlx</code> library, but we proxy the calls to the context version of these functions. This ensures that any in-process requests are cancelled if the context is cancelled if a client cancels an HTTP request.</p>
<p>Now the real excitement begins !!! TESTS !!! 🚀 🥹</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExM2Y0MWYwODM3MTA4YzE5NzQ2MDNkM2UxZjI4NzExMmJiMmEyOTQ1NyZjdD1n/MNmyTin5qt5LSXirxd/giphy.gif" alt class="image--center mx-auto" /></p>
<p>I am doing both approaches, the Unit test and the Integration Test. It is up to you what you want to use. My preference is the Integration test. They mimic behaviour close to real infrastructure.</p>
<h3 id="heading-unit-test">Unit Test</h3>
<p>For the unit test, I am using<code>sqlmock</code>. I set up the expectation as per the 3 behaviours of our code and assert if the expectations are met. I also check the connection count resets <code>0</code> at the end. Unit Tests are pretty fast so we are also doing <code>t.Parallel</code> and initiating a new sqlmock for every test.</p>
<pre><code class="lang-go"><span class="hljs-comment">// ------------------------------ UNIT Test ------------------------------</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_Unit</span><span class="hljs-params">(t *testing.T)</span></span> {
 t.Parallel()

 tests := []<span class="hljs-keyword">struct</span> {
  name      <span class="hljs-keyword">string</span>
  fn        <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span>
  setup     <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(mock sqlmock.Sqlmock)</span></span>
  wantErr   <span class="hljs-keyword">bool</span>
  wantPanic <span class="hljs-keyword">bool</span>
 }{
  {
   name: <span class="hljs-string">"success path"</span>,
   fn: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
   },
   setup: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(mock sqlmock.Sqlmock)</span></span> {
    mock.ExpectBegin()
    mock.ExpectCommit()
   },
  },
  {
   name: <span class="hljs-string">"failure path"</span>,
   fn: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"some error"</span>)
   },
   setup: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(mock sqlmock.Sqlmock)</span></span> {
    mock.ExpectBegin()
    mock.ExpectRollback()
   },
   wantErr: <span class="hljs-literal">true</span>,
  },
  {
   name: <span class="hljs-string">"panic"</span>,
   fn: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-built_in">panic</span>(<span class="hljs-string">"some panic"</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
   },
   setup: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(mock sqlmock.Sqlmock)</span></span> {
    mock.ExpectBegin()
    mock.ExpectRollback()
   },
   wantPanic: <span class="hljs-literal">true</span>,
  },
 }
 <span class="hljs-keyword">for</span> _, test := <span class="hljs-keyword">range</span> tests {
  test := test
  t.Run(test.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
   t.Parallel()

   db, mock, err := sqlmock.New()
   require.NoError(t, err)

   dbx := sqlx.NewDb(db, <span class="hljs-string">"sqlmock"</span>)

   <span class="hljs-keyword">if</span> test.setup != <span class="hljs-literal">nil</span> {
      test.setup(mock)
   }

   <span class="hljs-comment">// Only add this defer when we expect panic to take over the </span>
  <span class="hljs-comment">// panic recovery and see if there is a valid error</span>
   <span class="hljs-keyword">if</span> test.wantPanic {
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
     require.NotNil(t, <span class="hljs-built_in">recover</span>())
    }()
   }

   err = InTx(context.Background(), dbx, test.fn)

   require.Equal(t, test.wantErr, err != <span class="hljs-literal">nil</span>)
   require.NoError(t, mock.ExpectationsWereMet())

   stats := dbx.Stats()
   require.Equal(t, <span class="hljs-number">0</span>, stats.InUse)
   require.Equal(t, <span class="hljs-number">0</span>, stats.MaxOpenConnections)
  })
 }
}
</code></pre>
<h3 id="heading-integration-test">Integration Test</h3>
<p>For the Integration test below, I am using Postgres DB. I create a dummy <code>Employees</code> table first. Then I execute some inserts and selects statements for different scenario. In the end, check if the layer closes the connection for every test.</p>
<p>A point to note here is that I am not doing <code>t.Parallel</code>. The shared connection between tests in the parallel run would be a problem. The <code>InUse</code> and <code>MaxOpenConnections</code> will never be <code>0</code>. It is up to you how you want to do it. You can do a similar thing we did in the Unit test to create <strong>a separate connection</strong> for every test :</p>
<pre><code class="lang-go"><span class="hljs-comment">// ---------------------------------- INTEGRATION TEST -------------------------------------</span>
<span class="hljs-keyword">type</span> Employee <span class="hljs-keyword">struct</span> {
 ID   <span class="hljs-keyword">int64</span>  <span class="hljs-string">`db:"id"`</span>
 Name <span class="hljs-keyword">string</span> <span class="hljs-string">`db:"name"`</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_Integration</span><span class="hljs-params">(t *testing.T)</span></span> {
 pg, err := apptest.StartTestPostgres(t)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"CREATE TABLE IF NOT EXISTS employee (id serial PRIMARY KEY, name varchar(25) NOT NULL)"</span>)
 require.NoError(t, err)

 tests := []<span class="hljs-keyword">struct</span> {
  name      <span class="hljs-keyword">string</span>
  txfunc    <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span>
  wantErr   <span class="hljs-keyword">bool</span>
  wantPanic <span class="hljs-keyword">bool</span>
 }{
  {
   name: <span class="hljs-string">"success path"</span>,
   txfunc: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    _, err := tx.Exec(<span class="hljs-string">"INSERT INTO employee (name) VALUES ('John Doe')"</span>)
    <span class="hljs-keyword">return</span> err
   },
  },
  {
   name: <span class="hljs-string">"failure path"</span>,
   txfunc: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> employee Employee
    err := tx.Get(&amp;employee, <span class="hljs-string">"SELECT * FROM employee WHERE id = $1"</span>, <span class="hljs-number">100</span>)
    <span class="hljs-keyword">return</span> err
   },
   wantErr: <span class="hljs-literal">true</span>,
  },
  {
   name: <span class="hljs-string">"panic"</span>,
   txfunc: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *TxWrap)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-built_in">panic</span>(<span class="hljs-string">"some panic"</span>)

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
   },
   wantPanic: <span class="hljs-literal">true</span>,
  },
 }
 <span class="hljs-keyword">for</span> _, test := <span class="hljs-keyword">range</span> tests {
  test := test
  t.Run(test.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {

   <span class="hljs-comment">// Wrap the function in a defer to catch panics</span>
   <span class="hljs-comment">// and assert that the panic is not nil.</span>
   <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> test.wantPanic {
     require.NotNil(t, <span class="hljs-built_in">recover</span>())
    }
    stats := pg.DB.Stats()
    require.Equal(t, <span class="hljs-number">0</span>, stats.InUse)
    require.Equal(t, <span class="hljs-number">0</span>, stats.MaxOpenConnections)
   }()

   err = InTx(context.Background(), pg.DB, test.txfunc)

   require.Equal(t, test.wantErr, err != <span class="hljs-literal">nil</span>)
  })
 }
}
</code></pre>
<h2 id="heading-updating-the-service">Updating the Service</h2>
<p>We have the new transaction layer ready. Let’s change our old code to use this new layer.</p>
<pre><code class="lang-go"><span class="hljs-comment">// ------------------------------ Repository ------------------------------</span>

<span class="hljs-keyword">type</span> txRepo <span class="hljs-keyword">struct</span> {
}

<span class="hljs-comment">// GetSubscription is a repository method that does not leak connections</span>
<span class="hljs-comment">// it uses *TxWrap to wrap the transaction</span>
<span class="hljs-comment">// it uses the context to cancel the transaction if the context is canceled</span>
<span class="hljs-comment">// but the context is inside the *TxWrap and not exposed to the service</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *txRepo)</span> <span class="hljs-title">GetSubscription</span><span class="hljs-params">(tx *db.TxWrap, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(Subscription, error)</span></span> {
 <span class="hljs-keyword">var</span> sub Subscription
 err := tx.Get(&amp;sub, <span class="hljs-string">"SELECT * FROM subscription WHERE id = $1"</span>, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> sub, err
 }

 <span class="hljs-keyword">return</span> sub, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *txRepo)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(tx *db.TxWrap, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(Subscription, error)</span></span> {
 <span class="hljs-keyword">var</span> sub Subscription
 err := tx.Get(&amp;sub, <span class="hljs-string">"UPDATE subscription SET canceled_at = NOW(), status='canceled' WHERE id = $1 RETURNING *"</span>, id)
 <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
  <span class="hljs-keyword">return</span> sub, err
 }

 <span class="hljs-keyword">return</span> sub, <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// ------------------------------ Service ------------------------------</span>

<span class="hljs-keyword">type</span> txService <span class="hljs-keyword">struct</span> {
 db   *sqlx.DB
 repo *txRepo
}

<span class="hljs-comment">// CancelSubscriptionWithoutLeak is a service method that does not leak connections</span>
<span class="hljs-comment">// it uses InTx helper to wrap the transaction</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *txService)</span> <span class="hljs-title">CancelSubscriptionWithoutLeak</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(*Subscription, error)</span></span> {
 <span class="hljs-keyword">var</span> sub Subscription
 <span class="hljs-keyword">var</span> err error

<span class="hljs-comment">// So cool!!!!!!!! 🎸</span>
 err = db.InTx(ctx, s.db, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(tx *db.TxWrap)</span> <span class="hljs-title">error</span></span> {
  sub, err = s.repo.GetSubscription(tx, id)
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
   <span class="hljs-keyword">return</span> err
  }

  <span class="hljs-keyword">if</span> sub.Status != <span class="hljs-string">"active"</span> {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
  }

  <span class="hljs-keyword">if</span> sub.CanceledAt.Valid {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
  }

  sub, err = s.repo.CancelSubscription(tx, id)
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
   <span class="hljs-keyword">return</span> err
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
 })

 <span class="hljs-keyword">return</span> &amp;sub, err
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Test_NoConnectionLeak</span><span class="hljs-params">(t *testing.T)</span></span> {
 pg, err := apptest.StartTestPostgres(t)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"CREATE TABLE IF NOT EXISTS subscription (id serial PRIMARY KEY, status varchar(25) NOT NULL, canceled_at timestamp NULL)"</span>)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"INSERT INTO subscription (status, canceled_at) VALUES ('active', NULL)"</span>)
 require.NoError(t, err)

 _, err = pg.DB.Exec(<span class="hljs-string">"INSERT INTO subscription (status, canceled_at) VALUES ('canceled', '2023-02-02 01:00:00')"</span>)
 require.NoError(t, err)

 subscription, err := NewTxService(pg.DB, &amp;txRepo{}).CancelSubscriptionWithoutLeak(context.Background(), <span class="hljs-number">2</span>)
 require.NoError(t, err)

 stats := pg.DB.Stats()
 require.Equal(t, <span class="hljs-number">0</span>, stats.InUse, <span class="hljs-string">"expected no connections in use"</span>)
 require.Equal(t, <span class="hljs-number">0</span>, stats.MaxOpenConnections, <span class="hljs-string">"expected no max open connection"</span>)

 require.Equal(t, <span class="hljs-string">"canceled"</span>, subscription.Status)
 require.Equal(t, <span class="hljs-string">"2023-02-02 01:00:00 +0000 +0000"</span>, subscription.CanceledAt.Time.String())
}

-------------

=== RUN   Test_NoConnectionLeak
--- PASS: Test_NoConnectionLeak (<span class="hljs-number">5.61</span>s)
PASS
</code></pre>
<p>The test above is the exact test we used in our connection leak example. I have to tweak it for the new service and new imports. Everything else in the test is the same.</p>
<p>You could see when we changed the service to use the new transaction layer our same test is green. 🥳🥳🥳</p>
<h2 id="heading-do-we-need-a-transaction-layer">Do We Need A Transaction Layer?</h2>
<p>It was tough for me to create a connection leak with the transaction layer in the old code. It is close to impossible that anything could go wrong.</p>
<p>Although, I felt that testing would be a challenge. The <code>DBStruct</code> provided an easy way for testing.</p>
<p>With different business operations within a transaction, a single line could cause problems and issues in production. But a separate battle-tested layer would safeguard against any issues. Plus, the same layer could be shared with different flows to avoid code repetition.</p>
<p>So, if you haven’t yet extracted that transaction logic please do! You could write your library or use something off the shelf. I am also going to publish the lib on my GitHub which I would share with you 🚀</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjY0NmRjYzZiZThiNGVhNzBmYTdiMDdiNGJlMDQ4ZTZjMjY3YjEzMCZjdD1n/yBwgX64KAPrHW2ltZ2/giphy.gif" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>With this proof, I am very confident that I will not cause another transaction-related production issue. And I hope this blog post helps you to learn something new about Golang. </p>
<p>Some of my initial assumptions were proven wrong. The tests could allow me to answer that the layer works and there will be no connection leak.</p>
<hr />
<p>I want to thank all my readers 🙏. I feel a lot motivated by your replies and suggestions.</p>
<p>It feels good when people are reading the blog and in some way, I am helping the community back. If you have any suggestions or want to reach out to me please use the <a target="_blank" href="https://github.com/ninadingole/ama">AMA</a></p>
<p>I am exploring more production-level stuff in Golang, and things that I am working on. If you like to be part of this journey please follow me on <a target="_blank" href="https://medium.com/@iamninad">Medium</a> or <a target="_blank" href="https://blog.iamninad.com/newsletter">Hashnode</a> and subscribe to my posts. You could also reach out to me on other platforms:</p>
<ul>
<li><p><a target="_blank" href="https://twitter.com/iamneenad">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ninadingole/ama">Github</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/ninadingole/?lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3B8f5UxCdCR76i5C5SS%2FWQGg%3D%3D">LinkedIn</a></p>
</li>
</ul>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWFlYzUzODQzZGY2MTQ5ZTUyZWIzNTViYTAzODRjYmY2OTQ2YTUxZSZjdD1n/ZfK4cXKJTTay1Ava29/giphy.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[A Billion Dollar Go Mistake]]></title><description><![CDATA[This post is about a real problem I faced in my project. I merged two blog post into one so this is a bit longer post, please read till the end. Thank you!

I hope after reading this post you will be able to avoid the same mistake we did in our proje...]]></description><link>https://hashnode.iamninad.com/a-billion-dollar-go-mistake</link><guid isPermaLink="true">https://hashnode.iamninad.com/a-billion-dollar-go-mistake</guid><category><![CDATA[golang]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Fri, 10 Mar 2023 13:09:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/heNwUmEtZzo/upload/46a09240a9a246ddc93fd5a170093ad4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><em>This post is about a real problem I faced in my project. I merged two blog post into one so this is a bit longer post, please read till the end. Thank you!</em></p>
</blockquote>
<p>I hope after reading this post you will be able to avoid the same mistake we did in our project 😅. This small mistake didn’t cost us a billion dollars. But, It did cost us a few thousand, and the Prometheus alerts saved us.</p>
<p>But, it could cost you a Billion if you are not careful 💸. I learn my lessons through mistakes, this is something that I will never forget in future.</p>
<p>The scenario is a simple database transaction. We all have used database transactions at least once in our programming life. If you don’t know how transactions work you could read the docs <a target="_blank" href="https://www.postgresql.org/docs/current/tutorial-transactions.html">here</a> for Postgres.</p>
<p>Our service handles the management of some Subscriptions in the database. All the changes to the rows happen within a transaction. The service is in Golang and the flow is like this::</p>
<ul>
<li><p>Start the transaction</p>
</li>
<li><p>Fetch the record by ID</p>
</li>
<li><p>Confirm if the operation for the change is possible</p>
</li>
<li><p>If Yes, then update the record</p>
</li>
<li><p>Commit the transaction</p>
</li>
<li><p>For any error, revert the transaction</p>
</li>
</ul>
<p>To fetch a record and acquire a lock on the record so that other flows would wait to update, we use <code>SELECT ... FOR UPDATE</code> query to fetch the record from Postgres and lock it.</p>
<blockquote>
<p><strong><em>Note:</em></strong> <em>We use</em> <a target="_blank" href="https://github.com/jmoiron/sqlx"><em>sqlx</em></a> <em>lib for database access.</em></p>
</blockquote>
<p>The repository code is like this:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *fetcher)</span> <span class="hljs-title">GetSubscription</span><span class="hljs-params">(tx *sqlx.Tx, id uuid.UUID)</span> <span class="hljs-params">(*model.Subscription, error)</span></span> {
    <span class="hljs-keyword">var</span> subscription model.Subscription
    err := tx.Get(&amp;subscription, <span class="hljs-string">`
        SELECT * FROM subscriptions
        WHERE id = $1
        FOR UPDATE
    `</span>, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">return</span> &amp;subscription, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>The service code is like this:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *service)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(ctx context.Context, id uuid.UUID)</span> <span class="hljs-params">(*model.Subscription, error)</span></span> {
    tx, err := s.db.BeginTxx(ctx, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            tx.Rollback()
        }
    }()

    subscription, err := s.fetcher.GetSubscription(tx, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">if</span> subscription.CancelledAt != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
    }

    subscription.CancelledAt = time.Now()

    err = s.updater.UpdateSubscription(tx, subscription)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    err = tx.Commit()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-problem"><strong>Problem</strong></h1>
<p>All client requests were timing out, and the DB connection spiked over 1.2k connections 😅</p>
<p>Not a single request was able to complete the operation. It was a stop-the-world event 🤣</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://giphy.com/gifs/facepalm-XD4qHZpkyUFfq?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F">https://giphy.com/gifs/facepalm-XD4qHZpkyUFfq?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F</a></div>
<p> </p>
<p><strong>Why?</strong></p>
<p>The issue happens when we try to cancel a subscription that is already cancelled. If the subscription is already cancelled we return the subscription without doing any changes, but we are not releasing the lock on the record.</p>
<p>The reason is <code>defer</code> function calls <code>tx.Rollback()</code> only when there is an error. This would cause the lock to be active until the transaction commits or roll back. But since we are not doing any of the two things, the lock is held until the transaction times out.</p>
<p>If the service is having high traffic, the transactions could not timeout. This will cause the code to create new connections to the database and exhaust the connection pool.</p>
<h1 id="heading-fix"><strong>Fix</strong></h1>
<ol>
<li>Release the lock in every <code>if</code> condition.</li>
</ol>
<pre><code class="lang-go"><span class="hljs-comment">// Error handling is omitted for brevity</span>
<span class="hljs-keyword">if</span> subscription.CancelledAt != <span class="hljs-literal">nil</span> {
    _ = tx.Rollback() <span class="hljs-comment">// release the lock</span>

    <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>This is the simplest way to fix the issue. But this would need you to roll back in every <code>if</code> condition. And if you forget to roll back in any of the <code>if</code> conditions, you will have the same issue.</p>
<p>2. Rollback the transaction in the <code>defer</code> function for every case.</p>
<pre><code class="lang-go"><span class="hljs-comment">// Error handling is omitted for brevity</span>
<span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
   _ = tx.Rollback()
}()
</code></pre>
<p>This would release the lock in every case. For committed transactions and as per the tracing, we see some milliseconds the code spends to roll back the transactions. In the case of committed transactions, the rollback does nothing. But this is a better way to fix the issue.</p>
<p>3. Commit the transaction in the <code>defer</code> function for every case.</p>
<pre><code class="lang-go"><span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    _ = tx.Rollback()
  }

  commitErr := tx.Commit()
  <span class="hljs-comment">// handle commitErr</span>
}
</code></pre>
<p>If there are no changes the transaction will commit without any change. If there is any error only then the rollback would happen and will release the lock.</p>
<p>But, this affects the readability of the code. Your commit is happening in the <code>defer</code> function, and that's an extra pointer you have to keep in mind while reading the code.</p>
<h1 id="heading-service-layer-return-error"><strong>Service Layer — Return Error 👾</strong></h1>
<p>The problem with the service is that for validations, it is not returning any error. The other way to fix this issue is to return an error from the <code>if</code> condition. This would make the <code>Rollback</code> happen in <code>defer</code> function.</p>
<p>This is a very clean way. A service should have only one responsibility. Either it completes the operation or it returns an error. It should return an error in case of validation failure. This is a good practice and something we missed in our project. We fixed the issue later by returning an error from the <code>if</code> condition.</p>
<p>This change also helps the handler to decide what HTTP status code to return. Which is really helpful as we can return <code>400 Bad Request</code> for validation errors.</p>
<p>This is how the code would look after refactoring</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> ErrSubscriptionAlreadyCancelled = errors.New(<span class="hljs-string">"subscription already cancelled"</span>)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *service)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(ctx context.Context, id uuid.UUID)</span> <span class="hljs-params">(*model.Subscription, error)</span></span> {
    tx, err := s.db.BeginTxx(ctx, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            tx.Rollback()
        }
    }()

    subscription, err := s.fetcher.GetSubscription(tx, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">if</span> subscription.CancelledAt != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> subscription, ErrSubscriptionAlreadyCancelled
    }

    subscription.CancelledAt = time.Now()
    err = s.updater.UpdateSubscription(tx, subscription)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    err = tx.Commit()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-my-take"><strong>My Take</strong></h1>
<p>My personal preference is the second option. But you could choose any of the three options based on your preference. I am choosing the second option because I am sure that whatever happens in the flow at the end, the transaction revert will happen. Yes, it would cost me a few milliseconds in case of a committed transaction, but compared to the loss to the business, it’s worth it.</p>
<p>Keep in mind that the service layer has only one responsibility. Either it completes the operation or it returns an error.</p>
<p>You may be thinking that a better approach is to have an abstraction that takes care of transactions. I agree, and would also have a separate post on that. But, I also think that we can avoid the complexity of abstraction if the code is stable and not changes too often.</p>
<p>The golang official post for transactions also supports the reasoning. Check the code snippet <a target="_blank" href="https://go.dev/doc/database/execute-transactions#example">here</a>. The office post also uses the second option.</p>
<pre><code class="lang-go"><span class="hljs-comment">// code snippet from the golang official post</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateOrder</span><span class="hljs-params">(ctx context.Context, albumID, quantity, custID <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(orderID <span class="hljs-keyword">int64</span>, err error)</span></span> {
<span class="hljs-comment">// Create a helper function for preparing failure results.</span>
  fail := <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(err error)</span> <span class="hljs-params">(<span class="hljs-keyword">int64</span>, error)</span></span> {
    <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"CreateOrder: %v"</span>, err)
  }
  <span class="hljs-comment">// Get a Tx for making transaction requests.</span>
  tx, err := db.BeginTx(ctx, <span class="hljs-literal">nil</span>)
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-keyword">return</span> fail(err)
  }
  <span class="hljs-comment">// Defer a rollback in case anything fails.</span>
  <span class="hljs-keyword">defer</span> tx.Rollback()

  ... <span class="hljs-comment">// other code is omitted for brevity</span>

  <span class="hljs-comment">// Commit the transaction.</span>
  <span class="hljs-keyword">if</span> err = tx.Commit(); err != <span class="hljs-literal">nil</span> {
    <span class="hljs-keyword">return</span> fail(err)
  }

  <span class="hljs-comment">// Return the order ID.</span>
  <span class="hljs-keyword">return</span> orderID, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>There are two more ways to fix the problem. This will also provide one extra layer of safety. In my opinion, having multiple safety gates is always better.</p>
<p>If one fails there is another one to safeguard with any issues. The business-critical services with an extra layer will protect against any monetary loss.</p>
<p><strong>Excited to know more?</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://giphy.com/gifs/full-house-12SXVd8bmXdSg0?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F">https://giphy.com/gifs/full-house-12SXVd8bmXdSg0?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F</a></div>
<p> </p>
<p>I do this kind of dance when I find multiple ways to solve any problem :D</p>
<h1 id="heading-using-a-context"><strong>Using a context</strong></h1>
<p>We could use a cancel context to release the lock at the end of the transaction. Context is very helpful in many use cases.</p>
<p>Let’s see how to do things with context</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *service)</span> <span class="hljs-title">CancelSubscription</span><span class="hljs-params">(ctx context.Context, id uuid.UUID)</span> <span class="hljs-params">(*model.Subscription, err error)</span></span> {
    ctx, cancel := context.WithCancel(ctx)
    <span class="hljs-keyword">defer</span> cancel()

    tx, err := s.db.BeginTxx(ctx, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            tx.Rollback()
        }
    }()

    subscription, err := s.fetcher.GetSubscription(ctx, tx, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">if</span> subscription.CancelledAt != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
    }

    subscription.CancelledAt = time.Now()

    err = s.updater.UpdateSubscription(ctx, tx, subscription)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    err = tx.Commit()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    <span class="hljs-keyword">return</span> subscription, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>In the above code, we create a new cancel context from the parent context. If the DB transaction is not released either using <code>Rollback</code> or <code>Commit</code>. A defer <code>cancel()</code> happens at the end when the call returns.</p>
<p>The cancel call will notify the transaction that the operation is complete. The go runtime will close the transaction.</p>
<p>The <code>defer cancel()</code> call is important. If you miss that then the problem will still persist</p>
<p>At the go source code level transaction begin call starts a background go routine. The go routine monitors context Done signal. Let's see how it happens using context as shown <a target="_blank" href="https://github.com/golang/go/blob/master/src/database/sql/sql.go#L1887">here</a></p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(db *DB)</span> <span class="hljs-title">beginDC</span><span class="hljs-params">(ctx context.Context, dc *driverConn, release <span class="hljs-keyword">func</span>(error)</span>, <span class="hljs-title">opts</span> *<span class="hljs-title">TxOptions</span>) <span class="hljs-params">(tx *Tx, err error)</span></span> {
     <span class="hljs-keyword">var</span> txi driver.Tx
     keepConnOnRollback := <span class="hljs-literal">false</span>
     withLock(dc, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
      _, hasSessionResetter := dc.ci.(driver.SessionResetter)
      _, hasConnectionValidator := dc.ci.(driver.Validator)
      keepConnOnRollback = hasSessionResetter &amp;&amp; hasConnectionValidator
      txi, err = ctxDriverBegin(ctx, opts, dc.ci)
     })
     <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
      release(err)
      <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
     }

     ctx, cancel := context.WithCancel(ctx)
     tx = &amp;Tx{
      db:                 db,
      dc:                 dc,
      releaseConn:        release,
      txi:                txi,
      cancel:             cancel,
      keepConnOnRollback: keepConnOnRollback,
      ctx:                ctx,
     }
     <span class="hljs-keyword">go</span> tx.awaitDone() <span class="hljs-comment">// GO WAITS FOR THE CONTEXT TO DONE</span>
     <span class="hljs-keyword">return</span> tx, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>And inside the <code>tx.awaitDone</code></p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(tx *Tx)</span> <span class="hljs-title">awaitDone</span><span class="hljs-params">()</span></span> {
 <span class="hljs-comment">// Wait for either the transaction to be committed or rolled</span>
 <span class="hljs-comment">// back, or for the associated context to be closed.</span>
 &lt;-tx.ctx.Done()

 <span class="hljs-comment">// Discard and close the connection used to ensure the</span>
 <span class="hljs-comment">// transaction is closed and the resources are released.  This</span>
 <span class="hljs-comment">// rollback does nothing if the transaction has already been</span>
 <span class="hljs-comment">// committed or rolled back.</span>
 <span class="hljs-comment">// Do not discard the connection if the connection knows</span>
 <span class="hljs-comment">// how to reset the session.</span>
 discardConnection := !tx.keepConnOnRollback
 tx.rollback(discardConnection)  <span class="hljs-comment">// AT THE END THE TX WILL BE ROLLBACKED</span>
}
</code></pre>
<p>So transactions with a cancel context make more sense. Do not use the context coming from the HTTP request. HTTP context works like a client-side timeout. The service layer has no control over cancelling the HTTP context.</p>
<p>A timeout context could also work but that will not be that reliable. Finding the right timeout value is a difficult task in itself.</p>
<h1 id="heading-server-side-timeout"><strong>Server-side timeout</strong></h1>
<p>You set an optimal timeout for the request to complete and if the request is not completed within that time, you cancel the operation and return an error.</p>
<pre><code class="lang-go">server := &amp;http.Server{
    Addr:         <span class="hljs-string">":8080"</span>,
    Handler:      router,
    WriteTimeout: <span class="hljs-number">2</span> * time.Second,
}
</code></pre>
<p>In the above code, we are setting a server-side timeout of 2 seconds. If the entire request processing is not completed within 2 seconds, we cancel the operation and return an error to the client. With this approach, you don’t need to create a timeout context for each request. But, you would still need to fine-tune the timeout value based on the traces and load testing.</p>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>I hope you found this post useful. For customer-facing services, having a tiny bug could cause a big monetary impact. Securing code with enough safety nets is something we should do. And, I hope through this post I made a lot of people aware of what worse could happen if the transactions are leaking and how easy it is to fix them. 😊</p>
<p>To conclude my take I recommend using rollback of the transaction in <strong>defer always.</strong> Finding a reasonable timeout for context and server-side timeout is difficult. So, it doesn't matter if there is an error or not <code>defer</code> with rollback.</p>
<p>Cancel context is the best approach for most application types. And would serve most of the cases. The context in general has a lot of use cases and I would recommend you read more about it.</p>
<p>Also, for an extra layer of safety, I would recommend keeping a Server side timeout for HTTP services.</p>
<p>You would be also thinking about the client-side timeout. Those are not reliable enough. For example, a client could keep the timeout for a long duration and with a high load, the server could keep the connection open for a long time. This could again lead to the same problem we are trying to solve.</p>
<h2 id="heading-references"><strong>References:</strong></h2>
<ul>
<li><p><a target="_blank" href="https://go.dev/blog/context">Go Concurrency Patterns: Context</a></p>
</li>
<li><p><a target="_blank" href="https://go.dev/blog/context-and-structs">Contexts and structs</a></p>
</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://giphy.com/gifs/theoffice-the-office-episode-23-tv-DhstvI3zZ598Nb1rFf?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F">https://giphy.com/gifs/theoffice-the-office-episode-23-tv-DhstvI3zZ598Nb1rFf?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fcdn.embedly.com%2F</a></div>
<p> </p>
<p>I hope my team does a similar kind of dance one day 😅</p>
<blockquote>
<p><strong>Edits:</strong></p>
<ul>
<li><p>Fixed the context code with using cancel context instead of timeout context</p>
</li>
<li><p>Added the source code of golang database layer on how the cancellation is managed</p>
</li>
</ul>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Docker Compose For Your Next Debezium and Postgres Project]]></title><description><![CDATA[This is an addition to my docker-compose setup that allows me to test applications locally and quickly. I like running the services that I am building on my Mac because then it is easy to try any scenarios or replicate the reported bugs also, I think...]]></description><link>https://hashnode.iamninad.com/docker-compose-for-your-next-debezium-and-postgres-project</link><guid isPermaLink="true">https://hashnode.iamninad.com/docker-compose-for-your-next-debezium-and-postgres-project</guid><category><![CDATA[debezium]]></category><category><![CDATA[kafka]]></category><category><![CDATA[Docker compose]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Sat, 04 Feb 2023 12:16:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GNyjCePVRs8/upload/ecf581b0743c8600c683bc4e2dd4e337.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is an addition to my docker-compose setup that allows me to test applications locally and quickly. I like running the services that I am building on my Mac because then it is easy to try any scenarios or replicate the reported bugs also, I think the entire CI/CD duration to deploy the change to the staging/test environment sometimes takes more time for testing small stuff <em>(I am not saying that you should never test services on staging/test env 😅)</em>.</p>
<p>I am working with Change Data Capture using Debezium recently and this docker-compose helps me to write the consumer application that will process the changes happening at the DB level.</p>
<p>In this post, I will be setting up the Debezium connector for Postgres with all the changes required to allow Debezium to capture the changes. In addition, I will also provide the details on how to make this configuration on the AWS RDS if you are going to use the Debezium Connector for your next project.</p>
<p>The following services would be needed to make the Debezium and Postgres work locally:</p>
<ul>
<li><p>Kafka Broker - <code>localhost:9092</code></p>
</li>
<li><p>Zookeeper - <code>localhost:2181</code></p>
</li>
<li><p>Postgres - <code>localhost:5432</code></p>
</li>
<li><p>Debezium Connector - <code>localhost:8083</code></p>
</li>
<li><p>Schema Registry - <code>localhost:8081</code></p>
</li>
<li><p>Debezium UI - <code>localhost:8080</code></p>
</li>
<li><p>Rest-Proxy - This is optional, but helps with checking cluster metadata, topics etc - <code>localhost:8082</code></p>
</li>
</ul>
<h3 id="heading-starting-docker-compose">Starting Docker Compose</h3>
<p>Below is the docker-compose file I use to start the stack:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.9"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">zookeeper:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-zookeeper:7.3.1</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">zookeeper</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">zookeeper</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"2181:2181"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">ZOOKEEPER_CLIENT_PORT:</span> <span class="hljs-number">2181</span>
      <span class="hljs-attr">ZOOKEEPER_TICK_TIME:</span> <span class="hljs-number">2000</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">echo</span> <span class="hljs-string">srvr</span> <span class="hljs-string">|</span> <span class="hljs-string">nc</span> <span class="hljs-string">zookeeper</span> <span class="hljs-number">2181</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">20</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
  <span class="hljs-attr">broker:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-kafka:7.3.1</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">broker</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">broker</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">zookeeper:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"29092:29092"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9092:9092"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9101:9101"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_BROKER_ID:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_ZOOKEEPER_CONNECT:</span> <span class="hljs-string">'zookeeper:2181'</span>
      <span class="hljs-attr">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:</span> <span class="hljs-string">PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092</span>
      <span class="hljs-attr">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_TRANSACTION_STATE_LOG_MIN_ISR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS:</span> <span class="hljs-number">0</span>
      <span class="hljs-attr">KAFKA_AUTO_CREATE_TOPICS_ENABLE:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">KAFKA_JMX_PORT:</span> <span class="hljs-number">9101</span>
      <span class="hljs-attr">KAFKA_JMX_HOSTNAME:</span> <span class="hljs-string">localhost</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">nc</span> <span class="hljs-string">-z</span> <span class="hljs-string">localhost</span> <span class="hljs-number">9092</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">-1</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">15s</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">10</span>
  <span class="hljs-attr">debezium:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">debezium/connect:latest</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">debezium</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">debezium</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">postgres:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
      <span class="hljs-attr">broker:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8083:8083"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">BOOTSTRAP_SERVERS:</span> <span class="hljs-string">broker:29092</span>
      <span class="hljs-attr">GROUP_ID:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">CONFIG_STORAGE_TOPIC:</span> <span class="hljs-string">connect_configs</span>
      <span class="hljs-attr">STATUS_STORAGE_TOPIC:</span> <span class="hljs-string">connect_statuses</span>
      <span class="hljs-attr">OFFSET_STORAGE_TOPIC:</span> <span class="hljs-string">connect_offsets</span>
      <span class="hljs-attr">KEY_CONVERTER:</span> <span class="hljs-string">org.apache.kafka.connect.json.JsonConverter</span>
      <span class="hljs-attr">VALUE_CONVERTER:</span> <span class="hljs-string">org.apache.kafka.connect.json.JsonConverter</span>
      <span class="hljs-attr">ENABLE_DEBEZIUM_SCRIPTING:</span> <span class="hljs-string">"true"</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">"CMD"</span>, <span class="hljs-string">"curl"</span>, <span class="hljs-string">"--silent"</span>, <span class="hljs-string">"--fail"</span>, <span class="hljs-string">"-X"</span>, <span class="hljs-string">"GET"</span>, <span class="hljs-string">"http://localhost:8083/connectors"</span>]
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>

  <span class="hljs-attr">schema-registry:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-schema-registry:7.3.1</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">schema-registry</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">schema-registry</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">broker:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8081:8081"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">SCHEMA_REGISTRY_HOST_NAME:</span> <span class="hljs-string">schema-registry</span>
      <span class="hljs-attr">SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS:</span> <span class="hljs-string">broker:29092</span>
      <span class="hljs-attr">SCHEMA_REGISTRY_LISTENERS:</span> <span class="hljs-string">http://0.0.0.0:8081</span>

    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">20</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">curl</span> <span class="hljs-string">--user</span> <span class="hljs-string">superUser:superUser</span> <span class="hljs-string">--fail</span> <span class="hljs-string">--silent</span> <span class="hljs-string">--insecure</span> <span class="hljs-string">http://localhost:8081/subjects</span> <span class="hljs-string">--output</span> <span class="hljs-string">/dev/null</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>

  <span class="hljs-attr">rest-proxy:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-kafka-rest:7.3.1</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">broker:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8082:8082"</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">rest-proxy</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">rest-proxy</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_REST_HOST_NAME:</span> <span class="hljs-string">rest-proxy</span>
      <span class="hljs-attr">KAFKA_REST_BOOTSTRAP_SERVERS:</span> <span class="hljs-string">'broker:29092'</span>
      <span class="hljs-attr">KAFKA_REST_LISTENERS:</span> <span class="hljs-string">"http://0.0.0.0:8082"</span>

  <span class="hljs-attr">debezium-ui:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">debezium/debezium-ui:latest</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">debezium-ui</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">debezium-ui</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">debezium:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8080:8080"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_CONNECT_URIS:</span> <span class="hljs-string">http://debezium:8083</span>


  <span class="hljs-attr">postgres:</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:latest</span>
      <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
      <span class="hljs-attr">container_name:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"5432:5432"</span>
      <span class="hljs-attr">environment:</span>
        <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
        <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
        <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">movies_db</span>
      <span class="hljs-attr">command:</span> [<span class="hljs-string">"postgres"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"wal_level=logical"</span>]
      <span class="hljs-attr">healthcheck:</span>
        <span class="hljs-attr">test:</span> [<span class="hljs-string">"CMD"</span>, <span class="hljs-string">"psql"</span>, <span class="hljs-string">"-U"</span>, <span class="hljs-string">"postgres"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"SELECT 1"</span>]
        <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
        <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
        <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">./scripts:/docker-entrypoint-initdb.d</span>
</code></pre>
<p>For Debezium to work with Postgres, Postgres needs to have the <code>logical replication</code> enabled and if you observe the line <code>command: ["postgres", "-c", "wal_level=logical"]</code> we are configuring the Postgres DB to start with <code>wal_level</code> as <code>logical</code>.</p>
<p>If we don't do this step, then debezium would not be able to capture the changes happening on Postgres. The default <code>wal_level</code> is <code>replica</code>.</p>
<p>Now we have our docker-compose file ready let's start all the service</p>
<pre><code class="lang-bash"> docker compose -f docker-compose.yml up -d
[+] Running 8/8
 ⠿ Network postgres_default   Created                                                                                                                                                                          0.1s
 ⠿ Container postgres         Healthy                                                                                                                                                                         12.8s
 ⠿ Container zookeeper        Healthy                                                                                                                                                                         11.8s
 ⠿ Container broker           Healthy                                                                                                                                                                         22.6s
 ⠿ Container debezium         Healthy                                                                                                                                                                         44.2s
 ⠿ Container rest-proxy       Started                                                                                                                                                                         23.1s
 ⠿ Container schema-registry  Started                                                                                                                                                                         23.1s
 ⠿ Container debezium-ui      Started                                                                                                                                                                         44.6s
</code></pre>
<p>As we could see all the containers have started without any errors. If you are running this command for the first time it would take some time to download all the docker images but the later executions will be faster</p>
<h3 id="heading-configure-postgres-connector">Configure Postgres Connector</h3>
<p>At this point, our Debezium connector is running but it doesn't have any task to read changes happening on the Postgres DB. We need to register the Postgres connector using HTTP API so that debezium could read the transaction logs from the server.</p>
<p>It's easy to register the connector, there is a sample connector config already present in the repo. Execute the below <code>curl</code> and we would see the connector registered</p>
<pre><code class="lang-bash">curl -X POST --location <span class="hljs-string">"http://localhost:8083/connectors"</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -H <span class="hljs-string">"Accept: application/json"</span> -d @connector.json
</code></pre>
<pre><code class="lang-json">{
    <span class="hljs-attr">"config"</span>: {
        <span class="hljs-attr">"connector.class"</span>: <span class="hljs-string">"io.debezium.connector.postgresql.PostgresConnector"</span>,
        <span class="hljs-attr">"database.dbname"</span>: <span class="hljs-string">"movies_db"</span>,
        <span class="hljs-attr">"database.history.kafka.bootstrap.servers"</span>: <span class="hljs-string">"kafka:9092"</span>,
        <span class="hljs-attr">"database.history.kafka.topic"</span>: <span class="hljs-string">"schema-changes.movies"</span>,
        <span class="hljs-attr">"database.hostname"</span>: <span class="hljs-string">"postgres"</span>,
        <span class="hljs-attr">"database.password"</span>: <span class="hljs-string">"postgres"</span>,
        <span class="hljs-attr">"database.port"</span>: <span class="hljs-string">"5432"</span>,
        <span class="hljs-attr">"database.server.name"</span>: <span class="hljs-string">"postgres"</span>,
        <span class="hljs-attr">"database.user"</span>: <span class="hljs-string">"postgres"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"movies-db-connector"</span>,
        <span class="hljs-attr">"plugin.name"</span>: <span class="hljs-string">"pgoutput"</span>,
        <span class="hljs-attr">"table.include.list"</span>: <span class="hljs-string">"public.movies"</span>,
        <span class="hljs-attr">"tasks.max"</span>: <span class="hljs-string">"1"</span>,
        <span class="hljs-attr">"topic.creation.default.cleanup.policy"</span>: <span class="hljs-string">"delete"</span>,
        <span class="hljs-attr">"topic.creation.default.partitions"</span>: <span class="hljs-string">"1"</span>,
        <span class="hljs-attr">"topic.creation.default.replication.factor"</span>: <span class="hljs-string">"1"</span>,
        <span class="hljs-attr">"topic.creation.default.retention.ms"</span>: <span class="hljs-string">"604800000"</span>,
        <span class="hljs-attr">"topic.creation.enable"</span>: <span class="hljs-string">"true"</span>,
        <span class="hljs-attr">"topic.prefix"</span>: <span class="hljs-string">"postgres"</span>
    },
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"movies-db-connector"</span>,
    <span class="hljs-attr">"tasks"</span>: [],
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"source"</span>
}
</code></pre>
<h3 id="heading-list-kafka-topics">List Kafka Topics</h3>
<p>If there was no issue running the above steps we could confirm that our connector is working fine by checking if the topic is created for <code>movies</code> table by the connector.</p>
<pre><code class="lang-bash">❯ kafka-topics --bootstrap-server localhost:9092 --list
__consumer_offsets
_schemas
connect_configs
connect_offsets
connect_statuses
postgres.public.movies
</code></pre>
<p>As you could see from the above command we have <code>postgres.public.movies</code> a topic created by debezium. The topic name pattern is of a type <code>topic_prefix_config.schema.table</code> so the prefix is what we configured and the other part is fetched by the debezium from the database schema</p>
<h3 id="heading-reading-the-data">Reading the data</h3>
<p>Now we know that the topic is created next we would check the data available on the topic.</p>
<p>There would be data present in the topic because when the connector starts it takes an initial snapshot of the database table. This is a default config named <code>snapshot.mode</code> which we didn't configure but is set to <code>initial</code> which means that the connector will do a snapshot on the initial run when it doesn't find the last known offset from the transaction log available for the database server, to understand more about this configuration and others read more <a target="_blank" href="https://debezium.io/documentation/reference/2.1/connectors/postgresql.html#postgresql-property-snapshot-mode">here</a>.</p>
<pre><code class="lang-bash">kafka-console-consumer --bootstrap-server localhost:9092 --topic postgres.public.movies --from-beginning
{
    <span class="hljs-string">"schema"</span>: {
        <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
        <span class="hljs-string">"fields"</span>: [
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                <span class="hljs-string">"fields"</span>: [
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"id"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"title"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int32"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"year"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"director"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                        <span class="hljs-string">"fields"</span>: [
                            {
                                <span class="hljs-string">"type"</span>: <span class="hljs-string">"int32"</span>,
                                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                                <span class="hljs-string">"field"</span>: <span class="hljs-string">"scale"</span>
                            },
                            {
                                <span class="hljs-string">"type"</span>: <span class="hljs-string">"bytes"</span>,
                                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                                <span class="hljs-string">"field"</span>: <span class="hljs-string">"value"</span>
                            }
                        ],
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"name"</span>: <span class="hljs-string">"io.debezium.data.VariableScaleDecimal"</span>,
                        <span class="hljs-string">"version"</span>: 1,
                        <span class="hljs-string">"doc"</span>: <span class="hljs-string">"Variable scaled decimal"</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"rating"</span>
                    }
                ],
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"postgres.public.movies.Value"</span>,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"before"</span>
            },
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                <span class="hljs-string">"fields"</span>: [
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"id"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"title"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int32"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"year"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"director"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                        <span class="hljs-string">"fields"</span>: [
                            {
                                <span class="hljs-string">"type"</span>: <span class="hljs-string">"int32"</span>,
                                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                                <span class="hljs-string">"field"</span>: <span class="hljs-string">"scale"</span>
                            },
                            {
                                <span class="hljs-string">"type"</span>: <span class="hljs-string">"bytes"</span>,
                                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                                <span class="hljs-string">"field"</span>: <span class="hljs-string">"value"</span>
                            }
                        ],
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"name"</span>: <span class="hljs-string">"io.debezium.data.VariableScaleDecimal"</span>,
                        <span class="hljs-string">"version"</span>: 1,
                        <span class="hljs-string">"doc"</span>: <span class="hljs-string">"Variable scaled decimal"</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"rating"</span>
                    }
                ],
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"postgres.public.movies.Value"</span>,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"after"</span>
            },
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                <span class="hljs-string">"fields"</span>: [
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"version"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"connector"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"name"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"ts_ms"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                        <span class="hljs-string">"name"</span>: <span class="hljs-string">"io.debezium.data.Enum"</span>,
                        <span class="hljs-string">"version"</span>: 1,
                        <span class="hljs-string">"parameters"</span>: {
                            <span class="hljs-string">"allowed"</span>: <span class="hljs-string">"true,last,false,incremental"</span>
                        },
                        <span class="hljs-string">"default"</span>: <span class="hljs-string">"false"</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"snapshot"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"db"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"sequence"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"schema"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"table"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"txId"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"lsn"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"xmin"</span>
                    }
                ],
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"io.debezium.connector.postgresql.Source"</span>,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"source"</span>
            },
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"op"</span>
            },
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"ts_ms"</span>
            },
            {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"struct"</span>,
                <span class="hljs-string">"fields"</span>: [
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"id"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"total_order"</span>
                    },
                    {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"int64"</span>,
                        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
                        <span class="hljs-string">"field"</span>: <span class="hljs-string">"data_collection_order"</span>
                    }
                ],
                <span class="hljs-string">"optional"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"event.block"</span>,
                <span class="hljs-string">"version"</span>: 1,
                <span class="hljs-string">"field"</span>: <span class="hljs-string">"transaction"</span>
            }
        ],
        <span class="hljs-string">"optional"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-string">"name"</span>: <span class="hljs-string">"postgres.public.movies.Envelope"</span>,
        <span class="hljs-string">"version"</span>: 1
    },
    <span class="hljs-string">"payload"</span>: {
        <span class="hljs-string">"before"</span>: null,
        <span class="hljs-string">"after"</span>: {
            <span class="hljs-string">"id"</span>: 1,
            <span class="hljs-string">"title"</span>: <span class="hljs-string">"The Shawshank Redemption"</span>,
            <span class="hljs-string">"year"</span>: 1994,
            <span class="hljs-string">"director"</span>: <span class="hljs-string">"Frank Darabont"</span>,
            <span class="hljs-string">"rating"</span>: {
                <span class="hljs-string">"scale"</span>: 1,
                <span class="hljs-string">"value"</span>: <span class="hljs-string">"XQ=="</span>
            }
        },
        <span class="hljs-string">"source"</span>: {
            <span class="hljs-string">"version"</span>: <span class="hljs-string">"2.1.1.Final"</span>,
            <span class="hljs-string">"connector"</span>: <span class="hljs-string">"postgresql"</span>,
            <span class="hljs-string">"name"</span>: <span class="hljs-string">"postgres"</span>,
            <span class="hljs-string">"ts_ms"</span>: 1675103983563,
            <span class="hljs-string">"snapshot"</span>: <span class="hljs-string">"first"</span>,
            <span class="hljs-string">"db"</span>: <span class="hljs-string">"movies_db"</span>,
            <span class="hljs-string">"sequence"</span>: <span class="hljs-string">"[null,\"26612016\"]"</span>,
            <span class="hljs-string">"schema"</span>: <span class="hljs-string">"public"</span>,
            <span class="hljs-string">"table"</span>: <span class="hljs-string">"movies"</span>,
            <span class="hljs-string">"txId"</span>: 744,
            <span class="hljs-string">"lsn"</span>: 26612016,
            <span class="hljs-string">"xmin"</span>: null
        },
        <span class="hljs-string">"op"</span>: <span class="hljs-string">"r"</span>,
        <span class="hljs-string">"ts_ms"</span>: 1675103983739,
        <span class="hljs-string">"transaction"</span>: null
    }
}
----- more data ommitted <span class="hljs-keyword">for</span> readability purpose -----
</code></pre>
<h3 id="heading-cleanup">Cleanup</h3>
<p>By running the below command all the services will be shut down and the resources would be released</p>
<pre><code class="lang-bash">docker compose -f docker-compose.yml -v down
</code></pre>
<p>Here we specify <code>-v</code> option to also remove any volumes created by the docker-compose, this will also free the disk space allocated to the containers</p>
<h3 id="heading-debezium-ui">Debezium UI</h3>
<p>Debezium also provides a control dashboard. You could use it to add a new connector instead of using the REST API. The dashboard also provides the functionality to Pause or Restart the debezium task and also to delete the connector</p>
<p>Go to <code>http://localhost:8080</code> and you will be able to access the dashboard as shown below</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675511194851/2578ab99-14ed-4f6d-8623-5615b9a80d18.png" alt="Debezium Dashboard" class="image--center mx-auto" /></p>
<p>The Dashboard also allows you to add new connectors for other database providers</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675511286435/2760fb50-b814-4fd6-992e-f7bd310545d3.png" alt="Adding new connector" class="image--center mx-auto" /></p>
<h2 id="heading-aws-rds">AWS RDS</h2>
<p>Setting up docker-compose is easy because everything is local and we could use the configuration described by the Postgres documentation.</p>
<p>On AWS, it's a different story, on AWS we have to configure <code>rds.logical_replication</code> property in the parameter groups of your Postgres instance. The default value for this property is <code>0</code> which means the logical replication is disabled by default, however, setting it to <code>1</code> will enable the logical replication. An important thing that you should know is that when you change this property on AWS you would need to restart your AWS RDS Postgres instance. If your application is in production that means you would need some downtime to enable the replication. You could read more about this and the steps to do it in AWS RDS <a target="_blank" href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Replication.Logical.html#AuroraPostgreSQL.Replication.Logical.Configure">here</a></p>
<hr />
<p>This brings us to the end of this small and quick setup of Postgres and Debezium using Docker Compose. Hope, next time you are building any CDC-related consumer this docker-compose setup will help you to run the infrastructure locally without any hassle.</p>
<p>If you like the blog, please don't forget to like the post and subscribe to the Newsletter so that you would get the new blog posts and updates directly to your mailbox.</p>
<h3 id="heading-code">Code</h3>
<ul>
<li><a target="_blank" href="https://github.com/ninadingole/docker-compose-stacks">Github</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Docker Compose For Prometheus + Grafana]]></title><description><![CDATA[It becomes important that with all the tools you have in production, you can test the user flows end to end locally with very fewer efforts. The rationale behind such is the reduced feedback cycle that saves a developer from taking a long time to ach...]]></description><link>https://hashnode.iamninad.com/docker-compose-for-prometheus-grafana</link><guid isPermaLink="true">https://hashnode.iamninad.com/docker-compose-for-prometheus-grafana</guid><category><![CDATA[Devops]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Thu, 25 Feb 2021 12:14:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/jOqJbvo1P9g/upload/v1661506202304/rqN-WL_ye.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It becomes important that with all the tools you have in production, you can test the user flows end to end locally with very fewer efforts. The rationale behind such is the reduced feedback cycle that saves a developer from taking a long time to achieve the desired outcome.</p>
<p>I was working on sending some telemetry data from my Golang web application to Prometheus and then creating a Grafana dashboard out of it. But to test the flow I needed a local setup of Prometheus + Grafana so that I can check if the metrics are right and that I am building the right PromQL query to create the dashboard.</p>
<p>I am maintaining a Github repo for all the docker-compose setups I require for my local testing and now Prometheus + Grafana is a new addition to it. If you also need a similar setup and save your setup time in future and focus more on building things refer to this post or my Github Repository.</p>
<p>Clone the repo: <code>https://github.com/ninadingole/docker-compose-stacks</code></p>
<p>Then go to <code>prometheus-grafana</code> folder and run <code>docker-compose up -d</code>.</p>
<p>This will start Prometheus on <code>http://localhost:9090</code> and Grafana on <code>http://localhost:3000</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661512251293/nkc6cmgmI.png" alt="Prometheus running on localhost:9090" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661512166604/LPbWZlccj.png" alt="Grafana settings to connect to local Prometheus" /></p>
<p>There is also a <code>prometheus.yml</code> the configuration file which you can use to add the local apps that you want to scrape,</p>
<p><strong>Note</strong>: if your application is running inside a docker then use <code>host.docker.internal</code> as your hostname with the port to scrape the target.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">global:</span>
  <span class="hljs-attr">scrape_interval:</span>     <span class="hljs-string">15s</span>
  <span class="hljs-attr">evaluation_interval:</span> <span class="hljs-string">15s</span>

<span class="hljs-attr">rule_files:</span>
  <span class="hljs-comment"># - "first.rules"</span>
  <span class="hljs-comment"># - "second.rules"</span>

<span class="hljs-attr">scrape_configs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">prometheus</span>
    <span class="hljs-attr">static_configs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'localhost:9090'</span>]
  <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">app</span>
    <span class="hljs-attr">scrape_interval:</span> <span class="hljs-string">5s</span>
    <span class="hljs-attr">static_configs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'host.docker.internal:10088'</span>]
</code></pre>
<p>Once you have added the configs to connect Grafana to Prometheus like the above image, you are ready with an end-to-end setup on your local.</p>
<p>If you also have your own docker-compose stack setups that you use during your development please don't hesitate to share with me by sending me a PR to the repository.</p>
<hr />
<p>I hope you will find this docker-compose configuration pretty useful and saves you time. Please subscribe to the newsletter to get more articles delivered right to your inbox.</p>
<p>Thanks and Happy Coding!</p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li><a target="_blank" href="https://github.com/ninadingole/docker-compose-stacks">github</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Burp Suite For Web App Testing Go Lang]]></title><description><![CDATA[Have you ever come across a scenario where you want your code to call a dependent service however need to change the response so that you can test how different your code will behave to different data?
Recently, I came across the same situation where...]]></description><link>https://hashnode.iamninad.com/burp-suite-for-web-app-testing-go-lang</link><guid isPermaLink="true">https://hashnode.iamninad.com/burp-suite-for-web-app-testing-go-lang</guid><category><![CDATA[golang]]></category><category><![CDATA[Testing]]></category><category><![CDATA[testing tool]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Tue, 31 Dec 2019 10:51:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/cvBBO4PzWPg/upload/v1661680109205/IvjYI2Dve.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever come across a scenario where you want your code to call a dependent service however need to change the response so that you can test how different your code will behave to different data?</p>
<p>Recently, I came across the same situation where I was looking for a way to change the response of the service that I was consuming and instead of mocking the entire service using a tool like Postman or wire mock, I wanted to hit the actual service and then change a few fields to see if my error handling works as per the expectations.</p>
<p>Normally, we do write an integration test where we mock the service response using wiremock and then write the test for such edge cases. However, sometimes when we do manual testing and we want a way to mock the service response we need some mechanism to intercept the service response and modify the response. This is where Burp suites will help, Burp suites acts like a proxy that will call the actual service on behalf of you and then provides you with tools that can help to easily modify the response and send it back to your application.</p>
<p>For this post, I will be consuming <code>themoviesdb.org</code> API's to see details of "Fight Club" movie, and then I will write an application in golang which will connect to Burp Suite, there we modify the response using Burpsuite UI and print the modified response in the console which is received by our application.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"crypto/tls"</span>
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"net/url"</span>
    <span class="hljs-string">"os"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    proxyURL, e := url.Parse(os.Getenv(<span class="hljs-string">"PROXY"</span>))
    <span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(e)
    }
    client := &amp;amp;http.Client{
        <span class="hljs-comment">//Timeout: 20 * time.Second,</span>
        Transport: &amp;amp;http.Transport{
            Proxy: http.ProxyURL(proxyURL),
            TLSClientConfig: &amp;amp;tls.Config{InsecureSkipVerify:<span class="hljs-literal">true</span>},
        },
    }
    get, err := client.Get(fmt.Sprintf(<span class="hljs-string">"https://api.themoviedb.org/3/movie/550?api_key=%s"</span>, os.Getenv(<span class="hljs-string">"KEY"</span>)))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    <span class="hljs-keyword">var</span> data <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}
    err = json.NewDecoder(get.Body).Decode(&amp;amp;data)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    fmt.Printf(<span class="hljs-string">"Response: %+v"</span>,data)
}
</code></pre>
<p>As you can see in the above code, we create an HTTP client with custom Transport set to proxy URL which is configured in the environment setting of the application, for burp suite the URL is <code>http://localhost:8080</code>. The TLSClient config is configured to skip ssl certificate verification using <code>InsecureSkipVerify:true</code>. Then we do a normal <code>GET</code> request to the api which will get the details of the movie id <code>550</code>.</p>
<p>Let's now start the burp suite and configure the intercept rule to only run when the domain matches <code>themoviedb.org</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661680156032/HFdOE4B8U.png" alt="Add themoviedb domain to intercept criteria" /></p>
<p>Then start the interceptor:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661680198635/W2-DiKHqQ.png" alt="Enable Intercept" /></p>
<p>Now, run the go application and you will see that the request is captured by the burp suite as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661680229427/A5oz299G9.png" alt="When the request is captured" /></p>
<p>After you see the above screen click on the <code>Action</code> button and click <code>Do intercept</code> with <code>Response to this request</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661680251747/-QegNiQxu.png" alt="Select intercept response" /></p>
<p>After clicking the <code>Forward</code> button, burp suite will call the actual service and will open the editor where you can edit the response, this movie is "Flight Club" movie and I have changed the movie name to <code>Fight Pub</code> and rating from 8.4 to 9.4. Then click the forward button again to forward the response to the golang app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661680275739/MD9n59z_e.jpg" alt="screenshot-2019-12-30-at-2.37.43-pm-1-.jpg" /></p>
<p>Once the application receives this response we just print it to the console and you could see that the name of the movie is now <code>Fight Pub</code> and the rating is <code>9.4</code> which is what we modified in the response.</p>
<pre><code class="lang-go">Response: <span class="hljs-keyword">map</span>[adult:<span class="hljs-literal">false</span> 
backdrop_path:/mMZRKb3NVo5ZeSPEIaNW9buLWQ0.jpg belongs_to_collection:&amp;lt;<span class="hljs-literal">nil</span>&amp;gt; 
budget:<span class="hljs-number">6.3e+07</span> 
genres:[<span class="hljs-keyword">map</span>[id:<span class="hljs-number">18</span> name:Drama]] 
homepage:http:<span class="hljs-comment">//www.foxmovies.com/movies/fight-club id:550 imdb_id:tt0137523 </span>
original_language:en 
original_title:Fight Pub 
overview:A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking <span class="hljs-built_in">new</span> form of therapy. Their concept catches on, with underground <span class="hljs-string">"fight clubs"</span> forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion. 
popularity:<span class="hljs-number">58.844</span> 
poster_path:/adw6Lq9FiC9zjYEpOqfq03ituwp.jpg 
production_companies:[<span class="hljs-keyword">map</span>[id:<span class="hljs-number">508</span> logo_path:/<span class="hljs-number">7</span>PzJdsLGlR7oW4J0J5Xcd0pHGRg.png name:Regency Enterprises origin_country:US] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">711</span> logo_path:/tEiIH5QesdheJmDAqQwvtN60727.png name:Fox <span class="hljs-number">2000</span> Pictures origin_country:US] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">20555</span> logo_path:/hD8yEGUBlHOcfHYbujp71vD8gZp.png name:Taurus Film origin_country:DE] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">54051</span> logo_path:&amp;lt;<span class="hljs-literal">nil</span>&amp;gt; name:Atman Entertainment origin_country:] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">54052</span> logo_path:&amp;lt;<span class="hljs-literal">nil</span>&amp;gt; name:Knickerbocker Films origin_country:US] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">25</span> logo_path:/qZCc1lty5FzX30aOCVRBLzaVmcp.png name:<span class="hljs-number">20</span>th Century Fox origin_country:US] <span class="hljs-keyword">map</span>[id:<span class="hljs-number">4700</span> logo_path:/A32wmjrs9Psf4zw0uaixF0GXfxq.png name:The Linson Company origin_country:]] 
production_countries:[<span class="hljs-keyword">map</span>[iso_3166_1:DE name:Germany] <span class="hljs-keyword">map</span>[iso_3166_1:US name:United States of America]] 
release_date:<span class="hljs-number">1999</span><span class="hljs-number">-10</span><span class="hljs-number">-15</span> 
revenue:<span class="hljs-number">1.00853753e+08</span> 
runtime:<span class="hljs-number">139</span> 
spoken_languages:[<span class="hljs-keyword">map</span>[iso_639_1:en name:English]] 
status:Released 
tagline:Mischief. Mayhem. Soap. 
title:Fight Pub 
video:<span class="hljs-literal">false</span> 
vote_average:<span class="hljs-number">9.4</span> 
vote_count:<span class="hljs-number">17753</span>]
</code></pre>
<p>So, thats how you can connect your application to the burp suite and can intercept the response of the services to manually test the application for different cases.</p>
<p>I hope this post helps you to understand how to configure an HTTP client in your code to connect to Burp suite, I have used golang for this post because I am currently working on it. However, if you are working on any other programming language the HTTP Api's will have some way to configure the proxy as like I have done in my golang code.</p>
<p>Happy Coding!</p>
]]></content:encoded></item><item><title><![CDATA[Organizing Scala Tests For Faster Feedback]]></title><description><![CDATA[Writing tests is a developer's mundane task, and when you follow practices like TDD or BDD, hardly you find any code without any test cases. We all might have seen the testing pyramid in agile and if not then you can read this blog by Martin Fowler o...]]></description><link>https://hashnode.iamninad.com/organizing-scala-tests-for-faster-feedback</link><guid isPermaLink="true">https://hashnode.iamninad.com/organizing-scala-tests-for-faster-feedback</guid><category><![CDATA[sbt]]></category><category><![CDATA[Scala]]></category><category><![CDATA[integration test]]></category><category><![CDATA[Testing]]></category><category><![CDATA[scalatest]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Mon, 18 Mar 2019 21:45:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661459920376/iL6UbWaRX.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Writing tests is a developer's mundane task, and when you follow practices like TDD or BDD, hardly you find any code without any test cases. We all might have seen the testing pyramid in agile and if not then you can read this <a target="_blank" href="https://martinfowler.com/articles/practical-test-pyramid.html">blog</a> by Martin Fowler on the testing pyramid.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661459960783/0SXa8O378.png" alt="testing pyramid by 360logica" /></p>
<p>In short and crisp, tests gets divided into three categories: <em>unit test, integration/service test, and e2e/UI tests</em>. The unit test validates the functionality of each class and written on per class basis, integration test checks for the functionality of module with external systems like database, web service, and UI/e2e test the entire business flow or UI flow.</p>
<p>As you can see from the test pyramid, we should have more integration test than e2e test and more unit test than the integration test. The reason for having less integration and e2e tests is "time"; Integration test and e2e test interacts typically with other systems which are external or internal like in-memory DB or clusters. These tests take time to execute and are often slower.</p>
<p>And not to mention we have our CD/CI pipelines which run these tests and generate our code artifact. We also want our CI to provide us faster feedback by running these test individually, so we don't have to wait longer if there is an issue at the unit level.</p>
<p>Currently, I am working on a Big Data project which includes frameworks like Spark, HBase, Kafka, HDFS, etc. Some of our unit tests are independent of any spark code, and some unit test does require spark context, but they do not interact with external systems like HDFS and HBase. Moreover, integration test, runs an in-memory HBase cluster, in-memory mini dfs cluster and spark of course :P. Now, these integration test shares the single running clusters amongst them and executes sequentially.</p>
<p>We have SBT as the build tool and, we have disabled the parallel execution of tests which makes the entire test suite including the unit test to run slower. I am struggling with that sometimes have to wait for 10-15mins to get the feedback about my code changes, running unit tests also runs integration test which takes time for cluster initialization of HBase and HDFS, and I was seriously looking for some alternative to this issue. Seldom I do comment out my integration suite code only to run the unit test which is again a wrong way. We need some mechanism to organize our tests such that we can execute them separately as a suite of unit, integration and e2e tests and, we only have parallel execution disabled for integration and e2e tests. It would provide developers with quick feedback by just running the unit tests.</p>
<p>In this post, I am going to share my findings on how to organize the tests and configure sbt (<strong>v1.2.8</strong>) such that it enables us to run these different scala test independently without any code hacks or commenting your test cases which is a more of an ugly way.</p>
<h2 id="heading-integration-test">Integration Test</h2>
<p>Integration Test setting comes default in SBT. It's just a matter of configuring the modules with these settings in the build.sbt</p>
<pre><code class="lang-scala"><span class="hljs-keyword">lazy</span> <span class="hljs-keyword">val</span> module1 = (project in file(<span class="hljs-string">"module1"</span>))
  .configs(<span class="hljs-type">IntegrationTest</span>)
.settings(
<span class="hljs-type">Defaults</span>.itSettings,
  libraryDependencies += scalatest % <span class="hljs-string">"it,test"</span>
)
</code></pre>
<p>Adding "it, test" at the end of dependencies enables that lib to be available to both standard unit test &amp; integration test packages. After this, you have a new location to write an integration test.</p>
<pre><code class="lang-vim">src/it/scala
src/it/java
src/it/resources
</code></pre>
<p>I have created an integration test per module and placed in the above location now to execute the integration test use <code>IntegrationTest / testOnly</code> or <code>it:test</code> in SBT console, and the output as:</p>
<pre><code class="lang-log">[info] Compiling 1 Scala source to /Users/neenad/codes/tutorials/scala/sbt-learnings/module1/target/scala-2.11/it-classes ...
[info] Run completed in 9 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
this is a integration test
[info] Module2IntegrationTest1:
[info] - integration test
[info] Run completed in 194 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Done compiling.
module1 integration test
[info] Module1IntegrationTest:
[info] - integration test
[info] Run completed in 134 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 17 Mar, 2019 5:52:42 PM
</code></pre>
<h2 id="heading-end-to-end-tests">End-To-End Tests</h2>
<p>Like integration test end-to-end configuration is not provided by SBT. We would need to create them and apply to the module. I have created an E2E object in the <code>project</code> folder and, the contents are as below:</p>
<pre><code class="lang-scala"><span class="hljs-keyword">import</span> sbt.{<span class="hljs-type">Configuration</span>, <span class="hljs-type">Defaults</span>, <span class="hljs-type">Test</span>, inConfig}
<span class="hljs-keyword">import</span> sbt._
<span class="hljs-keyword">import</span> sbt.<span class="hljs-type">Keys</span>._
<span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">E2E</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-keyword">val</span> <span class="hljs-type">E2ETest</span> = <span class="hljs-type">Configuration</span>.of(<span class="hljs-string">"EndToEndTest"</span>, <span class="hljs-string">"e2e"</span>) extend (<span class="hljs-type">Test</span>)
  <span class="hljs-keyword">final</span> <span class="hljs-keyword">val</span> e2eSettings =
    inConfig(<span class="hljs-type">E2ETest</span>)(e2eConfig)
  <span class="hljs-keyword">lazy</span> <span class="hljs-keyword">val</span> e2eConfig =
      <span class="hljs-type">Defaults</span>.configSettings ++ <span class="hljs-type">Defaults</span>.testTasks ++ <span class="hljs-type">Seq</span>(
    scalaSource in <span class="hljs-type">E2ETest</span> := baseDirectory.value / <span class="hljs-string">"src"</span> / <span class="hljs-string">"e2e"</span> / <span class="hljs-string">"scala"</span>,
    javaSource in <span class="hljs-type">E2ETest</span> := baseDirectory.value / <span class="hljs-string">"src"</span> / <span class="hljs-string">"e2e"</span> / <span class="hljs-string">"java"</span>,
    resourceDirectory in <span class="hljs-type">E2ETest</span> := baseDirectory.value / <span class="hljs-string">"src"</span> / <span class="hljs-string">"e2e"</span> / <span class="hljs-string">"resources"</span>,
  )
}
</code></pre>
<p>Now apply these settings to your modules and add <code>e2e</code> to the library dependencies as we did with the integration test</p>
<pre><code class="lang-scala"><span class="hljs-keyword">lazy</span> <span class="hljs-keyword">val</span> module1 = (project in file(<span class="hljs-string">"module1"</span>))
  .configs(<span class="hljs-type">IntegrationTest</span>)
  .configs(<span class="hljs-type">E2ETest</span>)
.settings(
  <span class="hljs-type">E2E</span>.e2eSettings,
  <span class="hljs-type">Defaults</span>.itSettings,
  libraryDependencies += scalatest % <span class="hljs-string">"it,test,e2e"</span>,
    name := <span class="hljs-string">"module1"</span>,
  version := <span class="hljs-string">"0.1"</span>
)
<span class="hljs-keyword">lazy</span> <span class="hljs-keyword">val</span> module2 = (project in file(<span class="hljs-string">"module2"</span>))
  .configs(<span class="hljs-type">IntegrationTest</span>)
  .configs(<span class="hljs-type">E2ETest</span>)
  .settings(
    <span class="hljs-type">Defaults</span>.itSettings,
    libraryDependencies += scalatest % <span class="hljs-string">"it,test,e2e"</span>,
      <span class="hljs-type">E2E</span>.e2eSettings,
    name := <span class="hljs-string">"module2"</span>,
    version := <span class="hljs-string">"0.1"</span>
  )
<span class="hljs-keyword">lazy</span> <span class="hljs-keyword">val</span> root = (project in file(<span class="hljs-string">"."</span>))
  .configs(<span class="hljs-type">IntegrationTest</span>)
  .configs(<span class="hljs-type">E2ETest</span>)
  .aggregate(module1, module2)
  .enablePlugins(<span class="hljs-type">Common</span>)
  .settings(
    <span class="hljs-type">E2E</span>.e2eSettings,
    <span class="hljs-type">Defaults</span>.itSettings,
    libraryDependencies += scalatest % <span class="hljs-string">"it,test,e2e"</span>
  )
</code></pre>
<p>we can now create our e2e tests inside the following folders</p>
<pre><code class="lang-vim">src/e2e/scala
src/e2e/java
src/e2e/resources
</code></pre>
<p>To run the e2e test use <code>EndToEndTest / test</code> or <code>e2e:test</code> in sbt. This command runs only the integration test as shown below:</p>
<pre><code class="lang-log">[info] Run completed in 10 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
this is e2e test
[info] Module1E2ETest:
[info] - module1 e2e test
[info] Run completed in 172 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
module2 e2e test
[info] Module2E2ETest:
[info] - module2 e2e test
[info] Run completed in 250 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed 17 Mar, 2019 5:53:46 PM
</code></pre>
<h2 id="heading-unit-test">Unit Test?</h2>
<p>Now, what remains are the unit tests and configuring the unit tests is relatively straightforward. You don't have to do anything at all. 😄 Any test under package <code>src/test/scala</code> gets considered as a unit test and to execute it you have to use your standard <code>test</code> task in sbt, this runs all the unit test in your module:</p>
<pre><code class="lang-log">[info] Compiling 1 Scala source to /Users/neenad/codes/tutorials/scala/sbt-learnings/module1/target/scala-2.11/test-classes ...
[info] Run completed in 9 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
module2 sample unit test
[info] Module2UnitTest:
[info] - sample test
[info] Run completed in 202 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Done compiling.
module1 sample unit test
Hello World
[info] Module1UnitTest:
[info] - sample test
[info] Run completed in 142 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 17 Mar, 2019 6:06:45 PM
</code></pre>
<h2 id="heading-disabling-parallel-execution">Disabling Parallel Execution:</h2>
<p>To disable parallel execution of the e2e test in our code add below line in our E2E settings</p>
<pre><code class="lang-scala"><span class="hljs-type">E2ETest</span> / parallelExecution := <span class="hljs-literal">false</span>
</code></pre>
<h2 id="heading-code">Code:</h2>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="b5bab2423a507d4b38376c51954b4aed"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/ninadingole/b5bab2423a507d4b38376c51954b4aed" class="embed-card">https://gist.github.com/ninadingole/b5bab2423a507d4b38376c51954b4aed</a></div><hr />
<h2 id="heading-resources">Resources</h2>
<ul>
<li><a target="_blank" href="https://www.scala-sbt.org/1.x/docs/">SBT Reference</a></li>
</ul>
<p>I hope this post helps you to organize your tests such that you get faster feedback by running tests independently and don't have to survive the pain I went through in your project. If you have any doubt, please comment and don't forget to share and subscribe to the newsletter for more posts.</p>
]]></content:encoded></item><item><title><![CDATA[The Ultimate Guide To Setup Golden Gate On Vagrant Box]]></title><description><![CDATA[If you have read my previous post on Debezium, I have mentioned that currently, I am working on a platform which includes capturing CDC events from Oracle and publishing those to our new database. For this to work Oracle provides Golden Gate which is...]]></description><link>https://hashnode.iamninad.com/the-ultimate-guide-to-setup-golden-gate-on-vagrant-box</link><guid isPermaLink="true">https://hashnode.iamninad.com/the-ultimate-guide-to-setup-golden-gate-on-vagrant-box</guid><category><![CDATA[Oracle]]></category><category><![CDATA[setup]]></category><category><![CDATA[SQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[golden gate]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Sun, 22 Jul 2018 20:30:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/QhYTCG3CTeI/upload/v1661459265446/InOmFxBMG.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have read my previous post on <a target="_blank" href="https://iamninad.com/how-debezium-kafka-stream-can-help-you-write-cdc/">Debezium</a>, I have mentioned that currently, I am working on a platform which includes capturing CDC events from Oracle and publishing those to our new database. For this to work Oracle provides Golden Gate which is similar to what Debezium does, publishing all database changes to Kafka topics. However, If you are a developer like me who is working on Mac OS and do not have Oracle DB setup for Mac and which is sad 😞 then this post will help you to have your own Oracle Vagrant environment.</p>
<p>While I was working on this new platform development one of my colleagues told me about Oracle having vagrant images which could provide Oracle 12c to developers working on mac. Then I checked and found that Oracle has created a GitHub repo with the vagrant files both for Linux and Oracle 11 and 12c. After that, I basically took this vagrant files as a base for my work and installed golden gate setup along with confluent package on the Linux box. But you know it was a very tedious task to manually do the installation and all. And if I do a big blog with all the steps you guys would be like?</p>
<p><img src="https://media.giphy.com/media/oOTTyHRHj0HYY/giphy.gif" alt /></p>
<p>So, I enhanced the scripts to do the hard work and install Golden Gate + Confluent + enable some configuration for the Oracle database during vagrant box provisioning. And you know what one command does all the setup and configuration for you. Follow the exact given steps in the post and you will be gifted with the magical power of Oracle database, golden gate and confluent. 😂</p>
<p><img src="https://media.giphy.com/media/144waw4kQiQVgY/giphy.gif" alt="happy" /></p>
<h2 id="heading-1-clone-vagrant-box-images">1. Clone Vagrant Box Images</h2>
<p>Clone my oracle vagrant fork from <a target="_blank" href="https://github.com/ninadingole/vagrant-boxes.git">here</a>. Let's wish my pull request get accepted by the oracle contributors team and this work becomes officially a part of Oracle GitHub repository ( finger crossed 🤞🏼).</p>
<h2 id="heading-2-download-oracle-database-zip">2. Download Oracle Database Zip</h2>
<p>Download the Oracle database 12c zip file from oracle tech network <a target="_blank" href="http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html">here</a> or <a target="_blank" href="http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html">here</a>. Use the Oracle Database 12c Release 2 - Linux x86-64 version and download the single file zip which is around 3.2GB.</p>
<h2 id="heading-3-download-oracle-gg-and-oracle-gg-bd">3. Download Oracle GG and Oracle GG BD</h2>
<p>Now, we need the Oracle Golden Gate and Golden Gate for Big Data zip files. These files can be downloaded from <a target="_blank" href="http://www.oracle.com/technetwork/middleware/goldengate/downloads/index.html">here</a>. Download Oracle GoldenGate for Big Data 12.3.1.1.1 on Linux x86-64 &amp; Oracle GoldenGate 12.3.0.1.4 for Oracle on Linux x86-64.</p>
<blockquote>
<p>Keep all the downloaded files in the same vagrant git checkout directory.</p>
</blockquote>
<h2 id="heading-4-start-vagrant">4. Start Vagrant</h2>
<p>At this point, all the pre-requisites are done. Now run <code>vagrant up</code> and this will start installing Oracle database 12c, oracle golden gate, oracle golden gate for big data and confluent oss package on the Linux box.</p>
<p><img src="https://media.giphy.com/media/WZ4M8M2VbauEo/giphy.gif" alt="start" /></p>
<p>Wait till everything finishes. At the very end, the setup will print the password for Oracle database make sure you copy that as this will help you to progress with the next steps. Now, you have your CDC dev environment ready on Mac or on any machine having vagrant. 😎</p>
<p><img src="https://media.giphy.com/media/8UF0EXzsc0Ckg/giphy.gif" alt="done" /></p>
<hr />
<p>If you are new to Oracle golden gate and want to know about configuring the golden gate for Oracle database follow the below steps in the post.</p>
<h2 id="heading-5-configure-golden-gate-on-vagrant-box">5. Configure Golden Gate On Vagrant Box</h2>
<p>There are two things we need to configure, the first is oracle golden gate and then oracle golden gate for big data. Let's begin by first configuring the golden gate for Oracle database. Please follow the exact step to get it running. You can find more information about the following command on oracle golden gate <a target="_blank" href="https://www.oracle.com/technetwork/middleware/goldengate/documentation/index.html">documentation</a>.</p>
<h3 id="heading-1-load-sample-schema-to-oracle-database">1. Load Sample Schema To Oracle Database.</h3>
<p>We need a schema present in the installed oracle database so that we can configure the golden gate to listen to the changes happening on this schema. Well, Oracle setup comes with HR schema SQL files present in the installation. We will load the same into our database.</p>
<blockquote>
<p>use <code>vagrant ssh</code> command to ssh into the virtual machine</p>
</blockquote>
<p>Log in to the database using SQL shell as a sysdba:</p>
<p><code>sqlplus / as sysdba</code></p>
<p>Alter the session to allow Oracle scripts to execute:</p>
<p><code>alter session set "_ORACLE_SCRIPT"=true;</code></p>
<p>Alter the session to use the PDB database (inside SQL shell):</p>
<p><code>alter session set container=ORCLPDB1;</code></p>
<p>Load the schema and for all the input asked by the below command provide proper responses:</p>
<p><code>@?/demo/schema/human_resources/hr_main.sql</code></p>
<p>Execute the below commands as an oracle user, switch the vagrant user to Oracle using</p>
<p><code>sudo su - oracle</code></p>
<h3 id="heading-2-configure-oracle-golden-gate">2. Configure Oracle Golden Gate</h3>
<ol>
<li><p>Go to Oracle golden gate installation directory. <code>cd /u01/ogg</code></p>
</li>
<li><p>Open golden gate console. <code>./ggsci</code></p>
</li>
<li><p>Start manager. <code>&gt; start mgr</code></p>
</li>
<li><p>Log in to the database. <code>&gt; DBLOGIN USERID SYSTEM@localhost:1521/ORCLCDB PASSWORD [password copied while installation]</code></p>
</li>
<li><p>Register Extract. <code>&gt; REGISTER EXTRACT EXT1 DATABASE CONTAINER (ORCLPDB1)</code></p>
</li>
<li><p>Enable schema-level supplemental logging for a table. <code>&gt; ADD SCHEMATRANDATA ORCLPDB1.HR ALLCOLS</code></p>
</li>
<li><p>Create an Extract group. <code>&gt; ADD EXTRACT EXT1, INTEGRATED TRANLOG, BEGIN NOW</code></p>
</li>
<li><p>Create a trail for online processing on the local system and Associate it with an Extract group. <code>&gt; ADD EXTTRAIL ./dirdat/lt EXTRACT EXT1</code></p>
</li>
<li><p>Create EXT1 parameter file and paste the content in the file. <code>&gt; EDIT PARAM EXT1</code></p>
</li>
</ol>
<pre><code class="lang-go">EXTRACT EXT1
USERID SYSTEM@ORCLCDB, PASSWORD [password copied during installation]
EXTTRAIL ./dirdat/lt
SOURCECATALOG ORCLPDB1
TABLE HR.*;
</code></pre>
<ol>
<li><p>Start Extract EXT1. <code>&gt; start ext1</code></p>
</li>
<li><p>View status of manager and ext1. <code>&gt; info all</code></p>
</li>
</ol>
<pre><code class="lang-go">Program     Status      Group       Lag at Chkpt  Time Since Chkpt
MANAGER     RUNNING
EXTRACT     RUNNING     EXT1        <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span>      <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span>
</code></pre>
<p>The Oracle golden gate for Oracle 12c is configured. Now, its time to configure Oracle golden gate for big data which will push all the changes/ CDC events to confluent Kafka.</p>
<h3 id="heading-3-configure-oracle-golden-gate-for-big-data">3. Configure Oracle Golden Gate For Big Data</h3>
<p>Before following the below steps make sure you add java path to <code>$LD_LIBRARY_PATH</code> environment variable in your <code>.bashrc</code> file. The location of the java is <code>/usr/lib/jvm/java-1.8.0-openjdk-XXXXX/jre/lib/amd64/server</code>. Replace XXXXX with your current version installed on during the installation.</p>
<ol>
<li><p>Change directory to oggbd <code>cd /u01/oggbd</code></p>
</li>
<li><p>Open golden gate for bd console. <code>./ggsci</code></p>
</li>
<li><p>Create default directories. <code>&gt; CREATE SUBDIRS</code> After doing this exit the console using <code>exit</code> command, and create the following files in <code>dirprm</code> directory.</p>
</li>
</ol>
<ul>
<li><p>kafkaconnect.properties</p>
</li>
<li><p>rkafka_handler.props</p>
</li>
<li><p>rkafka.prm</p>
</li>
</ul>
<ol>
<li><p>Login to GG console again using step <code>2</code> then execute the below command and add the contents to the param file <code>&gt; edit param mgr</code></p>
<pre><code class="lang-go"> PORT <span class="hljs-number">7801</span>
</code></pre>
</li>
<li><p>Start the manager. <code>&gt; start mgr</code></p>
</li>
<li><p>Create replicat group <code>rkafka</code>. <code>&gt; add replicat rkafka, exttrail /u01/ogg/dirdat/lt</code></p>
</li>
<li><p>Start the replicat. <code>&gt; start rkafka</code></p>
</li>
<li><p>View status of all if everything is properly configured. <code>&gt; info all</code></p>
<pre><code class="lang-go"> Program     Status      Group       Lag at Chkpt  Time Since Chkpt
 MANAGER     RUNNING
 REPLICAT    RUNNING     RKAFKA      <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span>      <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">09</span>
</code></pre>
</li>
</ol>
<h3 id="heading-4-verify-setup">4. Verify Setup</h3>
<ol>
<li><p>Login to the database and add row to any table like:</p>
<pre><code class="lang-sql"> &gt; sqlplus / as sysdba
 SQL&gt; <span class="hljs-keyword">alter</span> <span class="hljs-keyword">session</span> <span class="hljs-keyword">set</span> <span class="hljs-keyword">container</span>=ORCLPDB1;
 SQL&gt; <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> HR.REGIONS(REGION_ID, REGION_NAME) <span class="hljs-keyword">VALUES</span>(<span class="hljs-number">47</span>, <span class="hljs-string">'FOO'</span>);
 SQL&gt; <span class="hljs-keyword">COMMIT</span>;
</code></pre>
</li>
<li><p>Check if the kafka topics are created for the data inserted tables <code>&gt; kafka-topics --zookeeper localhost:2181 --list</code></p>
<pre><code class="lang-go"> __confluent.support.metrics
 __consumer_offsets
 _confluent-ksql-default__command_topic
 _schemas
 connect-configs
 connect-offsets
 connect-statuses
 ora-ogg-HR-REGIONS-avro
</code></pre>
</li>
<li><p>To check the data in the kafka topics run - <code>&gt; kafka-avro-console-consumer --bootstrap-server localhost:9092 --topic ora-ogg-HR-REGIONS-avro --from-beginning</code></p>
</li>
</ol>
<p>You can run both <code>kafka-topics</code> and <code>kafka-avro-console-consumer</code> from the host machine where you are running vagrant. This will help you while coding to connect to Kafka instance present in the vagrant vm. Also, The Oracle database is running on port 1521 to which you can connect from your java code or from your IDE.</p>
<p><img src="https://media.giphy.com/media/MTclfCr4tVgis/giphy.gif" alt="done" /></p>
<p>This vagrant images is really helpful to me and hope it will also help you guys while developing your CDC related applications. For any issues, during setup or configuring please do comment on the post.</p>
<h4 id="heading-other-information">Other Information:</h4>
<ul>
<li><p>Zookeeper Port: 2181</p>
</li>
<li><p>Kafka Broker: 9092</p>
</li>
<li><p>Confluent Schema Registry: 8081</p>
</li>
<li><p>Oracle DB Port: 1521</p>
</li>
</ul>
<p>Don't forget to share the post with your friends and colleagues.</p>
<hr />
<h2 id="heading-references">References:</h2>
<ol>
<li><p><a target="_blank" href="https://github.com/oracle/vagrant-boxes">Oracle Repository</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ninadingole/vagrant-boxes.git">Own Personal Project Repository</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Running Spark Job On Minikube Kubernetes Cluster]]></title><description><![CDATA[Kubernetes is another industry buzzword these days and I am trying a few different things with Kubernetes. On Feb 28th, 2018 Apache spark released v2.3.0, I am already working on Apache Spark and the new release has added a new Kubernetes scheduler b...]]></description><link>https://hashnode.iamninad.com/running-spark-job-on-minikube-kubernetes-cluster</link><guid isPermaLink="true">https://hashnode.iamninad.com/running-spark-job-on-minikube-kubernetes-cluster</guid><category><![CDATA[#apache-spark]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[development]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Neenad Ingole]]></dc:creator><pubDate>Sat, 10 Mar 2018 11:10:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/AljDaiCbCVY/upload/v1661768045098/n0MH468Lnw.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kubernetes is another industry buzzword these days and I am trying a few different things with Kubernetes. On Feb 28th, 2018 Apache spark released v2.3.0, I am already working on Apache Spark and the new release has added a new Kubernetes scheduler backend that supports native submission of spark jobs to a cluster managed by Kubernetes. The feature is still currently experimental but I wanted to try it out.</p>
<p>I am writing this post because I have to search a lot of things to get it working and there is no single place to get the exact information on how to run a Spark job on a local Kubernetes cluster. I thought writing a post on my findings will enable others not to go through the same pain as I did.</p>
<p>In this post, we will take a look at how to set up a Kubernetes cluster using minikube on a local machine and how to run an Apache spark job on top of it. We are not going to write any code for this, a few examples are already available within the Apache Spark distribution and we are going to use the same example jar and run the <code>SparkPi</code> program from it onto our Kubernetes cluster.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before we start please make sure you have the following prerequisites installed on your local machine.</p>
<blockquote>
<p>I am running the tools on Mac High Sierra.</p>
</blockquote>
<ul>
<li><p>Apache Spark (v2.3.0)</p>
</li>
<li><p>Kubernetes (v.1.9.3)</p>
</li>
<li><p>Docker</p>
</li>
<li><p>Minikube</p>
</li>
<li><p>VirtualBox</p>
</li>
</ul>
<h3 id="heading-start-minikube">Start Minikube</h3>
<p>We can start minikube locally by simply running the below command.</p>
<pre><code class="lang-console">&gt; minikube start
Starting local Kubernetes v1.9.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
</code></pre>
<p>Minikube is a tool that makes it easy to run Kubernetes locally. Minikube runs a single-node Kubernetes cluster inside a VM on a local machine. It enables us to try out Kubernetes or develop it day-to-day.</p>
<p>When minikube starts it starts with a single-node configuration and takes <code>1Gb</code> of Memory and <code>2</code> cores of CPU by default, however, for running spark this requirement will not suffice and the jobs will fail (and they do as I have tried multiple times) so we have to increase both Memory and CPU cores for our minikube cluster.</p>
<p>There are <strong>three</strong> different ways to do this. However, for me not all worked and that’s how I came to know about the different ways to change the minikube configuration. You can use any one of the below ways to change the configuration.</p>
<h3 id="heading-pass-configuration-to-minikube-start-command">Pass Configuration To Minikube Start Command</h3>
<p>You can directly pass the memory and CPU options to the minikube start command like:</p>
<pre><code class="lang-console">&gt; minikube start --memory 8192 --cpus 4
Starting local Kubernetes v1.9.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
</code></pre>
<p>This will start a minikube cluster with 8Gb of memory and 4 cores of CPU. (somehow for me this didn't work)</p>
<h2 id="heading-modify-minikube-config">Modify minikube config</h2>
<p>We can change the minikube config with the <code>config</code> command in minikube cli. The <code>config</code> command allows setting different config options for minikube like memory, CPUs, vm-driver, disk-size etc. To see all the available options use the<code>&gt; minikube config</code> command it will list all the available options that can be modified.</p>
<pre><code class="lang-console">&gt; minikube config set memory 8192

## These changes will take effect upon a minikube delete and then a minikube start
</code></pre>
<pre><code class="lang-console">&gt; minikube config set cpus 4

## These changes will take effect upon a minikube delete and then a minikube start
</code></pre>
<p>After setting the configuration using the <code>config</code> command we need to delete our previous running cluster and start a new one. To delete minikube cluster run <code>&gt; minikube delete</code> and rerun the start minikube command.</p>
<h2 id="heading-change-configuration-using-virtualbox">Change Configuration Using VirtualBox</h2>
<p>For me, the above two options didn't work and if you are like me you can use this last option which worked for me and hope it works for you. Open the VirtualBox app on your machine and select your VM like the one shown in the below image. <code>RightClick -&gt; Settings</code> on your VM, this will open the configuration page for the Minikube VM.</p>
<blockquote>
<p>note: To change configuration using VirtualBox first you need to shutdown the VM if it is already running.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661767529708/n4boCjrfO.png" alt /></p>
<p>Inside option <code>System -&gt; Motherboard</code>, you can change the memory of the VM using the slider, in my case I have given it <code>8192MB</code> of memory.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661767562446/Pz6fn5KEl.png" alt /></p>
<p>To change the CPU config, go to <code>Processor</code> tab and change it to <code>4</code>. You can try other options also for me 4 works just fine. Don’t change it to less than 2 otherwise things won’t work. :p</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661767589859/I7MN-v2y1.png" alt /></p>
<p>After the configuration changes are done, start the VM using the VirtualBox app or using the above given <code>minikube start</code> command.</p>
<p>Your cluster is running and now you need to have a docker image for spark. Let's see how to build the image next.</p>
<h2 id="heading-creating-docker-image-for-spark">Creating Docker Image For Spark</h2>
<p>Make sure you have Docker installed on your machine and the spark distribution is extracted.</p>
<p>Go inside your extracted spark folder and run the below command to create a spark docker image</p>
<blockquote>
<p>some of the output logs are excluded.</p>
</blockquote>
<pre><code class="lang-console">&gt; ./bin/docker-image-tool.sh -m -t spark-docker build
./bin/docker-image-tool.sh: line 125: ================================================================================: command not found
Sending build context to Docker daemon  256.5MB
Step 1/16 : FROM openjdk:8-alpine
 ---&gt; 224765a6bdbe
Step 2/16 : ARG spark_jars=jars
 ---&gt; Using cache
 ---&gt; dd42fdc28d7a
Step 3/16 : ARG img_path=kubernetes/dockerfiles
 ---&gt; Using cache
 ---&gt; 570fc343f883
Step 4/16 : RUN set -ex &amp;&amp;     apk upgrade --no-cache &amp;&amp;     apk add --no-cache bash tini libc6-compat &amp;&amp;     mkdir -p /opt/spark &amp;&amp;     mkdir -p /opt/spark/work-dir     touch /opt/spark/RELEASE &amp;&amp;     rm /bin/sh &amp;&amp;     ln -sv /bin/bash /bin/sh &amp;&amp;     chgrp root /etc/passwd &amp;&amp; chmod ug+rw /etc/passwd
 ---&gt; Using cache
 ---&gt; 705ae89cb075
Step 5/16 : COPY ${spark_jars} /opt/spark/jars
 ---&gt; Using cache
 ---&gt; 506ab8c02abf
Step 9/16 : COPY ${img_path}/spark/entrypoint.sh /opt/
 ---&gt; Using cache
 ---&gt; 03959bfa5250
Step 10/16 : COPY examples /opt/spark/examples
 ---&gt; Using cache
 ---&gt; 5a2f91a7ce3e
Step 11/16 : COPY data /opt/spark/data
 ---&gt; Using cache
 ---&gt; 58090cef2be4
Step 13/16 : RUN echo $(ls /opt/spark/examples/jars/)
 ---&gt; Using cache
 ---&gt; 0a6c27628ac7
Step 14/16 : ENV SPARK_HOME /opt/spark
 ---&gt; Using cache
 ---&gt; 7f32a22e0196
Step 16/16 : ENTRYPOINT [ "/opt/entrypoint.sh" ]
 ---&gt; Using cache
 ---&gt; 01daf3302719
Successfully built 01daf3302719
Successfully tagged spark:spark-docker

&gt; docker image ls
REPOSITORY                                                TAG                 IMAGE ID            CREATED             SIZE
spark-docker                                              v0.1                01daf3302719        2 days ago          347MB
</code></pre>
<p>Now if you run <code>&gt; docker image ls</code> you will see the docker build available on your local machine. Make a note of this image name we need to provide the image name to <code>spark-submit</code> command.</p>
<p>There is a <code>push</code> option available to the above command which enables you to push the docker image to your own repository this, in turn, will enable your production kubernetes to pull the docker image from the configured Docker repository. Run the same command without any options to see its usage.</p>
<p>It might happen that the command will not work and will give an error like:</p>
<pre><code class="lang-console">./bin/docker-image-tool.sh: line 125: ================================================================================: command not found
“docker build” requires exactly 1 argument.
See ‘docker build — help’.
Usage: docker build [OPTIONS] PATH | URL | — [flags]
Build an image from a Dockerfile
</code></pre>
<p>This is because of an issue with the <code>docker-image-tool.sh</code> file. I have raised a bug for this in Apache Spark JIRA you can see it <a target="_blank" href="https://issues.apache.org/jira/browse/SPARK-23618">here</a>.</p>
<p>The issue is under fix but for you to continue with this post what you can do is open the <code>docker-image-tool.sh</code> file present inside the <code>bin</code> folder and after line no <code>59</code> add <code>BUILD_ARGS=()</code>, save the file and run the command once again and it will work.</p>
<p>For those who the above command worked without the workaround the issue might have been fixed at the time you are reading this post and you don't have to do anything hurrah!!!</p>
<h2 id="heading-submit-spark-job">Submit Spark Job</h2>
<p>Now, let's submit our <code>SparkPi</code> job to the cluster. Our cluster is ready and we have the docker image. Run the below command to submit the spark job on a kubernetes cluster. The <code>spark-submit</code> script takes care of setting up the classpath with Spark and its dependencies and can support different cluster managers and deploy modes that Spark supports</p>
<pre><code class="lang-console">&gt; spark-submit \                                      
--master k8s://https://192.168.99.100:8443 \
--deploy-mode cluster \
--name spark-pi \
--class org.apache.spark.examples.SparkPi \
--conf spark.executor.instances=3 \
--conf spark.kubernetes.container.image=spark-docker \
local:///opt/spark/examples/jars/spark-examples_2.11-2.3.0.jar
</code></pre>
<p>options used to run on Kubernetes are:</p>
<ul>
<li><p><code>--class</code>: The entry point for your application (e.g. org.apache.spark.examples.SparkPi)</p>
</li>
<li><p><code>--master</code>: The master URL for the Kubernetes cluster (e.g. k8s://https://192.168.99.100:8443)</p>
</li>
<li><p><code>--deploy-mode</code>: Whether to deploy your driver on the worker nodes (cluster) or locally as an external client (client) (default: client)</p>
</li>
<li><p><code>--conf spark.executor.instances=3</code>: configuration property to specify how many executor instances to use while running the spark job.</p>
</li>
<li><p><code>--conf spark.kubernetes.container.image=spark-docker</code>: Configuration property to specify which docker image to use, here provide the same docker name from <code>docker image ls</code> command.</p>
</li>
<li><p><code>local:///opt/spark/examples/jars/spark-examples_2.11-2.3.0.jar</code>: Path to a bundled jar including your application and all dependencies. The URL must be globally visible inside of your cluster, for instance, an hdfs:// path or a local:// path that is present on all nodes</p>
</li>
</ul>
<p>The package jar should be available cluster-wide either through HDFS, or HTTP or should be available within every packaged docker image so that it will be available to all the executor nodes in our spark program. The <code>local://</code> address means that the jar is available as a local file on every initialized pod by the spark driver and no IO has to be made to pull the jar from anywhere, this works well when the application jar is large and is pushed to every worker node or shared using any shared filesystem.</p>
<p>Luckily, our spark docker image file packages the example jar in the docker container so we can use it. However, how to package our own application code and push it either on HDFS or as a separate docker image I will write as a separate post.</p>
<p>To check if the pods are started and the spark job is running, open the Kubernetes dashboard available within minikube.</p>
<h2 id="heading-view-minikube-dashboard-andamp-kubernetes-logs">View Minikube Dashboard &amp; Kubernetes Logs</h2>
<p>To check the status of our submitted job we can use either the Kubernetes dashboard or view Kubernetes logs. Minikube comes with the dashboard available as an add-on and can be started using the following command.</p>
<pre><code class="lang-console">&gt; minikube dashboard
Opening kubernetes dashboard in default browser...

OR

&gt; minikube service list
|-------------|------------------------------------------------------|--------------------------------|
|  NAMESPACE  |                         NAME                         |              URL               |
|-------------|------------------------------------------------------|--------------------------------|
| default     | kubernetes                                           | No node port                   |
| default     | neo4j                                                | No node port                   |
| default     | neo4j-public                                         | http://192.168.99.100:30388    |
|             |                                                      | http://192.168.99.100:30598    |
| default     | spark-pi-709e1c1b19813e7cbc1aeff45200c64e-driver-svc | No node port                   |
| kube-system | kube-dns                                             | No node port                   |
| kube-system | kubernetes-dashboard                                 | http://192.168.99.100:30000    |
|-------------|------------------------------------------------------|--------------------------------|
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661767738231/nsCGxB2CP.png" alt="Kubernetes Dashboard" /></p>
<p>Navigate to the URL given by the above command to view the dashboard. The dashboard provides lots of information about cluster memory usage, CPU usage, pods, services, replica set etc. We can also view service logs directly through the dashboard. However, if you don't want to go to the dashboard you can view the Spark Driver log using the<code>&gt; kubectl logs &lt;pod name&gt;</code> command:</p>
<pre><code class="lang-console">&gt; kubectl logs spark-pi-709e1c1b19813e7cbc1aeff45200c64e-driver 
.....
2018-03-07 13:10:35 INFO  TaskSchedulerImpl:54 - Removed TaskSet 0.0, whose tasks have all completed, from pool 
2018-03-07 13:10:35 INFO  DAGScheduler:54 - Job 0 finished: reduce at SparkPi.scala:38, took 1.091617 s
Pi is roughly 3.1389956949784747
2018-03-07 13:10:35 INFO  AbstractConnector:318 - Stopped Spark@53e211ee{HTTP/1.1,[http/1.1]}{0.0.0.0:4040}
2018-03-07 13:10:35 INFO  SparkUI:54 - Stopped Spark web UI at http://spark-pi-709e1c1b19813e7cbc1aeff45200c64e-driver-svc.default.svc:4040
2018-03-07 13:10:35 INFO  KubernetesClusterSchedulerBackend:54 - Shutting down all executors
2018-03-07 13:10:35 INFO  KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint:54 - Asking each executor to shut down
2018-03-07 13:10:35 INFO  KubernetesClusterSchedulerBackend:54 - Closing kubernetes client
2018-03-07 13:10:35 INFO  MapOutputTrackerMasterEndpoint:54 - MapOutputTrackerMasterEndpoint stopped!
2018-03-07 13:10:35 INFO  MemoryStore:54 - MemoryStore cleared
2018-03-07 13:10:35 INFO  BlockManager:54 - BlockManager stopped
2018-03-07 13:10:35 INFO  BlockManagerMaster:54 - BlockManagerMaster stopped
2018-03-07 13:10:35 INFO  OutputCommitCoordinator$OutputCommitCoordinatorEndpoint:54 - OutputCommitCoordinator stopped!
2018-03-07 13:10:35 INFO  SparkContext:54 - Successfully stopped SparkContext
2018-03-07 13:10:35 INFO  ShutdownHookManager:54 - Shutdown hook called
......
</code></pre>
<p>If you see in the logs the program has calculated the Pi value and the container is stopped in the end.</p>
<h2 id="heading-shutdown-cluster">Shutdown Cluster</h2>
<p>Shutting down the cluster is very easy, use <code>&gt; minikube stop</code> and it will stop the cluster.</p>
<hr />
<p>Hope this helps you try running apache spark on the local Kubernetes cluster. Please do comment if you didn’t understand any steps or get any errors while following the steps and I will try to add more details to the post. Also, please do let me know if you liked it and help others by sharing.</p>
<p>Happy Coding...</p>
<h2 id="heading-references">References</h2>
<ol>
<li><p><a target="_blank" href="https://spark.apache.org/docs/latest/running-on-kubernetes.html">Apache Spark Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kubernetes/minikube">Minikube Github</a></p>
</li>
</ol>
]]></content:encoded></item></channel></rss>