<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: calvinsadewa</title>
    <description>The latest articles on DEV Community by calvinsadewa (@calvinsadewa).</description>
    <link>https://dev.to/calvinsadewa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F332286%2F5b816704-b1bd-475c-90b6-5a9c1dbe2b74.png</url>
      <title>DEV Community: calvinsadewa</title>
      <link>https://dev.to/calvinsadewa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/calvinsadewa"/>
    <language>en</language>
    <item>
      <title>Case Study: Reducing toil of resolving issue in Node JS - Introduction</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Tue, 16 Apr 2024 07:33:38 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/case-study-reducing-toil-of-resolving-issue-in-node-js-introduction-4g7f</link>
      <guid>https://dev.to/calvinsadewa/case-study-reducing-toil-of-resolving-issue-in-node-js-introduction-4g7f</guid>
      <description>&lt;p&gt;Hi there! Welcome to the first of a four-part journey where I share the transformation we achieved in addressing and resolving issues within our Node.js projects at Kargo. This adventure led us to dramatically reduce the time and effort required to tackle these challenges head-on.&lt;/p&gt;

&lt;p&gt;In this opening segment, we delve into the core problems that initially confronted us and outline the innovative solutions we proposed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Some time ago, the Tech team at Kargo embarked on an interesting project: to build a new Node.js backend service designed to support a feature requiring specific integration with a third-party platform. This venture presented a unique challenge for Kargo's engineers, as NodeJS backend was a new addition to our technology stack, which usually was based on Elixir or Go. Despite the initial hardships stemming from our unfamiliarity with this tech stack, we persevered, successfully delivering the required functionality. The project not only met but exceeded our success metrics, leading to a well-deserved celebration among the team.&lt;/p&gt;

&lt;p&gt;However, as time progressed, the project's scope expanded, and with it, an increase in operational issues and bugs became apparent. The time required to resolve these issues began to increase significantly. On average, it took the team approximately four hours to address each problem, with around three issues arising per week. This not only led to considerable time loss but also began to affect team productivity adversely. More importantly, the user experience and the product's overall success suffered, as frequent issues and extended downtime became more common.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs0rybo69of1xaizqrhz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs0rybo69of1xaizqrhz.gif" alt="Frustated Engineer" width="220" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnosis
&lt;/h3&gt;

&lt;p&gt;After some investigation, the NodeJS project lack observability &amp;amp; debugging tools that we have in usual Kargo's backend service (Elixir/Go). This result in increased effort across maintenance lifecycle (Detect, Diagnose, Debug). To illustrate, here's an hypothetical example of how a particular issue is resolved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Issue detection:&lt;/strong&gt;
The team received a report from user that the feature is not working as expected. An engineer are assigned to investigate the issue. There are no dashboard for the project, so the engineer usually need to look at relevant code for impacted feature, then look at log file, custom query to database, and other ad-hoc action for confirming the issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue diagnosis:&lt;/strong&gt;
To determine what's the most likely cause of the issue, the engineer need to be familiar with codebase involved. Occasionally, they are lucky and there's log that's explicit enough to point out the issue. But most of the time, they need to add more log into codebase then redeploy it to get more information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue debugging:&lt;/strong&gt;
After the engineer have some idea on what's the issue, though it's not usually 100% certain. They would implement probable fix, then redeploy the code through CI/CD. If the issue still persist, they would need to repeat the process again. Usually it takes 3 to 4 retry before the issue is resolved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ka65aeql0k580gcfguw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ka65aeql0k580gcfguw.gif" alt="Let's fix this" width="220" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;We brainstorm on how to improve the situation, also looking at what the tools &amp;amp; technique we have in Elixir/Go backend service that might help in this situation. We come up following solutions that we think will help addressing problem in each of the stage (Detect, Diagnose, Debug):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring Dashboard + Canonical Log&lt;/strong&gt;:
By implementing Action-oriented Dashboard, we've created a quick and centralized access point for vital aspects of the project, significantly speeding up our ability to detect and validate issues. This dashboard, built on a Canonical Log record approach, offers the flexibility needed for performing custom advanced analyses with ease. Our aim with this system is to verify common issues within a mere 5 minutes of engineering effort. For example, we successfully reduced the time to check for a frequent session connection issue from 20 minutes to just 1 minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Demand Diagnostic Logging&lt;/strong&gt;:
We developed a feature for dynamic, detailed logging of operations, enabling us to drill down into issues as they happened. This was particularly effective in a recent incident where typical logs were inconclusive. By enabling on-demand logging, we pinpointed the problem within minutes, a process that could have otherwise taken multiple redeployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Code Execution&lt;/strong&gt;:
Just like how Elixir's remote shell capability help Kargo's engineer rapidly diagnosing &amp;amp; debug for issue happened in backend, we would like to have similar capability in NodeJS project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational Handbook&lt;/strong&gt;:
We compiled a comprehensive handbook detailing common issues and their solutions, which served as a first reference point for our engineers. One memorable success story involved a team member who are relatively new to the project, was able to resolve an issue independently by following the handbook when the usual maintainer taking day off. This not only make issue resolving more efficient, but also give assurance to engineer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbk3nvknaq03f23r3yyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbk3nvknaq03f23r3yyk.png" alt="Screenshot of Grafana Dashboard" width="800" height="441"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Action-oriented dashboard help engineer quickly understand &amp;amp; troubleshoot issue and decide what's the fix&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ideal Outcome
&lt;/h3&gt;

&lt;p&gt;We target we were able to reduce the time needed to resolve issue from 4 hour to 30 minutes. Thus reducing the impact of issue to user experience and product success, and help the team to focus on delivering more value to the user.&lt;/p&gt;

&lt;p&gt;An ideal scenario of how the issue resolved in NodeJS project looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Issue detection&lt;/strong&gt;&lt;br&gt;
For 50% common issue, the issue would be automatically detected and alerted from metric dashboard. The engineer could easily validate the issue exist by looking at metric dashboard and resolve the issue by following the operational handbook.&lt;br&gt;
For the rest of 50% issue, engineer could do analysis based on dashboard + extended query from canonical log to confirm the issue and it's scope. If the issue is persistent, engineer could easily create new metric dashboard for the issue from canonical log data. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Issue diagnosis&lt;/strong&gt;&lt;br&gt;
When there’s an unknown issue, engineer could turn on diagnostic logging for user they are interested in (based on user ID) and get relevant debug log of WhatsApp bot server to form reasonable guess on where’s the issue is. Then engineer could use developer remote execution capability to validate whether the guess is correct or not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Issue debugging&lt;/strong&gt;&lt;br&gt;
Once root cause of issue is identified, engineer could implement fix to the solution, then test it out first using developer remote execution. Once it's confirmed then engineer could deploy the fix through CI/CD.&lt;br&gt;
Now, because root cause of issue is well identified, engineer could resolve the issue in just one try.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;That's the background of the problem that we are facing, and next write up will be focusing on how we implement each of the solution that we proposed.&lt;/p&gt;

&lt;p&gt;What do you do to make sure your team is able to resolve issue quickly? Share your experience in the comment below!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4ukvegv9huoqepweti9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4ukvegv9huoqepweti9.gif" alt="Stay tuned for next part" width="200" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Does high performing organization doesn't care about best practice / architecture?</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Wed, 29 Dec 2021 07:19:56 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/does-high-performing-organization-doesnt-care-about-best-practice-architecture-4io3</link>
      <guid>https://dev.to/calvinsadewa/does-high-performing-organization-doesnt-care-about-best-practice-architecture-4io3</guid>
      <description>&lt;p&gt;Hi all, recently i read Puppet State of Devops 2021 report &lt;a href="https://puppet.com/resources/report/2021-state-of-devops-report"&gt;link&lt;/a&gt;. Beside gaining a lot of insight from the report, i am also fascinated by a very interesting chart about aggregate team composition for low vs medium vs high performing organization from one of the slide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bmoNQYqi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6iq6z077d3vqmhglcjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bmoNQYqi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6iq6z077d3vqmhglcjc.png" alt="Teams Composition Chart" width="880" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beside telling the usual story about high DevOps evolution characteristic like high performing team invest a lot in automating infrastructure / internal platform or low performing team has significant silo between developer &amp;amp; operation team, i also notice something different.&lt;/p&gt;

&lt;p&gt;High performing team has less team composition which emphasis is on architecture or best practice, and has higher team composition which focus on providing reusable assets (tools, library). Below is subset of related statistic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Teams&lt;/th&gt;
&lt;th&gt;Low&lt;/th&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;th&gt;High&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A team that creates reusable assets (e.g., libraries, tools, or services) for other teams to assemble into solutions&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A team that defines the standards, processes, practices, frameworks or architectures that other teams must follow&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A team whose primary mission is to help define and encourage the adoption of good practices by other teams&lt;/td&gt;
&lt;td&gt;7%&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If understood naively, these statistics would imply that high performing organization doesn't invest much in architecture or best practice, and invest more in tooling/library, hence the click-bait title of this post. It would mean that high performing organization doesn't invest much on DevOps Center of Excellence.&lt;/p&gt;

&lt;p&gt;Of course, this naive conclusion is wrong when we look at example of high DevOps evolution company, like Google or Netflix, as frequently they have exemplar best practice (like code stylesheet, or coding practice) and deals with really complicated architecture, like highly available, fault tolerant, zero trust security.&lt;/p&gt;

&lt;p&gt;Then maybe, just thinking out loud, there is other reason that this statistic happen. maybe it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High DevOps organization thought that centralized best practice / architecture is slow or ineffective, and prefer shared / decentralized approach to it like frequent pair programming&lt;/li&gt;
&lt;li&gt;Hiring &amp;amp; Screening of Engineers, FANG companies is notorious for their screening of engineers to have high standard on best practice / architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these reasons is just hypothesis from me, i want to understand what's the actual difference or reason of this discrepancy, could anyone with relevant experience (i.e has work experience in high devops evolution team before) share their thought / experience on this issue?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>discuss</category>
      <category>organization</category>
    </item>
    <item>
      <title>Uncertainty increase development effort </title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Thu, 19 Aug 2021 08:26:28 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/uncertainty-increase-development-effort-1k3a</link>
      <guid>https://dev.to/calvinsadewa/uncertainty-increase-development-effort-1k3a</guid>
      <description>&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Currently i am working in a sprint-based squad, and recently at work i have been involved in quarterly planning on how to schedule sprints for squads for the rest of the quarter. You might think this is tedious process, but it is helpful in term of coordinating effort between multiple division, so that other division. &lt;/p&gt;

&lt;p&gt;One of the problem we encounter is translating from estimated story point of each effort, to a precise schedule on when effort would be finished. We could do this by making translation between Story Point to Man-Days, and then from each Man-Days to precise schedule according to each squad composition.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Story Point&lt;/th&gt;
&lt;th&gt;Man-days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Uncertain Estimation
&lt;/h3&gt;

&lt;p&gt;We can say uncertainty in estimation as chance for an estimation to deviate from original estimation. Like 10 % chance for story point to go up or down from original estimation.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pb-G5uoT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e64tdlkjynpgkujoimp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pb-G5uoT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e64tdlkjynpgkujoimp2.png" alt="Infographic 10%"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How would this uncertainty effect development effort? Turn out it generally increase effort due to higher story point generally take longer time to finish than lower story point.&lt;/p&gt;

&lt;p&gt;For example, let's take estimation of a task as &lt;code&gt;8&lt;/code&gt; with 10% chance ± &lt;code&gt;5&lt;/code&gt; story point. Expected Man-Days for the task to finish would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;80% * Man-Days(8 SP) + 10% * Man-Days(3 SP) + 10% * Man-Days(13 SP)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;80% * 1 + 10% * 0.25 + 10% * 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;1.025 Man-Days, which is higher than perfectly certain Man-Days(8 SP) = 1&lt;/li&gt;
&lt;li&gt;Hence uncertainty increase effort, even though Average expected complexity still same.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Effect of Higher Uncertainty toward increased effort
&lt;/h3&gt;

&lt;p&gt;We could also label these chances of change and calculate expected increase of effort, like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Chances&lt;/th&gt;
&lt;th&gt;Avg Increase of effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Perfectly certain&lt;/td&gt;
&lt;td&gt;0% chance of change&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certain&lt;/td&gt;
&lt;td&gt;10% chance of 1 level of change&lt;/td&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uncertain&lt;/td&gt;
&lt;td&gt;20% chance of 1 level of change, 5% chance of 2 level of change&lt;/td&gt;
&lt;td&gt;12.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highly Uncertain&lt;/td&gt;
&lt;td&gt;25% chance of 1 level of change, 10% chance of 2 level of change&lt;/td&gt;
&lt;td&gt;27.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Afternote
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;This article is inspired this talk about statistical consequence of fat tail: &lt;a href="https://www.youtube.com/watch?v=_8j1XZ0N_wE"&gt;https://www.youtube.com/watch?v=_8j1XZ0N_wE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I am using normal distribution here, giving equal chance for story point to be added and to be reduced. Given well recorded tendency for software developer to underestimate, probably log normal distribution would be better&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Detail of calculations for Higher Uncertainty vs Effort
&lt;/h4&gt;

&lt;p&gt;In which 1 level change is based of Fibonacci change, &lt;code&gt;F(N+1) - F(N) = F(N-1)&lt;/code&gt; which for 8 Story Point it is 5 Story Point.&lt;br&gt;
2 level change is &lt;code&gt;F(N+2) - F(N) = F(N+1)&lt;/code&gt;, which for 8 SP it is 13(!) SP, up and down (for down, it would make it goes to -5, for Manday we will treat it as negative Man-Day(5)).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Story Point&lt;/th&gt;
&lt;th&gt;Perfectly Certain MD&lt;/th&gt;
&lt;th&gt;Certain MD&lt;/th&gt;
&lt;th&gt;Uncertain MD&lt;/th&gt;
&lt;th&gt;Highly Uncertain MD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.51&lt;/td&gt;
&lt;td&gt;0.545&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.025&lt;/td&gt;
&lt;td&gt;1.125&lt;/td&gt;
&lt;td&gt;1.275&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2.05&lt;/td&gt;
&lt;td&gt;2.25&lt;/td&gt;
&lt;td&gt;2.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;4.5&lt;/td&gt;
&lt;td&gt;5.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>estimation</category>
      <category>productivity</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Hacking Ecto: Constructing Ecto Query from Raw SQL</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Wed, 13 Jan 2021 13:24:40 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/hacking-ecto-constructing-ecto-query-from-raw-sql-1m8e</link>
      <guid>https://dev.to/calvinsadewa/hacking-ecto-constructing-ecto-query-from-raw-sql-1m8e</guid>
      <description>&lt;p&gt;Hi all, here's sharing "hack" tips for constructing &lt;code&gt;Ecto.Query&lt;/code&gt; from Raw SQL. You would want to do this in case you want all good feature of &lt;code&gt;Ecto.Query&lt;/code&gt; but want to bypass safeguard put in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  A bit of introduction
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Ecto&lt;/code&gt; is really cool database layer in Elixir, for SQL it let you define SQL query inside Elixir in readable and concise way, and execute it with automatic mapping to defined data structure.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Je8hqRJs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wdjjpehwzel2xcv7mq1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Je8hqRJs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wdjjpehwzel2xcv7mq1p.png" alt="Alt Text" width="569" height="155"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;example of SQL query and execution in Ecto&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ecto.Query&lt;/code&gt; is main module and data structure for defining a query in &lt;code&gt;Ecto&lt;/code&gt;. &lt;code&gt;Ecto.Query&lt;/code&gt; is composable and dynamically programmable, which means that we can extend a lot and reuse query functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p79wuM6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ploerr642u4xtuo7wug6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p79wuM6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ploerr642u4xtuo7wug6.png" alt="Alt Text" width="880" height="354"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;we define with_search_keyword query filtering function and use it as part of bill_of_ladings function&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Constructing Ecto from Raw Query
&lt;/h3&gt;

&lt;p&gt;By default, &lt;code&gt;from&lt;/code&gt;, usual construction method of &lt;code&gt;Ecto.Query&lt;/code&gt; doesn't support complicated SQL. &lt;code&gt;from&lt;/code&gt; support only &lt;code&gt;Ecto.Schema&lt;/code&gt; data structure and table name in string, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"transporter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"STAR TRANSPORT COMPANY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, you can specify Raw SQL in join, via &lt;code&gt;fragment&lt;/code&gt;, which usually used in case you have complicated join query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"transporter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;join:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM invoice WHERE invoice.transporter_id = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can leverage this Raw SQL join to get Raw SQL as base object of &lt;code&gt;Ecto.Query&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Define dummy query
&lt;/h4&gt;

&lt;p&gt;We need a dummy query, need to has exactly one result. as example we can query from "constants" table and limit it to 1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;dummy_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"truck_types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as:&lt;/span&gt; &lt;span class="ss"&gt;:dummy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;select:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ksuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Join dummy query with raw query
&lt;/h4&gt;

&lt;p&gt;By using &lt;code&gt;subquery&lt;/code&gt;, we can reuse the dummy query to be used as join component, which we use then to join with raw data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;aggregate_subquery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy_query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@raw_sql&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;on:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as:&lt;/span&gt; &lt;span class="ss"&gt;:raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;raw:&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example of @raw_sql would be below. @raw_sql need to be constant string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;transporter&lt;/span&gt;
&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;shipper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Wrap joined query in subquery (again)
&lt;/h4&gt;

&lt;p&gt;By wrapping the joined query again in subquery, we will have a query which is just like Ecto.Query but with custom SQL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggregate_subquery&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Now you can use query as in usual Ecto way
&lt;/h4&gt;

&lt;p&gt;Like in below, which search from transporter and shipper who has name "ANDI"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ANDI"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why would you want to hack it?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Ecto&lt;/code&gt; has really flexible and expressive way to write SQL, why then you would want to resort to such hacking as writing Raw SQL?&lt;/p&gt;

&lt;p&gt;For me, the case come when i need to retrofit an existing SQL query, ~500 line - full with Postgres specific SQL feature, with existing functionality which is already built with &lt;code&gt;Ecto.Query&lt;/code&gt;. Rewriting it in &lt;code&gt;Ecto&lt;/code&gt; would need to decipher such 500 line of SQL, and would be full of &lt;code&gt;fragment&lt;/code&gt; for safety hatch of writing Raw SQL. I reason that doing it all in Raw SQL would be easier to comprehend, which then led to this hack.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>ecto</category>
      <category>sql</category>
    </item>
    <item>
      <title>Property-based testing with StreamData</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Wed, 10 Jun 2020 16:07:30 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/property-based-testing-with-streamdata-1gia</link>
      <guid>https://dev.to/calvinsadewa/property-based-testing-with-streamdata-1gia</guid>
      <description>&lt;p&gt;Recently, i have been (re)implementing &lt;a href="https://github.com/segmentio/ksuid"&gt;KSUID&lt;/a&gt; library on elixir (called &lt;a href="https://github.com/calvinsadewa/ex_ksuid"&gt;ExKsuid&lt;/a&gt;). &lt;code&gt;KSUID&lt;/code&gt; is a way to generate identifier (like &lt;code&gt;UUID&lt;/code&gt;) which result can be sorted by time, i have been using &lt;code&gt;KSUID&lt;/code&gt; a lot, and i like how it can make DB operation like find all notification after &lt;code&gt;X&lt;/code&gt; timestamp really efficient if used as primary key. There is an old &lt;code&gt;KSUID&lt;/code&gt; library (&lt;a href=""&gt;elixir-ksuid&lt;/a&gt;) but some of feature that i want hasn't implemented yet (like generate KSUID from a timestamp).&lt;/p&gt;

&lt;p&gt;During implementing &lt;code&gt;ExKsuid&lt;/code&gt;, i remember there is &lt;a href="https://github.com/whatyouhide/stream_data"&gt;StreamData&lt;/a&gt; elixir library for Property-based testing, which then i took a fancy of using in testing.&lt;/p&gt;

&lt;p&gt;Suprise to me! &lt;code&gt;StreamData&lt;/code&gt; actually help me &lt;strong&gt;tremendously&lt;/strong&gt; during implementing to find out edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Property-based testing
&lt;/h3&gt;

&lt;p&gt;When we are thinking of testing, usually what come to mind is testing by example, in which we test &lt;strong&gt;process input example and expect result to conform to output example&lt;/strong&gt;, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Describe Find Longest Pair Point of Set

Given set of 2D-point [{1.3, 1.6}, {4.2, 1.0}, {0.3, 0.6}, {2.0, 4.5}, {5.3, 4.3}]
When Find Longest Pair Point of the set
Then return [{0.3, 0.6}, {5.3, 4.3}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Describe Transfer

Given User A with Account Balance 20000,
  AND User B with Account Balance 10000
When User A transfer to User B with amount 5000
Then User A should have Balance 15000,
  AND User B should have Balance 15000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, Property-based testing try to leverage property of the tested code or model to then generate a bunch of input and try to find when property is broken. Property can be any rule the code/model is expected to hold if it is implemented correctly.&lt;/p&gt;

&lt;p&gt;for example, if we have an rectangle with height A, and width B, then pair {(0, A), (B, 0)} is going to be longer then all of set of point taken inside of rectangle &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p-sN4beR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qjtgmt4adcxx0kes894j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p-sN4beR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qjtgmt4adcxx0kes894j.png" alt="Alt Text" width="132" height="195"&gt;&lt;/a&gt;&lt;br&gt;
using StreamData, we can generate list of point inside rectangle like (here generate 10 list)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# result&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mf"&gt;1.875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mf"&gt;3.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;4.375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.125&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;3.046875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;4.375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.3125&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.875&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.390625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.46875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.68359375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.21875&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;3.4375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.78125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.9765625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.484375&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;4.2578125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.8125&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.6875&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;4.765625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.625&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;2.44140625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.46875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.4765625&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;3.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.2734375&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.7578125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.484375&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.46875&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.9375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then from this generated test data, we can test if &lt;code&gt;find_longest_pair_point&lt;/code&gt; maintain the property or not&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;TestFindLongestPair&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ExUnitProperties&lt;/span&gt;

  &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="s2"&gt;"pair corner of a rectangle is longer than all pair of point inside rectangle"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positive_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positive_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;inside_points&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="n"&gt;inside_points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_longest_pair_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which if not implemented correctly, would fail like this&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--afIQ7jy7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ztddml6zvbfv5havu68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--afIQ7jy7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ztddml6zvbfv5havu68.png" alt="Alt Text" width="880" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also make a property from Transfer process, we can say that during transferring should maintain total account balance before and after transfer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Describe Transfer Maintain Total Account Balance

Given User A with Account Balance X,
  AND User B with Account Balance Y
When User A transfer to User B with amount Z
Then X + Y == currentBalance(User A) + currentBalance(User B)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, Transfer process is what we usually say &lt;code&gt;stateful&lt;/code&gt; process, in which it change state of user A and user B. Property-based testing (and &lt;code&gt;StreamData&lt;/code&gt; in particular) sadly doesn't have much support in testing &lt;code&gt;stateful&lt;/code&gt; process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example in Real Life
&lt;/h2&gt;

&lt;p&gt;There are two main module in &lt;code&gt;ExKsuid&lt;/code&gt;, &lt;code&gt;ExKsuid&lt;/code&gt; (which handle how to generate/parse KSUID) and &lt;code&gt;ExKsuid.Base62&lt;/code&gt; (which handle how to encode/decode data in Base62 format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helping clarify by giving edge cases
&lt;/h3&gt;

&lt;p&gt;Property i would like to test for &lt;code&gt;Base62&lt;/code&gt; is that data which is encoded and decoded should return the same, so i write&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;  &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="s2"&gt;"encode and decode binary data return same binary data"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;Base62&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Base62&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but it return error like,&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OQB8us0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kw76z4sh2hltu7gh85r6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OQB8us0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kw76z4sh2hltu7gh85r6.png" alt="Alt Text" width="880" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;turn out binary data is &amp;lt;&amp;lt;0, 245&amp;gt;&amp;gt;, which after decode &amp;amp; encode return &amp;lt;&amp;lt;245&amp;gt;&amp;gt;. After considering it, i would like to only care integer value of binary data (in this case both is 245) due to seemingly hard problem to match leading 0 position in binary form and base62 form, so i update test to reflect on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;  &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="s2"&gt;"encode and decode binary data return same binary data"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode_unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode_unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Base62&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Base62&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Actually finding bugs in edge cases
&lt;/h3&gt;

&lt;p&gt;On main &lt;code&gt;ExKsuid&lt;/code&gt;, i test property "KSUID generated from lower timestamp is lower that KSUID generated"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;  &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="s2"&gt;"Generated KSUID from earlier time lexicographically smaller than later time"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positive_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;int2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;StreamData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positive_integer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ExKsuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;timestamp:&lt;/span&gt; &lt;span class="n"&gt;int1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;@epoch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
        &lt;span class="no"&gt;ExKsuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;timestamp:&lt;/span&gt; &lt;span class="n"&gt;int1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;int2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;@epoch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and immediately it fail with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  1&lt;span class="o"&gt;)&lt;/span&gt; property Generated KSUID from earlier &lt;span class="nb"&gt;time &lt;/span&gt;lexicographically smaller than later &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ExKsuidTest&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="nb"&gt;test&lt;/span&gt;/ex_ksuid_test.exs:37
     Failed with generated values &lt;span class="o"&gt;(&lt;/span&gt;after 4 successful runs&lt;span class="o"&gt;)&lt;/span&gt;:

         &lt;span class="k"&gt;*&lt;/span&gt; Clause:    int1 &amp;lt;- StreamData.positive_integer&lt;span class="o"&gt;()&lt;/span&gt;
           Generated: 5

         &lt;span class="k"&gt;*&lt;/span&gt; Clause:    int2 &amp;lt;- StreamData.positive_integer&lt;span class="o"&gt;()&lt;/span&gt;
           Generated: 3

     Assertion with &amp;lt; failed
     code:  assert ExKsuid.generate&lt;span class="o"&gt;(&lt;/span&gt;timestamp: int1 + @epoch&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt; ExKsuid.generate&lt;span class="o"&gt;(&lt;/span&gt;timestamp: int1 + int2 + @epoch&lt;span class="o"&gt;)&lt;/span&gt;
     left:  &lt;span class="s2"&gt;"e3RlO2qT8eNY7rPCfQ8Ku0"&lt;/span&gt;
     right: &lt;span class="s2"&gt;"10OPH1xqiF0vG3XHXcAAFYj"&lt;/span&gt;
     stacktrace:
       &lt;span class="nb"&gt;test&lt;/span&gt;/ex_ksuid_test.exs:39: anonymous fn/3 &lt;span class="k"&gt;in &lt;/span&gt;ExKsuidTest.&lt;span class="s2"&gt;"property Generated KSUID from earlier time lexicographically smaller than later time"&lt;/span&gt;/1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after looking closely at test data,i realize that KSUID should have 27 character, and it seems 0-encoding of Base62 have connection with the problem. Turn out that binary data for low timestamp has a lot of leading 0&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;ExKsuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;timestamp:&lt;/span&gt; &lt;span class="mi"&gt;1400000008&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#result&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;162&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it is encoded to base62, those extra leading 0 is ignored. The fix is simple, just add leading 0 until it is 27 character, and the test goes green.&lt;/p&gt;

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

&lt;p&gt;Does Property-based test help finding out bugs and giving confidence with low effort? Absolutely! &lt;/p&gt;

&lt;p&gt;Can it be used to wholly replace example based test though? Not in my case, I still use example test use to cross check my implementation with existing &lt;a href="https://github.com/segmentio/ksuid"&gt;reference library&lt;/a&gt; to give even more confidence. &lt;/p&gt;

&lt;p&gt;The confidence and ease is what's matter for me in testing after all.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>testing</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Mocking in Elixir: Comparison between Mox, Mockery, Mimic, Syringe, and Lightweight DI</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Sat, 02 May 2020 15:58:52 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/mocking-in-elixir-comparison-between-mox-mockery-mimic-syringe-and-lightweight-di-3d5h</link>
      <guid>https://dev.to/calvinsadewa/mocking-in-elixir-comparison-between-mox-mockery-mimic-syringe-and-lightweight-di-3d5h</guid>
      <description>&lt;p&gt;Unit test in elixir is fantastic using ExUnit! It is simple and easy! However sometimes we need to test function/module which access outside resources our code, like third party services or API. &lt;/p&gt;

&lt;p&gt;These outside resource may &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take time to be accessed (due to network) &lt;/li&gt;
&lt;li&gt;require additional money (in case of pay per request API) &lt;/li&gt;
&lt;li&gt;not return stable data (in case of live updating data) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These make accessing outside resource directly when unit testing is not desirable, hence the need of &lt;strong&gt;mock&lt;/strong&gt; these resources in our unit test so it can still be fast, cheap, and stable.&lt;/p&gt;

&lt;p&gt;This article aims to explore 5 different ways of mocking in elixir: Mox, Mockery, Mimic, Syringe, and Lightweight DI&lt;/p&gt;

&lt;h3&gt;
  
  
  Code to test
&lt;/h3&gt;

&lt;p&gt;Here is our sample code to test, &lt;code&gt;corona_news&lt;/code&gt;. &lt;code&gt;corona_news&lt;/code&gt; is a simple application to display latest update regarding Corona Virus / COVID-19 by accessing &lt;a href="https://api.covid19api.com"&gt;https://api.covid19api.com&lt;/a&gt;. We would like to test mocking &lt;code&gt;CoronaNews.Gateway&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Module for displaying Corona Virus (COVID-19) data update
  output of this module is expected to be readable by human user
  """&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Return human-readable text summary of corona update for a country

  ## Example
    iex&amp;gt; text_news_for(CoronaNews.Country.global)
    Total Case: 3340989
    Total Recovered: 1052510
    New Case: 86819
    Latest Update: 2020-05-02 12:50:40.760326Z
  """&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;text_news_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_data_for_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="sd"&gt;"""
        Total Case: #{data.total_case}
        Total Recovered: #{data.total_recovered}
        New Case: #{data.new_case}
        Latest Update: #{data.latest_update}
        """&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="sd"&gt;"""
        Failed fetching data
        Error: #{inspect error}
        """&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Display human-readable text summary of corona update for a country
  """&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;display_news_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_news_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Module for storing constants identifier of each country
  Country Identifier is CountryCode from https://api.covid19api.com
  """&lt;/span&gt;

  &lt;span class="nv"&gt;@type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Global"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;indonesia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ID"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;china&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"CN"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;united_states_of_america&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"US"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Gateway&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Module for contacting API at https://api.covid19api.com
  for latest data regarding corona
  """&lt;/span&gt;

  &lt;span class="nv"&gt;@base_url&lt;/span&gt; &lt;span class="s2"&gt;"https://api.covid19api.com"&lt;/span&gt;
  &lt;span class="nv"&gt;@summary_url&lt;/span&gt; &lt;span class="nv"&gt;@base_url&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/summary"&lt;/span&gt;

  &lt;span class="nv"&gt;@typedoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Result for fetch API,
  latest_update is always UTC
  example:
  %{
    latest_update: ~U[2020-05-02 12:41:23Z],
    new_case: 433,
    total_case: 10551,
    total_recovered: 1591
  }
  """&lt;/span&gt;
  &lt;span class="nv"&gt;@type&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="ss"&gt;total_case:&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="ss"&gt;total_recovered:&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="ss"&gt;new_case:&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="ss"&gt;latest_update:&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Fetch latest summary data for a country
  ## Example
    CoronaNews.Gateway.fetch_data_for_country(CoronaNews.Country.indonesia)
  """&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;fetch_data_for_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;fetch_data_for_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HTTPoison&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@summary_url&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;status_code:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:body_decode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_map&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:body_decode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Jason&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;parse_summary_data_for_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Request failed, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Response code not 200, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:body_decode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"JSON decode failed, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# parse JSON result for get summary data to result type for country&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;parse_summary_data_for_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_api_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;json_api_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;CoronaNews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;json_api_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Countries"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CountryCode"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Country data not found for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;latest_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_iso8601&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;datetime&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
        &lt;span class="ss"&gt;total_case:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalConfirmed"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;total_recovered:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalRecovered"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;new_case:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NewConfirmed"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;latest_update:&lt;/span&gt; &lt;span class="n"&gt;latest_update&lt;/span&gt;
      &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is example of result of &lt;code&gt;corona_news&lt;/code&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TLt6zeLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sbe6ccu0tsogp5gfjhbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TLt6zeLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sbe6ccu0tsogp5gfjhbo.png" alt="Alt Text" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Using Mox
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/dashbitco/mox"&gt;Mox&lt;/a&gt; is library for defining concurrent mocks in Elixir.&lt;/p&gt;

&lt;p&gt;Mox principle is that &lt;strong&gt;mock&lt;/strong&gt; should be an object and not a verb. You should create behaviour of module to be mocked, and then during test change using module (at compile time) to mocked module conforming to behaviour.&lt;/p&gt;

&lt;p&gt;It is perhaps the most used library for &lt;strong&gt;mock&lt;/strong&gt; in elixir, with  541015 recent download.&lt;/p&gt;

&lt;p&gt;For using Mox to test &lt;code&gt;CoronaNews&lt;/code&gt;, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define behaviour for &lt;code&gt;CoronaNews.Gateway&lt;/code&gt; (&lt;code&gt;CoronaNews.Gateway.Behaviour&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;mox&lt;/code&gt; to our dependency&lt;/li&gt;
&lt;li&gt;Write the unit test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the diff of code when i use mox for testing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; at test/corona_news_test.exs
&lt;span class="gi"&gt;+defmodule CoronaNewsTest do
+  use ExUnit.Case
+  import Mox
+
+  describe "CoronaNews" do
+    test "text_news_for/1 on Gateway Success" do
+      CoronaNews.GatewayMock
+      |&amp;gt; expect(:fetch_data_for_country, 1, fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:ok, %{
+          total_case: 6,
+          total_recovered: 3,
+          new_case: 1,
+          latest_update: ~U[2020-05-02 13:37:37Z]
+        }}
+      end)
+
+      assert """
+      Total Case: 6
+      Total Recovered: 3
+      New Case: 1
+      Latest Update: 2020-05-02 13:37:37Z
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+
+    test "text_news_for/1 on Gateway Failed" do
+      CoronaNews.GatewayMock
+      |&amp;gt; expect(:fetch_data_for_country, 1, fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:error, "Error due to Mock"}
+      end)
+
+      assert """
+      Failed fetching data
+      Error: \"Error due to Mock\"
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+  end
+end
&lt;/span&gt;
# at test/test_helper.exs
&lt;span class="gi"&gt;+Application.put_env(:corona_news, :gateway, CoronaNews.GatewayMock)
+Mox.defmock(CoronaNews.GatewayMock, for: CoronaNews.Gateway.Behaviour)
+Application.ensure_started(:mox)
&lt;/span&gt;&lt;span class="p"&gt;ExUnit.start()
&lt;/span&gt;
# at mix.exs
  defp deps do
    [
      {:httpoison, "~&amp;gt; 1.6"},
      {:jason, "~&amp;gt; 1.2"},
&lt;span class="gi"&gt;+      {:mox, "~&amp;gt; 0.5", only: :test}
&lt;/span&gt;    ]
  end

# at lib/corona_news.ex
&lt;span class="p"&gt;defmodule CoronaNews do
&lt;/span&gt;  @moduledoc """
  Module for displaying Corona Virus (COVID-19) data update
  output of this module is expected to be readable by human user
  """
&lt;span class="gd"&gt;- 
&lt;/span&gt;&lt;span class="gi"&gt;+  def gateway(), do: Application.get_env(:corona_news, :gateway, CoronaNews.Gateway)
&lt;/span&gt;
  @doc """
  Return human-readable text summary of corona update for a country

  ## Example
    iex&amp;gt; text_news_for(CoronaNews.Country.global)
    Total Case: 3340989
    Total Recovered: 1052510
    New Case: 86819
    Latest Update: 2020-05-02 12:50:40.760326Z
  """
  def text_news_for(country \\ CoronaNews.Country.global()) do
&lt;span class="gd"&gt;-    result = CoronaNews.Gateway.fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="gi"&gt;+    result = gateway().fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;

&lt;span class="gi"&gt;+defmodule CoronaNews.Gateway.Behaviour do
+  @moduledoc """
+  Behaviour of Gateway (API data request module) for corona data
+  """
+  @type result :: %{
+    total_case: number(),
+    total_recovered: number(),
+    new_case: number(),
+    latest_update: DateTime.t()
+  }
+
+  @doc """
+  Fetch latest summary data for a country
+  """
+  @callback fetch_data_for_country(CoronaNews.Country.t) :: {:ok, result} | {:error, any()}
+end
&lt;/span&gt;
defmodule CoronaNews.Gateway do
  @moduledoc """
  Module for contacting API at https://api.covid19api.com
  for latest data regarding corona
  """
&lt;span class="gd"&gt;- 
&lt;/span&gt;&lt;span class="gi"&gt;+  @behaviour CoronaNews.Gateway.Behaviour
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Mockery
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/mockery/readme.html"&gt;Mockery&lt;/a&gt; is Simple mocking library for asynchronous testing in Elixir.&lt;/p&gt;

&lt;p&gt;It define several type of mock, but what i am using is macro based one in which the macro will change behaviour of module at testing&lt;/p&gt;

&lt;p&gt;to use Mockery, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add mockery to depedency&lt;/li&gt;
&lt;li&gt;modify &lt;code&gt;CoronaNews&lt;/code&gt; module to able to mock &lt;code&gt;CoronaNews.Gateway&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;write test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the code diff for testing using Mockery&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; at test/corona_news_test.exs
&lt;span class="gi"&gt;+defmodule CoronaNewsTest do
+  use ExUnit.Case
+  import Mockery
+
+  describe "CoronaNews" do
+    test "text_news_for/1 on Gateway Success" do
+      mock CoronaNews.Gateway, [fetch_data_for_country: 1], fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:ok, %{
+          total_case: 6,
+          total_recovered: 3,
+          new_case: 1,
+          latest_update: ~U[2020-05-02 13:37:37Z]
+        }}
+      end
+
+      assert """
+      Total Case: 6
+      Total Recovered: 3
+      New Case: 1
+      Latest Update: 2020-05-02 13:37:37Z
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+
+    test "text_news_for/1 on Gateway Failed" do
+      mock CoronaNews.Gateway, [fetch_data_for_country: 1], fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:error, "Error due to Mock"}
+      end
+
+      assert """
+      Failed fetching data
+      Error: \"Error due to Mock\"
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+  end
+end
+
&lt;/span&gt;
# at mix.exs
  defp deps do
    [
      {:httpoison, "~&amp;gt; 1.6"},
      {:jason, "~&amp;gt; 1.2"},
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+      {:mockery, "~&amp;gt; 2.3.0", runtime: false}
&lt;/span&gt;    ]
  end

# at lib/corona_news.ex
&lt;span class="p"&gt;defmodule CoronaNews do
&lt;/span&gt;  @moduledoc """
  Module for displaying Corona Virus (COVID-19) data update
  output of this module is expected to be readable by human user
  """
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+  import Mockery.Macro
&lt;/span&gt;
  @doc """
  Return human-readable text summary of corona update for a country

  ## Example
    iex&amp;gt; text_news_for(CoronaNews.Country.global)
    Total Case: 3340989
    Total Recovered: 1052510
    New Case: 86819
    Latest Update: 2020-05-02 12:50:40.760326Z
  """
  def text_news_for(country \\ CoronaNews.Country.global()) do
&lt;span class="gd"&gt;-    result = CoronaNews.Gateway.fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="gi"&gt;+    result = mockable(CoronaNews.Gateway).fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Mimic
&lt;/h3&gt;

&lt;p&gt;&lt;a href=""&gt;Mimic&lt;/a&gt; is A sane way of using mocks in Elixir.&lt;/p&gt;

&lt;p&gt;It's usage is the most simple among other library in comparison due not needing any kind of change in codebase&lt;/p&gt;

&lt;p&gt;to use Mimic, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add Mimic to dependency&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;Mimic.copy(CoronaNews.Gateway)&lt;/code&gt; at &lt;code&gt;test/test_helper.exs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;write test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the code diff for testing using Mimic&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; at test/corona_news_test.exs
&lt;span class="gi"&gt;+defmodule CoronaNewsTest do
+  use ExUnit.Case
+
+  describe "CoronaNews" do
+    test "text_news_for/1 on Gateway Success" do
+      Mimic.expect(CoronaNews.Gateway, :fetch_data_for_country, fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:ok, %{
+          total_case: 6,
+          total_recovered: 3,
+          new_case: 1,
+          latest_update: ~U[2020-05-02 13:37:37Z]
+        }}
+      end)
+
+      assert """
+      Total Case: 6
+      Total Recovered: 3
+      New Case: 1
+      Latest Update: 2020-05-02 13:37:37Z
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+
+    test "text_news_for/1 on Gateway Failed" do
+      Mimic.expect(CoronaNews.Gateway, :fetch_data_for_country, fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:error, "Error due to Mock"}
+      end)
+
+      assert """
+      Failed fetching data
+      Error: \"Error due to Mock\"
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+  end
+end
&lt;/span&gt;
# at mix.exs
  defp deps do
    [
      {:httpoison, "~&amp;gt; 1.6"},
      {:jason, "~&amp;gt; 1.2"},
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+      {:mimic, "~&amp;gt; 1.0.0", only: :test}
&lt;/span&gt;    ]
  end

# at test/test_helper.exs
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+Mimic.copy(CoronaNews.Gateway)
&lt;/span&gt;&lt;span class="p"&gt;ExUnit.start()
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Syringe
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/skylerparr/syringe"&gt;Syringe&lt;/a&gt; is a injection framework that also opens the opportunity for clearer mocking and to run mocked test asynchronously&lt;/p&gt;

&lt;p&gt;to use Syringe, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add Syringe to dependency&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;config :syringe, injector_strategy: MockInjectingStrategy&lt;/code&gt; to &lt;code&gt;config/test.exs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;config :syringe, injector_strategy: AliasInjectingStrategy&lt;/code&gt; to &lt;code&gt;config/config.exs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reshuffle &lt;code&gt;CoronaNews.Gateway&lt;/code&gt; to be above &lt;code&gt;CoronaNews&lt;/code&gt; because &lt;code&gt;Syringe&lt;/code&gt; rely on macro (hence need to define &lt;code&gt;CoronaNews.Gateway&lt;/code&gt; before &lt;code&gt;CoronaNews&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add Injector to &lt;code&gt;CoronaNews.Gateway&lt;/code&gt; at &lt;code&gt;CoronaNews&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;Mocker.start_link()&lt;/code&gt; at &lt;code&gt;test/test_helper.exs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;write test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is code diff using syringe&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; at test/corona_news_test.exs
&lt;span class="gi"&gt;+defmodule CoronaNewsTest do
+  use ExUnit.Case, async: true
+  import Mocker
+
+  describe "CoronaNews" do
+    test "text_news_for/1 on Gateway Success" do
+      mock(CoronaNews.Gateway)
+      intercept(CoronaNews.Gateway, :fetch_data_for_country, nil, with: fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:ok, %{
+          total_case: 6,
+          total_recovered: 3,
+          new_case: 1,
+          latest_update: ~U[2020-05-02 13:37:37Z]
+        }}
+      end)
+
+      assert """
+      Total Case: 6
+      Total Recovered: 3
+      New Case: 1
+      Latest Update: 2020-05-02 13:37:37Z
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+
+    test "text_news_for/1 on Gateway Failed" do
+      mock(CoronaNews.Gateway)
+      intercept(CoronaNews.Gateway, :fetch_data_for_country, nil, with: fn country -&amp;gt;
+        assert country == CoronaNews.Country.indonesia()
+        {:error, "Error due to Mock"}
+      end)
+
+      assert """
+      Failed fetching data
+      Error: \"Error due to Mock\"
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia())
+    end
+  end
+end
+
&lt;/span&gt;
# at test/test_helper.exs
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+Mocker.start_link()
&lt;/span&gt;&lt;span class="p"&gt;ExUnit.start()
&lt;/span&gt;
# at mix.exs
  defp deps do
    [
      {:httpoison, "~&amp;gt; 1.6"},
      {:jason, "~&amp;gt; 1.2"},
&lt;span class="gi"&gt;+      {:syringe, "~&amp;gt; 1.0"}
&lt;/span&gt;    ]
  end

# at lib/corona_news.ex
&lt;span class="err"&gt;#&lt;/span&gt; Reshuffle CoronaNews.Gateway module to be above CoronaNews module
&lt;span class="p"&gt;defmodule CoronaNews do
&lt;/span&gt;  @moduledoc """
  Module for displaying Corona Virus (COVID-19) data update
  output of this module is expected to be readable by human user
  """
&lt;span class="gi"&gt;+  use Injector
+
+  inject CoronaNews.Gateway, as: Gateway
&lt;/span&gt;
  @doc """
  Return human-readable text summary of corona update for a country

  ## Example
    iex&amp;gt; text_news_for(CoronaNews.Country.global)
    Total Case: 3340989
    Total Recovered: 1052510
    New Case: 86819
    Latest Update: 2020-05-02 12:50:40.760326Z
  """
  def text_news_for(country \\ CoronaNews.Country.global()) do
&lt;span class="gd"&gt;-    result = CoronaNews.Gateway.fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="gi"&gt;+    result = Gateway.fetch_data_for_country(country)
&lt;/span&gt;
# at config/config.exs
&lt;span class="gi"&gt;+config :syringe, injector_strategy: MockInjectingStrategy
&lt;/span&gt;
# at config/test.exs
&lt;span class="gi"&gt;+config :syringe, injector_strategy: MockInjectingStrategy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Lightweight DI
&lt;/h3&gt;

&lt;p&gt;Lightweight Depedency Injection is done by giving module/function as argument to called function.&lt;/p&gt;

&lt;p&gt;on simple case this is easy, however it will become cumbersome on many nested function because each function need to pass the module/function to nested function after it&lt;/p&gt;

&lt;p&gt;to use Lightweight DI, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modify function &lt;code&gt;CoronaNews.text_news_for&lt;/code&gt; to accept gateway module&lt;/li&gt;
&lt;li&gt;define mock module on test&lt;/li&gt;
&lt;li&gt;write test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is code diff using Lightweight DI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; at test/corona_news_test.exs
&lt;span class="gi"&gt;+defmodule CoronaNewsTest do
+  use ExUnit.Case, async: true
+
+  describe "CoronaNews" do
+    test "text_news_for/1 on Gateway Success" do
+
+      defmodule GatewayMock do
+        def fetch_data_for_country(country) do
+          assert country == CoronaNews.Country.indonesia()
+          {:ok, %{
+            total_case: 6,
+            total_recovered: 3,
+            new_case: 1,
+            latest_update: ~U[2020-05-02 13:37:37Z]
+          }}
+        end
+      end
+
+      assert """
+      Total Case: 6
+      Total Recovered: 3
+      New Case: 1
+      Latest Update: 2020-05-02 13:37:37Z
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia(), GatewayMock)
+    end
+
+    test "text_news_for/1 on Gateway Failed" do
+      defmodule GatewayMock do
+        def fetch_data_for_country(country) do
+          assert country == CoronaNews.Country.indonesia()
+          {:error, "Error due to Mock"}
+        end
+      end
+
+      assert """
+      Failed fetching data
+      Error: \"Error due to Mock\"
+      """ == CoronaNews.text_news_for(CoronaNews.Country.indonesia(), GatewayMock)
+    end
+  end
+end
+
&lt;/span&gt;
# at lib/corona_news.ex
&lt;span class="p"&gt;defmodule CoronaNews do
&lt;/span&gt;  @moduledoc """
  Module for displaying Corona Virus (COVID-19) data update
  output of this module is expected to be readable by human user
  """

  @doc """
  Return human-readable text summary of corona update for a country

  ## Example
    iex&amp;gt; text_news_for(CoronaNews.Country.global)
    Total Case: 3340989
    Total Recovered: 1052510
    New Case: 86819
    Latest Update: 2020-05-02 12:50:40.760326Z
  """
&lt;span class="gd"&gt;-  def text_news_for(country \\ CoronaNews.Country.global()) do
-    result = CoronaNews.Gateway.fetch_data_for_country(country)
&lt;/span&gt;&lt;span class="gi"&gt;+  def text_news_for(country \\ CoronaNews.Country.global(), gateway \\ CoronaNews.Gateway) do
+    result = gateway.fetch_data_for_country(country)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;You have now see multitude of way to &lt;strong&gt;mock&lt;/strong&gt; in elixir, personally my favorite is &lt;code&gt;Mimic&lt;/code&gt; due to simplicity of setup and doesn't need to change tested codebase. Do go out and try, your mileage may vary!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>testing</category>
      <category>mock</category>
      <category>compare</category>
    </item>
    <item>
      <title>Implementing Message Outbox Pattern with Oban</title>
      <dc:creator>calvinsadewa</dc:creator>
      <pubDate>Sat, 08 Feb 2020 12:01:54 +0000</pubDate>
      <link>https://dev.to/calvinsadewa/implementing-message-outbox-pattern-with-oban-131</link>
      <guid>https://dev.to/calvinsadewa/implementing-message-outbox-pattern-with-oban-131</guid>
      <description>&lt;p&gt;Hey guys, today i want to share Message Outbox pattern and implementation with Oban in Elixir&lt;br&gt;
I have used them extensively and hope that it can benefit you too.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problems
&lt;/h2&gt;

&lt;p&gt;What happen if as part of database transaction, you need to send email / push notification / outside communication?&lt;br&gt;
Implemented naively, we could just send email / push notification as part of transaction.&lt;/p&gt;

&lt;p&gt;Imagine we have &lt;code&gt;transfer_money&lt;/code&gt; function which send money from a sender to receiver,&lt;br&gt;
as part of operation, transfering money need to also send email notification to sender and receiver.&lt;/p&gt;

&lt;p&gt;Naively, we could make &lt;code&gt;transfer_money&lt;/code&gt; a transaction which do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;substract sender balance&lt;/li&gt;
&lt;li&gt;send email to sender regarding substracted money&lt;/li&gt;
&lt;li&gt;add receiver balance&lt;/li&gt;
&lt;li&gt;send email to receiver regarding added money&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is pseudo-code snippet implementing &lt;code&gt;transfer_money&lt;/code&gt; function in elixir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# model/payment.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;change:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Transfer money from sender to receiver&lt;/span&gt;
    &lt;span class="c1"&gt;# as part of transfer, we will send email notification&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;transfer_money&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
        &lt;span class="ss"&gt;sender:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;receiver:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;amount:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
          &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;balance:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# operation 1&lt;/span&gt;
          &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"substracted by"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# operation 2&lt;/span&gt;
          &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;balance:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# operation 3&lt;/span&gt;
          &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# operation 4&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Send transfer email notification&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"payment@mycompany.com"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sd"&gt;"""
        Your bank account #{account.account_number} have #{operation} #{amount}
        """&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite seeming okay, it is possible bug that can happen. Let me show a scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;transfer_money function initiated&lt;/li&gt;
&lt;li&gt;Open a transaction&lt;/li&gt;
&lt;li&gt;substract sender balance by amount via DB operation&lt;/li&gt;
&lt;li&gt;send email notification to sender that balance has been substracted&lt;/li&gt;
&lt;li&gt;add receiver balance by amount via DB operation&lt;/li&gt;
&lt;li&gt;try send email notification to recevier that balance has been added, however failed due to transient network failure&lt;/li&gt;
&lt;li&gt;Transaction rolledback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In above failure scenario, even though transfer_money function rolledback, &lt;br&gt;
sender already sended email notifing s/he bank account subtracted emoji.&lt;/p&gt;

&lt;p&gt;While mis-sent email may seem harmless (though will probably shock sender that s/he subtracted twice),&lt;br&gt;
These kind of cases can also happen with Message Queue (RabbitMQ, Kafka, SQS) using system,&lt;br&gt;
which would mean subscribing service get wrong data, in turn may spawn more problem.&lt;/p&gt;

&lt;p&gt;The problem is that operation 2 and 4 cannot rolledback, Message Outbox pattern help us with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Message Outbox
&lt;/h2&gt;

&lt;p&gt;Message Outbox is a pattern in which we insert intention to send message to Outbox in transactional manner,&lt;br&gt;
Outbox is usually a table which then polled/listened regulary by Worker. Worker would send any message waiting to be sended.&lt;/p&gt;

&lt;p&gt;With that in mind, let us patch &lt;code&gt;transfer_money&lt;/code&gt; to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;substract sender balance&lt;/li&gt;
&lt;li&gt;insert intent to send email for sender to outbox&lt;/li&gt;
&lt;li&gt;add receiver balance&lt;/li&gt;
&lt;li&gt;insert intent to send email for receiver to outbox&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also need to implement Worker which poll/listen to our outbox, and send email if any pending.&lt;/p&gt;

&lt;p&gt;Here is snippet of pseudo function implementing Message Outbox&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# model/payment.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="c1"&gt;# insert Send transfer email notification to outbox&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert!&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;SendMessage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;account:&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;operation:&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Real implementation of sending email&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;real_send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"payment@mycompany.com"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sd"&gt;"""
        Your bank account #{account.account_number} have #{operation} #{amount}
        """&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# outbox/worker.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Perform is done regularly by worker, either via polling/listening&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;SendMessage&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;real_send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Message Outbox, outside communication become part of transaction, and can be rolledback in case transaction failed.&lt;br&gt;
However, implementing worker which listen/poll for message can be hard, &lt;br&gt;
not to mention we still need to handle cases where we failed sended message and retry logic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Oban
&lt;/h2&gt;

&lt;p&gt;Oban is a transactional job queue in elixir built on PostgreSQL. It handle many of the operation relating to job queue such as enqueuing job,&lt;br&gt;
distribute job to worker, handle job processing throughout lifecycle, log job status, and more. You can see more at link &lt;a href="https://hexdocs.pm/oban/1.0.0/Oban.html"&gt;https://hexdocs.pm/oban/1.0.0/Oban.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because of it's transactional feature in PostgreSQL, we can leverage Oban as our Outbox. &lt;/p&gt;

&lt;p&gt;Using Oban, we will do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup Oban in config.exs and Migration (I left out this part, see &lt;a href="https://hexdocs.pm/oban/1.0.0/Oban.html"&gt;https://hexdocs.pm/oban/1.0.0/Oban.html&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;Implement an Oban worker which would send email for us, &lt;code&gt;Outbox.Email&lt;/code&gt;, and implement processing for job &lt;code&gt;send_email&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify &lt;code&gt;send_transfer_email!&lt;/code&gt; to insert job &lt;code&gt;send_email&lt;/code&gt; to &lt;code&gt;Outbox.Email&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's modify our snippet to use Oban.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# model/payment.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;change:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Transfer money from sender to receiver&lt;/span&gt;
    &lt;span class="c1"&gt;# as part of transfer, we will send email notification&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;transfer_money&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
        &lt;span class="ss"&gt;sender:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;receiver:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;amount:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
          &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;balance:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# operation 1&lt;/span&gt;
          &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"substracted by"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# operation 2&lt;/span&gt;
          &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;balance:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# operation 3&lt;/span&gt;
          &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# operation 4&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# insert intent to send transfer email notification&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send_transfer_email!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"payment@mycompany.com"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sd"&gt;"""
        Your bank account #{account.account_number} have #{operation} #{amount}
        """&lt;/span&gt;

        &lt;span class="c1"&gt;# Insert args to Outbox.Email&lt;/span&gt;
        &lt;span class="p"&gt;%{&lt;/span&gt;
            &lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="ss"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="ss"&gt;event:&lt;/span&gt; &lt;span class="s2"&gt;"send_email"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# outbox/email.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;

    &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
    &lt;span class="c1"&gt;# Process sending email&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="s2"&gt;"event"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"send_email"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty simple doesn't it? Though &lt;code&gt;args&lt;/code&gt; for Oban worker is interesting.&lt;br&gt;
When i insert &lt;code&gt;args&lt;/code&gt; to &lt;code&gt;Outbox.Email&lt;/code&gt;, i insert it as Atom Map. However when i process &lt;code&gt;args&lt;/code&gt; at &lt;code&gt;perform&lt;/code&gt; function, it become String Map.&lt;br&gt;
This is because when Oban inserting job to Database, it transform &lt;code&gt;args&lt;/code&gt; to JSON, and when Worker try to process job, &lt;code&gt;args&lt;/code&gt; is parsed as JSON.&lt;/p&gt;

&lt;p&gt;While Message Outbox is good, one weakness is that Message Outbox cannot depend on outside communication result inside transaction.&lt;br&gt;
If your transaction really need to depend on outside communication, you may consider implementing two phase (or three phase) commit.&lt;/p&gt;

&lt;p&gt;**Note: Oban is built upon PostgreSQL, so if you use MariaDB/MySQL you may consider other Job Queue such as Rihanna or implement polling by using cron-based Quantum&lt;/p&gt;

&lt;p&gt;**Special thanks for Nanda Girindratama and Rifki Adrian for early review&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>microservices</category>
      <category>database</category>
    </item>
  </channel>
</rss>
