<?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: Willian Antunes</title>
    <description>The latest articles on DEV Community by Willian Antunes (@willianantunes).</description>
    <link>https://dev.to/willianantunes</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%2F271856%2Fddf36ba6-0050-413f-9912-c825329d980e.jpeg</url>
      <title>DEV Community: Willian Antunes</title>
      <link>https://dev.to/willianantunes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willianantunes"/>
    <language>en</language>
    <item>
      <title>Query compressed logs that are stored in S3 using AWS Athena</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Mon, 23 May 2022 11:54:17 +0000</pubDate>
      <link>https://dev.to/willianantunes/query-compressed-logs-that-are-stored-in-s3-using-aws-athena-2i45</link>
      <guid>https://dev.to/willianantunes/query-compressed-logs-that-are-stored-in-s3-using-aws-athena-2i45</guid>
      <description>&lt;p&gt;Recently someone asked me to create an easy way to consult all the logs stored in S3. Unfortunately, the person who was trying to check all the log files couldn't consult them suitably because of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20.3 GB of data compressed with GZIP.&lt;/li&gt;
&lt;li&gt;Each file has more than 40 thousand lines.&lt;/li&gt;
&lt;li&gt;Many folders, with each containing various compressed files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moreover, those logs have been exported from a Log Group that is not available on CloudWatch anymore; otherwise, we could use Log Insights right away 😅. &lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas that I had at first glance
&lt;/h2&gt;

&lt;p&gt;Some options that I thought of when I received the request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restore from S3 to a Log Group:&lt;/strong&gt; I'd have to create a serverless function that would read all the objects in S3, check if one is a GZIP, if true, then uncompress it, read the log file and send each line using &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html"&gt;The PutLogEvents API&lt;/a&gt; to the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html#SendingLogData"&gt;Log Group&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download files from S3 and fill them into Elasticsearch:&lt;/strong&gt; Simply put, I'd expend some time programming something to understand the underlying data from the logs to populate an index in Elasticsearch. A simple LOREM would be enough to do the ETL followed by the Kibana startup afterward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use some ETL service from AWS and push what has been processed to a Log Group:&lt;/strong&gt; &lt;a href="https://aws.amazon.com/glue/"&gt;AWS Glue&lt;/a&gt; is the service that can be used for this purpose. So it's another option to make everything inside the cloud itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before executing one of these options, I sought cookbooks, lessons learned, and tutorials to help me with something that I couldn't grasp at first. That's when I met AWS Athena 😍! If you look at &lt;a href="https://aws.amazon.com/athena/?nc=sn&amp;amp;loc=1"&gt;its overview&lt;/a&gt;, you can get a quick idea of what it may deliver to you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon Athena is an interactive query service that makes it easy to analyze data in Amazon S3 using standard SQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But how would I query the bucket with the compressed log files 🤔?&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding AWS Athena and applying a solution
&lt;/h2&gt;

&lt;p&gt;When I found an &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/analyze-logs-athena/"&gt;AWS blog post describing an example of how to consult access logs&lt;/a&gt;, I quickly get the point! For my case, basically, the plan was to arrange the following topics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the folder in your bucket where you'd like AWS Athena to analyze to create the table. It's important to mention that it will look over all objects recursively.&lt;/li&gt;
&lt;li&gt;Understand a REGEX pattern that can capture each line of your log file in a meaningful way (below, I show you my occasion).&lt;/li&gt;
&lt;li&gt;Define the table schema to match the capturing group you configured and some extra metadata to inform AWS Athena to create everything properly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first was relatively easy to handle. My folder structure is something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bucket-name/
│
└── 0smpprubzz/  &amp;lt;--- API Gateway ID
    └── 2574f75d-7a93-4387-bed3-2ea5f4e2be59/  &amp;lt;--- folder with many other folders
        ├── 00107dc08cd17d3ea18805491763048c/  &amp;lt;--- sort of aggregation of log files
        |   ├── 000000.gz  &amp;lt;--- Compressed log file 0
        |   ├── 000001.gz  &amp;lt;--- Compressed log file 1
        |   └── 00000X.gz  &amp;lt;--- Compressed log file X
        |       └── 00000X &amp;lt;--- If you open the compressed log file, you get this file. It has no extension!
        ├── 00249903990fb8d8cc29b88e4b0d3a1a/  &amp;lt;--- sort of aggregation of log files
        |   ├── 000000.gz  &amp;lt;--- Compressed log file 0
        |   ├── 000001.gz  &amp;lt;--- Compressed log file 1
        |   └── 00000X.gz  &amp;lt;--- Compressed log file X
        |       └── 00000X &amp;lt;--- If you open the compressed log file, you get this file. It has no extension!
        └── X/  &amp;lt;--- there are more than 1000 folders like this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus I had to use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3://bucket-name/0smpprubzz/2574f75d-7a93-4387-bed3-2ea5f4e2be59/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the second topic, I just had to look at some entries from a random log file to recognize a pattern. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2021-06-18T16:41:50.920Z (3e08ac47-b24b-417f-9d8c-13a4bb335ac3) Starting execution for request: Ze08ac47-b24b-417f-9d8c-13a4bb335ac3
2021-06-18T16:41:50.920Z (3e08ac47-b24b-417f-9d8c-13a4bb335ac3) HTTP Method: OPTIONS, Resource Path: /jafar-application/v1/lamps
2021-06-18T16:41:50.920Z (3e08ac47-b24b-417f-9d8c-13a4bb335ac3) Method request path: {}
2021-06-18T16:41:50.920Z (3e08ac47-b24b-417f-9d8c-13a4bb335ac3) Method request query string: {}
2021-06-18T16:41:50.920Z (3e08ac47-b24b-417f-9d8c-13a4bb335ac3) Method request headers: {Origin=https://agrabah.gov.eg, sec-fetch-mode=cors, X-Akamai-SR-Hop=1, Akamai-Origin-Hop=2, sec-fetch-site=same-site, Accept=*/*, Referer=https://agrabah.gov.eg/, User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.0 Chrome/87.0.4280.141 Mobile Safari/537.36, X-Forwarded-Proto=https, Host=api.agrabah.gov.eg, Accept-Encoding=gzip, Pragma=no-cache, True-Client-IP=X.P.219.112, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=X-60ccccce-P84a5379108e3a2773817ca1, Via=1.1 v1-akamaitech.net(ghost) (AkamaiGHost), 1.1 akamai.net(ghost) (AkamaiGHost), X-Akamai-CONFIG-LOG-DETAIL=true, Access-Control-Request-Method=GET, Cache-Control=no-cache, max-age=0, Access-Control-Request-Headers=authorization,x_etag, X-Forwarded-For=X.P.219.112, X.P.63.4, X.P.247.39, Accept-Language=pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7, sec-fetch-dest=empty}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I came up with the following regex (see it on &lt;a href="https://regexr.com/63b0d"&gt;RegEx website&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^(\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}[+-\.]\d{1,3}Z?) (\(.+?\)) (.+)$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the value of each group from a sample line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rPYNygXi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmt96lvydell4lwhemzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rPYNygXi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmt96lvydell4lwhemzn.png" alt="A regex expression is applied on the first line of a sample text from a random log file." title="Sample result of the regex given the first line of a small part of the log file." width="880" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It extracts three groups; hence we can use them to finally define the table schema. Through the &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/what-is.html"&gt;User Guide&lt;/a&gt;, which describes &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/create-table.html"&gt;how "&lt;em&gt;create table"&lt;/em&gt; works&lt;/a&gt;, I wrote the following DDL statement:&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`jafar_database.agrabah`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`requestdatetime`&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`identifier`&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`message`&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FORMAT&lt;/span&gt; &lt;span class="n"&gt;SERDE&lt;/span&gt;
  &lt;span class="s1"&gt;'org.apache.hadoop.hive.serde2.RegexSerDe'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;SERDEPROPERTIES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'input.regex'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'^(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{4}-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{2}-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{2}T&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{2}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{2}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{2}[+-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;.]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{1,3}Z?) (&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;(.+?&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;)) (.+)$'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STORED&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;INPUTFORMAT&lt;/span&gt;
  &lt;span class="s1"&gt;'org.apache.hadoop.mapred.TextInputFormat'&lt;/span&gt;
&lt;span class="n"&gt;OUTPUTFORMAT&lt;/span&gt;
  &lt;span class="s1"&gt;'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'&lt;/span&gt;
&lt;span class="k"&gt;LOCATION&lt;/span&gt;
  &lt;span class="s1"&gt;'s3://bucket-name/0smpprubzz/2574f75d-7a93-4387-bed3-2ea5f4e2be59/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, STRING for all columns wasn't a problem (if it will ever be, I update this article, but you should know that you can use &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/data-types.html"&gt;many other types&lt;/a&gt;, like TIMESTAMP). I didn't specify the compression type because &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/compression-formats.html"&gt;it will get it by the file extension by default&lt;/a&gt;. By the way, if you look at the regex again, I had to use double backslash because &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/regex-serde.html"&gt;RegexSerDe&lt;/a&gt; follows the Java Standard: the backslash is an escape character in the Java String class.&lt;/p&gt;

&lt;p&gt;Given you created everything accordingly, you can use some &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/select.html"&gt;DQL&lt;/a&gt; and explore your data!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NiuF6-jE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xsixakf3k6zqrruehj0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NiuF6-jE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xsixakf3k6zqrruehj0o.png" alt="It shows a query editor with a simple DQL statement to retrieve the first 10 rows." title="AWS Athena Query Editor." width="880" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This approach to consulting data is compelling, but it's worth mentioning that it can become expensive pretty quickly though. Also, it depends on the requirements and how a person will use it. Therefore some care with &lt;a href="https://aws.amazon.com/blogs/big-data/top-10-performance-tuning-tips-for-amazon-athena/"&gt;performance concerns&lt;/a&gt; is fundamental.&lt;/p&gt;

&lt;p&gt;Posted listening to &lt;a href="https://youtu.be/Bh_rCiU-9_A"&gt;Shout, Disturbed&lt;/a&gt; 🎶.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>serverless</category>
      <category>sql</category>
    </item>
    <item>
      <title>Discover issues with performance testing</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Mon, 16 May 2022 12:22:06 +0000</pubDate>
      <link>https://dev.to/willianantunes/discover-issues-with-performance-testing-189o</link>
      <guid>https://dev.to/willianantunes/discover-issues-with-performance-testing-189o</guid>
      <description>&lt;p&gt;It's incredible when your back-end project is well implemented in terms of unit and integration tests, covering most of the business rules that your application should respect to deliver what is expected for those who will use it. Now the moment has come! You think it's prepared to be deployed in production, and when you do, you start seeing some problems 😵‍💫 such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The cache is not working correctly.&lt;/li&gt;
&lt;li&gt;The REST API is too slow for most of the requests.&lt;/li&gt;
&lt;li&gt;The database uses 100% of the CPU during peak hours, but it seems dead even in some basic circumstances.&lt;/li&gt;
&lt;li&gt;Some users receive data from other users, which could denote race conditions (or perhaps even worse things).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One way to mitigate these issues (and many others possibly) is doing &lt;a href="https://en.wikipedia.org/wiki/Software_performance_testing"&gt;performance testing&lt;/a&gt;. I'm not saying it will save you. Instead, it's another layer of protection that you can have beyond a &lt;a href="https://en.wikipedia.org/wiki/Code_review"&gt;code review&lt;/a&gt; process, let's say. But, of course, it may have some problem if it's not correctly implemented, so it will all depend. Here's an image that illustrates some gains that you have depending on which approach you're using (there are many, but I'm showing just some):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i47id-oY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gksp7bjqrcw6wgpgmhaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i47id-oY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gksp7bjqrcw6wgpgmhaf.png" alt="An almost pyramid format showing three approaches to test you application." title="The somewhat test pyramid." width="880" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is known as the &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html"&gt;test pyramid&lt;/a&gt;. As you can see above, you can identify bottlenecks with performance testing and some other situations. Before doing anything to create a test plan, it's a good idea to start with the &lt;a href="https://en.wikipedia.org/wiki/Functional_requirement"&gt;functional requirements&lt;/a&gt; of your project, if it has one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gathering functional requirements
&lt;/h2&gt;

&lt;p&gt;Before creating your performance testing script, it's a good idea to start answering how many requests your application should handle (in the case of a REST API), given its business requirements. To illustrate, imagine that your application is an ad-hoc one specially built to deal with incoming requests from your partners, and it should be capable of working with 3,500 requests per minute. In addition to that, your app should answer each request until the limit of 500 ms. There we go! We have something to start our script 😄. By the way, I'm roughly speaking here. I'm just giving a fictional example. Sometimes you might not even have this information 😬, which is why some measurements are required to retrieve that first. You can check out many cases from the &lt;a href="https://en.wikipedia.org/wiki/Software_performance_testing#Setting_performance_goals"&gt;setting performance goal&lt;/a&gt; on the Wikipedia article about the related topic.&lt;/p&gt;

&lt;p&gt;Now, if you are in a condition where you have nothing, literally, then it's a good idea to start with the APDEX (Application Performance Index); besides, even if you have many cases, it's always fundamental to use APDEX.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apdex: Measure user satisfaction
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://www.apdex.org/index.php/history/"&gt;the APDEX official website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Apdex methodology was first described by Peter Sevcik in 2004. The objective was to have a simple and uniform open standard to report on application performance regardless of how it was measured.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following is from &lt;a href="https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/"&gt;Newrelic&lt;/a&gt; (which I think it's easier to understand):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's a simplified Service Level Agreement (SLA) solution that helps you see how satisfied users are with your app through metrics such as Apdex score and dissatisfaction percentage instead of easily skewed traditional metrics such as average response time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we understand a few of what it is, which tool can we use? Furthermore, how can we apply it in our working flow 🤔?&lt;/p&gt;

&lt;h2&gt;
  
  
  JMeter
&lt;/h2&gt;

&lt;p&gt;There are many tools out there like &lt;a href="https://github.com/gatling/gatling"&gt;Gatling&lt;/a&gt;, &lt;a href="https://github.com/locustio/locust"&gt;Locust&lt;/a&gt;, and &lt;a href="https://github.com/k6io/k6"&gt;K6&lt;/a&gt;, but here I'll explain just a bit of &lt;a href="https://github.com/apache/jmeter"&gt;JMeter&lt;/a&gt;. It is totally visual and pretty easy to follow and understand 🤗; it also includes &lt;a href="https://jmeter.apache.org/usermanual/generating-dashboard.html#overview"&gt;a module that generates dashboards&lt;/a&gt; that gives us an APDEX result, which is what we want. By default, &lt;a href="https://github.com/willianantunes/personal-environment/blob/bd5c47f61990b973dd15eb51c21ff1078e99f58b/personal_environment/software_engineering_packages_and_tools.sh#L11"&gt;I have it on my system&lt;/a&gt;; you can use the same idea or simply download it from &lt;a href="https://jmeter.apache.org/"&gt;the official website&lt;/a&gt; and run it afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample project and creating a script
&lt;/h3&gt;

&lt;p&gt;We'll use &lt;a href="https://github.com/willianantunes/tic-tac-toe-csharp-playground/"&gt;the REST API Version of the Tic Tac Toe game&lt;/a&gt; that I have written using C#! Now let's see the performance test script. Ah! It's worth mention that I won't explain in detail each configuration step. JMeter has an excellent assistant feature. You just add a resource and click on the help icon. It will redirect you to the manual related to the same resource you added. It's awesome 😛. Thus, if you don't understand a given component, either go to the help page or comment below that I will happily help you 🤟.&lt;/p&gt;

&lt;p&gt;OK! Download &lt;a href="https://github.com/willianantunes/tic-tac-toe-csharp-playground/blob/4fad3a703e3eb58b6cf4f25d9fed546cc310caa1/tests/PerformanceTesting/jmeter-test-plan.jmx"&gt;this file&lt;/a&gt; and open it on your JMeter. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7hdkXnBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3qxftjdlidpqqwaev2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7hdkXnBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3qxftjdlidpqqwaev2k.png" alt="The current configuration of the thread group used by the performance testing." title="JMeter Thread Group." width="579" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thread group is configured to meet our fictional functional requirement, including a safe margin. Thus we have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;775 users (threads). Each user will execute 10 requests (see the green dropper icon symbolizing an HTTP request), which means we will have 7750 samples.&lt;/li&gt;
&lt;li&gt;All the users will make their first request in 60 seconds. After this ramp-up, the test will be fully up to speed.&lt;/li&gt;
&lt;li&gt;Each user can live up to 120 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, JMeter has the following parameters configured, which are used for &lt;a href="https://en.wikipedia.org/wiki/Apdex#Apdex_method"&gt;the APDEX formula&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apdex_satisfied_threshold&lt;/strong&gt;: Sets the satisfaction threshold for the APDEX calculation in ms. By default is 500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apdex_tolerated_threshold&lt;/strong&gt;: Sets the tolerance threshold for the APDEX calculation in ms. By default is 1500.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we run it (see &lt;a href="https://github.com/willianantunes/tic-tac-toe-csharp-playground/blob/4fad3a703e3eb58b6cf4f25d9fed546cc310caa1/scripts/start-performance-testing.sh"&gt;this script&lt;/a&gt;), we may receive the following result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zJxtx-E7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h538v4azvw6z3g2byit6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zJxtx-E7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h538v4azvw6z3g2byit6.png" alt="A table containing the APDEX result." title="APDEX result." width="880" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that our test was asserted as expected 🚀! So now we get back to the question I wrote here previously: how do we implement it in our workflow? Our next topic 👀!&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple workflow to start with
&lt;/h2&gt;

&lt;p&gt;I believe half a loaf is better than none. So starting from this idea, with the help of containers, given our performance testing script is done and well tested, we can create a &lt;a href="https://github.com/willianantunes/tic-tac-toe-csharp-playground/blob/4fad3a703e3eb58b6cf4f25d9fed546cc310caa1/docker-compose.yaml#L126"&gt;compose service&lt;/a&gt; to execute the test plan so that the result is available in some format. This output can be used to assert that the result matches our expectations. Look at this simple C# code that we can use to verify the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerformanceTestingResultTest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;JObject&lt;/span&gt; &lt;span class="n"&gt;_generalStatistics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;JObject&lt;/span&gt; &lt;span class="n"&gt;_applicationPerformanceIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SkippableFact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Should have no error in all requests"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestScenario1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nf"&gt;PreparePropertiesAndSkipTestIfNeeded&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert&lt;/span&gt;
        &lt;span class="n"&gt;_generalStatistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_generalStatistics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;errorCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"errorCount"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="n"&gt;errorCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SkippableFact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Should APDEX result be greater than 0.9 to all evaluated requests"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestScenario2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nf"&gt;PreparePropertiesAndSkipTestIfNeeded&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawOverallResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_applicationPerformanceIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"overall"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawRequestsResults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_applicationPerformanceIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert&lt;/span&gt;
        &lt;span class="n"&gt;rawOverallResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;NotBeNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;rawRequestsResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rawRequestsResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apdexResultForGivenRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="n"&gt;apdexResultForGivenRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;BeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.9&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;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PreparePropertiesAndSkipTestIfNeeded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Skip test if required&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EVALUATE_PERFORMANCE_TESTING"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;shouldNotSkipTest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;If&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shouldNotSkipTest&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"It was informed to be skipped"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// General statistics&lt;/span&gt;
        &lt;span class="n"&gt;_generalStatistics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFileAsDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tests-jmeter/statistics.json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// APDEX sadly is not in only one file, we should extract it&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lineWhereApdexResultIs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"createTable($(""#apdexTable"")"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;regexPatternToCaptureResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@", ({"".+]}), "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dashBoardFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerableFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tests-jmeter/content/js/dashboard.js"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dashBoardFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lineWhereApdexResultIs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regexPatternToCaptureResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawApdexResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;_applicationPerformanceIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawApdexResult&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something is wrong, the test fails. Thus we can use it to break on purpose our pipeline, avoiding bugs in production 🤩. Looking at the code above, you may have noticed &lt;a href="https://github.com/willianantunes/tic-tac-toe-csharp-playground/blob/4fad3a703e3eb58b6cf4f25d9fed546cc310caa1/tests/PerformanceTesting/PerformanceTestingResultTest.cs#L52"&gt;an environment variable&lt;/a&gt; on it. It can trigger the test execution or not because you normally wouldn't have the outputs from JMeter. That is especially important when you want to control whether this test should be executed or not.&lt;/p&gt;

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

&lt;p&gt;When I started my career as a developer, I wish I'd had this advice right from the start, so that's why I created this post. JMeter can help you learn even more programming topics such as concurrency, parallelism, threading, coroutines, and many others. In addition, it opens a new broad set of tools that are at our disposal. I hope it can help you in any way and, as always, feel welcome to post any question you have.&lt;/p&gt;

&lt;p&gt;Posted listening to &lt;a href="https://youtu.be/8tI1_KlO6xI"&gt;Time (Clock Of The Heart), Culture Club&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jmeter</category>
      <category>performance</category>
      <category>apdex</category>
    </item>
    <item>
      <title>RegEx, Data Classes and Type Hints with Python: Learning from tweet text</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Mon, 02 May 2022 12:28:14 +0000</pubDate>
      <link>https://dev.to/willianantunes/regex-data-classes-and-type-hints-with-python-learning-from-tweet-text-3de0</link>
      <guid>https://dev.to/willianantunes/regex-data-classes-and-type-hints-with-python-learning-from-tweet-text-3de0</guid>
      <description>&lt;p&gt;Before we jump into the code, let's first define our fictional business rule ✍:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After the mention, the word "concedes" must be present. It is identified as the trigger keyword.&lt;/li&gt;
&lt;li&gt;After the keyword, at least one hashtag must be present. The tweet may contain more than one if the user wishes to.&lt;/li&gt;
&lt;li&gt;The hashtag must be written using &lt;a href="https://en.wikipedia.org/wiki/Camel_case"&gt;Upper Camel Case (also known as Pascal Case)&lt;/a&gt; to identify as separate words to create slugs. For instance, &lt;code&gt;#VillainJafar&lt;/code&gt; turns into &lt;code&gt;villain-jafar&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus if we receive something like &lt;code&gt;@GenieOfTheLamp concedes #FirstWish #SecondWish&lt;/code&gt; we should get two hashtags and their slugs: &lt;code&gt;first-wish&lt;/code&gt; and &lt;code&gt;second-wish&lt;/code&gt;, as well as if the tweet is valid or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting from the test
&lt;/h2&gt;

&lt;p&gt;It's pretty typical to start from the tests when you have a well-defined business rule. This approach of development is known as TDD. If we consider the sample given with &lt;code&gt;#VillainJafar&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_evaluate_text_as_valid_given_keyword_and_one_hashtag_presence&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;sample_tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@GenieOfTheLamp concedes #VillainJafar"&lt;/span&gt;
    &lt;span class="c1"&gt;# Act
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hashtags&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"VillainJafar"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slugs&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"villain-jafar"&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 also use the second one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_evaluate_text_as_valid_given_keyword_and_two_hashtags_presence&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;sample_tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@GenieOfTheLamp concedes #FirstWish #SecondWish"&lt;/span&gt;
    &lt;span class="c1"&gt;# Act
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hashtags&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"FirstWish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SecondWish"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slugs&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"first-wish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"second-wish"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's think in terms of flows that don't match what is expected. Some test cases that we can assert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The text is missing the keyword.&lt;/li&gt;
&lt;li&gt;The text has the wrong keyword.&lt;/li&gt;
&lt;li&gt;The text is none or empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can write tests like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_evaluate_text_as_invalid_given_missing_keyword&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;sample_tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@GenieOfTheLamp #FirstWish #SecondWish"&lt;/span&gt;
    &lt;span class="c1"&gt;# Act
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_evaluate_text_as_invalid_given_wrong_keyword&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;sample_tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@GenieOfTheLamp creates #FirstWish #SecondWish"&lt;/span&gt;
    &lt;span class="c1"&gt;# Act
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_throw_exception_when_text_is_none_or_empty&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;sample_tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@GenieOfTheLamp creates #FirstWish #SecondWish"&lt;/span&gt;
    &lt;span class="c1"&gt;# Act and assert
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Empty String case
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# None as an argument
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's jump into the concrete implementation! By the way, as you may have noticed, I'm using &lt;code&gt;pytest&lt;/code&gt;, but you can see how it would be with &lt;code&gt;unittest&lt;/code&gt; if you check &lt;a href="https://github.com/willianantunes/tutorials/blob/master/2021/06/regex-dataclasses-with-python-learning-from-tweet-text/tests/test_unittest_text_evaluator.py"&gt;the repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining method contract using type hints and data classes
&lt;/h2&gt;

&lt;p&gt;As you've seen in our tests, we didn't define our method contract. Let's start with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We defined the argument's type, but what about the returned type? That's a good situation to use &lt;a href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses"&gt;Data Classes&lt;/a&gt;! From our tests, we know what the expected behavior is. Let's define our data class and use it as the returning type of our method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The defined data class has the parameter &lt;code&gt;Frozen&lt;/code&gt; as true, which means that its instance will be read-only, and any assignment attempt to fields will generate an exception. &lt;a href="https://docs.python.org/3/library/dataclasses.html#frozen-instances"&gt;Learn more about Frozen Instances in Python's documentation&lt;/a&gt;. It has two fields as optional and with value none. It allows us to instantiate the data class like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"FirstWish"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"first-wish"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method contract is adequate, thanks to &lt;a href="https://docs.python.org/3/library/typing.html#module-typing"&gt;type hints&lt;/a&gt;. We can take advantage of it and use &lt;code&gt;mypy&lt;/code&gt; to guarantee all the methods contracts, but let's configure it in the end. Let's dive into our method's implementation first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method implementation and the RE module
&lt;/h2&gt;

&lt;p&gt;To have a valid regular expression, I used the site &lt;a href="https://regexr.com/"&gt;RegExr&lt;/a&gt; to create it. If you don't know how it works, I recommend the book &lt;a href="https://www.amazon.com.br/dp/8575224743/"&gt;Piazinho&lt;/a&gt;. &lt;a href="https://regexr.com/5u3m2"&gt;The regex pattern&lt;/a&gt; can be defined in our Python code like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;regex_valid_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;".* ?@GenieOfTheLamp concedes ( ?#([a-zA-Z]{1,}))+$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.python.org/3/library/re.html#re.compile"&gt;The method compile&lt;/a&gt; does the following according to Python documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compile a regular expression pattern into a regular expression object, which can be used for matching using its match(), search() and other methods.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll use &lt;a href="http://regexr.com/5u3m8"&gt;another regex pattern&lt;/a&gt; to transform our hashtag into a slug. We just need to identify the upper case letters, ignoring the first matched group to avoid inserting a dash at the beginning. Now that we have our regex expressions determined, it's essential to clean our text before running them. Wrapping everything up, here's our code so far:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="n"&gt;regex_valid_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;".* ?@GenieOfTheLamp concedes ( ?#([a-zA-Z]{1,}))+$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;regex_camel_case_conversion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;"(?!^)([A-Z]+)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cleaned_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strip_left_and_right_sides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cleaned_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;TextIsFalsyException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use the methods &lt;a href="https://docs.python.org/3/library/re.html#re.Pattern.match"&gt;match&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/re.html#re.Pattern.findall"&gt;findall&lt;/a&gt;, and &lt;a href="https://docs.python.org/3/library/re.html#re.Pattern.sub"&gt;sub&lt;/a&gt;; they are all related to regex patterns. The first will check if the tweet is valid, the second will retrieve all matched hashtags with the help of &lt;a href="https://regexr.com/5u59n"&gt;another regex pattern&lt;/a&gt;, and the last one will replace a given match to a particular character; in our case, to transform the hashtag to slug. This is the final code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="n"&gt;pattern_valid_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;".* ?@GenieOfTheLamp concedes (#[a-zA-Z]{1,} ?)+$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pattern_hashtags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;"(#[a-zA-Z]{1,})"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pattern_camel_case_conversion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;"(?!^)([A-Z]+)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;TextDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cleaned_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strip_left_and_right_sides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cleaned_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;TextIsFalsyException&lt;/span&gt;

    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern_valid_text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;all_hashtags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern_hashtags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;hashtags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;slugs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hashtag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_hashtags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashtag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;almost_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern_camel_case_conversion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;"-\1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;almost_slug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashtags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the tests
&lt;/h2&gt;

&lt;p&gt;We just need to replace the following snippets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Act
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Act and assert
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Empty String case
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# None as an argument
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Act
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_tweet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Act and assert
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextIsFalsyException&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;check_text_and_grab_its_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we run the tests 🚀:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oBOLDt8c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2zo57lwolsnk9ulylal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oBOLDt8c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2zo57lwolsnk9ulylal.png" alt="It shows a list of tests containing 5 test cases. All of them are running successfully." title="All tests passing." width="721" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: static analysis with mypy
&lt;/h2&gt;

&lt;p&gt;Nowadays, a production-ready Python project must have a static type checker. You can gradually type your project, then make it safer to work with and ship to your environment. One that you can use is &lt;code&gt;mypy&lt;/code&gt;; what is it according to &lt;a href="https://github.com/python/mypy#what-is-mypy"&gt;the documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mypy is an optional static type checker for Python. You can add type hints (&lt;a href="https://www.python.org/dev/peps/pep-0484/"&gt;PEP 484&lt;/a&gt;) to your Python programs, and use mypy to type check them statically. Find bugs in your programs without even running them! You can mix dynamic and static typing in your programs. You can always fall back to dynamic typing when static typing is not convenient, such as for legacy code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've found many bugs with the help of &lt;code&gt;mypy&lt;/code&gt; since I started using it two years ago, so let's make our project a bit better. Let's apply &lt;a href="https://github.com/psf/black"&gt;black&lt;/a&gt; and &lt;a href="https://github.com/PyCQA/isort"&gt;isort&lt;/a&gt; also. Here's the script to evaluate our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;TARGET_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;regex_dataclasses
&lt;span class="nv"&gt;TARGET_TEST_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tests
&lt;span class="nv"&gt;TARGET_FOLDERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_PROJECT&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TARGET_TEST_PROJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"######## ISORT..."&lt;/span&gt;
isort &lt;span class="nv"&gt;$TARGET_FOLDERS&lt;/span&gt; &lt;span class="nt"&gt;--check-only&lt;/span&gt; &lt;span class="nt"&gt;--diff&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"######## BLACK..."&lt;/span&gt;
black &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nt"&gt;--diff&lt;/span&gt; &lt;span class="nv"&gt;$TARGET_FOLDERS&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"######## MYPY..."&lt;/span&gt;
&lt;span class="c"&gt;# mypy will only target the project folder&lt;/span&gt;
mypy &lt;span class="nv"&gt;$TARGET_PROJECT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run it, here's the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ ./scripts/start-lint.sh
######## ISORT...
######## BLACK...
All done! ✨ 🍰 ✨
7 files would be left unchanged.
######## MYPY...
Success: no issues found in 4 source files
(regex-dataclasses-with-python-learning-from-tweet-text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;If you've been working with strong typing languages and dynamic ones for a time, once you start &lt;a href="https://en.wikipedia.org/wiki/Gradual_typing"&gt;gradually typing&lt;/a&gt; projects, you'll notice how fast you can produce and deliver good quality code. Instead of typing everything, you can create types and apply them to important places on your code. It's been some years that I understood that 100% of pure dynamic code or typed one is a bad thing, depending, of course, in which context you are. Static type checkers enable Python projects to support gradual typing for our luck, and data classes are a fantastic way to help us with it. This technique must be used wisely, or more problems are brought up actually, though.&lt;/p&gt;

&lt;p&gt;You can take a look at &lt;a href="https://github.com/willianantunes/tutorials/tree/master/2021/06/regex-dataclasses-with-python-learning-from-tweet-text"&gt;the whole project on GitHub&lt;/a&gt; 🤟. By the way, how about if you modify &lt;code&gt;TextDetails&lt;/code&gt; class? Perhaps you can create a dedicated data class to contain the hashtag and its slug and then produce a list of it. There are many ways! Try it out 😜!&lt;/p&gt;

&lt;p&gt;Posted listening to &lt;a href="https://youtu.be/cFH5JgyZK1I"&gt;It's My Life, Talk Talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>dataclasses</category>
      <category>regex</category>
      <category>gradualtyping</category>
    </item>
    <item>
      <title>C# Web API: How to call your endpoint through integration tests</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Tue, 13 Jul 2021 13:13:08 +0000</pubDate>
      <link>https://dev.to/willianantunes/c-web-api-how-to-call-your-endpoint-through-integration-tests-li1</link>
      <guid>https://dev.to/willianantunes/c-web-api-how-to-call-your-endpoint-through-integration-tests-li1</guid>
      <description>&lt;p&gt;It's fantastic when you have all of your unit tests returning green signs everywhere. Still, when you execute your project, it raises an error because an infrastructure setup is wrong, making it impossible to run your &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Web API&lt;/a&gt; properly 😑. It can be how you set up your logs, database, providers, well, many other things. One approach to fix it or minimize its impact is through integration tests. So how can you do a quick setup for that 🤔? By the way, in this blog post, we're going to consider the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-5.0?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;.NET Core 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xunit/xunit" rel="noopener noreferrer"&gt;xUnit.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/moq/moq4" rel="noopener noreferrer"&gt;Moq&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Describing the sample project
&lt;/h2&gt;

&lt;p&gt;Here's the image that shows a sample flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8s9e5jljbpvy94c6elhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8s9e5jljbpvy94c6elhi.png" title="Sample flow of execution" alt="This flow has a person who calls the API, which does something and then returns the answer to the user."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is composed of three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user calls the endpoint &lt;strong&gt;&lt;em&gt;/api/v1/movies&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The application will do fake processing.&lt;/li&gt;
&lt;li&gt;A random movie is returned to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To take care of this business rule, here's our controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/v1/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MoviesController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MoviesController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt; &lt;span class="n"&gt;filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filmSpecialist&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="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Let me ask the film specialist..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SuggestSomeMovie&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Suggested movie: {Movie}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;movie&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;Who will be responsible for doing fake processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilmSpecialist&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Films&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RoboCop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10/08/1987"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Thriller"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Science Fiction"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"1h 42m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The Matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"05/21/1999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Science Fiction"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"2h 16m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Soul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"12/25/2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Family"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Animation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Comedy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Drama"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Music"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Fantasy"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"1h 41m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Space Jam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"12/25/1996"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Adventure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Animation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Comedy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Family"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"1h 28m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Aladdin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"07/03/1993"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Animation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Family"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Adventure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Fantasy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Romance"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"1h 28m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The World of Dragon Ball Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"01/21/2000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"20m"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt; &lt;span class="nf"&gt;SuggestSomeMovie&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OKAY! Which film will I suggest 🤔"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filmIndexThatIWillSuggest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Films&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Will suggest the film with index {FilmIndex}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filmIndexThatIWillSuggest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Films&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;filmIndexThatIWillSuggest&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;Let's first do the unit testing to ensure that our methods contracts are being respected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting from unit testing
&lt;/h2&gt;

&lt;p&gt;Here we'll do a simple unit test on the service responsible for returning a random movie. We can write something like the following, as it's not our focus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilmSpecialistTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FilmSpecialist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ShouldReturnRandomMovieWhenAsked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Act&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;suggestedMovie&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SuggestSomeMovie&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;expectedTiles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"RoboCop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"The Matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Soul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Space Jam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Aladdin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"The World of Dragon Ball Z"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;suggestedMovie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;BeOneOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedTiles&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;h2&gt;
  
  
  Making an actual HTTP request to our API
&lt;/h2&gt;

&lt;p&gt;To call our endpoint, we can use a &lt;a href="https://xunit.net/docs/shared-context#class-fixture" rel="noopener noreferrer"&gt;class fixture&lt;/a&gt; with the help of &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;WebApplicationFactory&lt;/a&gt; (know more about it at the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0#basic-tests-with-the-default-webapplicationfactory" rel="noopener noreferrer"&gt;section Basic tests with the default WebApplicationFactory&lt;/a&gt; in &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0" rel="noopener noreferrer"&gt;Integration tests in ASP.NET Core&lt;/a&gt; guide). In our class test constructor, we can use the factory to create a &lt;strong&gt;&lt;em&gt;HttpClient&lt;/em&gt;&lt;/strong&gt;, hence allowing us to do HTTP calls to our endpoint. Moreover, let's say you'd like to replace an injected service with a mock: you can do that through &lt;code&gt;ConfigureTestServices&lt;/code&gt;. To illustrate a complete example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MoviesControllerITests&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClassFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MoviesControllerITests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_filmSpecialist&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Of&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_httpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithWebHostBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0#inject-mock-services&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureTestServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFilmSpecialist&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
                &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAddTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_filmSpecialist&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="nf"&gt;CreateClient&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="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ShouldCreateGameGivenFirstMovementIsBeingExecuted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;requestPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/api/v1/movies"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;movieToBeSuggested&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Schindler's List"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"12/31/1993"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Drama"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"History"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"War"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;"3h 15m"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SuggestSomeMovie&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movieToBeSuggested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Verifiable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Act&lt;/span&gt;
        &lt;span class="kt"&gt;var&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;await&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestPath&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="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Assert&lt;/span&gt;
        &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;BeEquivalentTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movieToBeSuggested&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_filmSpecialist&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Verify&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;By the way, to use &lt;code&gt;WebApplicationFactory&lt;/code&gt;, you must install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft.AspNetCore.Mvc.Testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty simple 🤗. I'll leave it as it is, but we could abstract our integration test to avoid creating a &lt;strong&gt;&lt;em&gt;HttpClient&lt;/em&gt;&lt;/strong&gt; every time for each of our class tests 😏.&lt;/p&gt;

&lt;h2&gt;
  
  
  To end things off
&lt;/h2&gt;

&lt;p&gt;I think you can get enormous benefits from doing a cheap integration test sometimes because, as I told you at the beginning, it can almost guarantee that your code will be shipped as expected at the infrastructure layer. In this article, I gave a somewhat simple example, but things can be more challenging, let's say when it comes to broker connection — the subject of another blog entry 😜.&lt;/p&gt;

&lt;p&gt;You can consult the code I showed here on &lt;a href="https://github.com/willianantunes/tutorials/tree/master/2021/05/c-sharp-web-api-how-to-endpoint-it" rel="noopener noreferrer"&gt;this GitHub repository&lt;/a&gt;. You can use Docker Compose to &lt;a href="https://github.com/willianantunes/tutorials/blob/332bdcee8385ceb27cecd7c95a1ec6dadfc4cfe4/2021/05/c-sharp-web-api-how-to-endpoint-it/docker-compose.yaml#L7" rel="noopener noreferrer"&gt;run the project&lt;/a&gt; as well as &lt;a href="https://github.com/willianantunes/tutorials/blob/332bdcee8385ceb27cecd7c95a1ec6dadfc4cfe4/2021/05/c-sharp-web-api-how-to-endpoint-it/docker-compose.yaml#L19" rel="noopener noreferrer"&gt;execute its tests&lt;/a&gt;. Check the &lt;a href="https://github.com/willianantunes/tutorials/blob/master/2021/05/c-sharp-web-api-how-to-endpoint-it/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Posted listening to &lt;a href="https://www.youtube.com/watch?v=LvdLovAaYzM" rel="noopener noreferrer"&gt;Toy Soldiers, Martika&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>testing</category>
    </item>
    <item>
      <title>GKE Ingress: How to configure IPv4 and IPv6 addresses</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Tue, 01 Jun 2021 18:34:41 +0000</pubDate>
      <link>https://dev.to/willianantunes/gke-ingress-how-to-configure-ipv4-and-ipv6-addresses-3a52</link>
      <guid>https://dev.to/willianantunes/gke-ingress-how-to-configure-ipv4-and-ipv6-addresses-3a52</guid>
      <description>&lt;p&gt;In the past, when you were about to release a website to be accessed worldwide, you usually would have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A domain where your users would use to access your website.&lt;/li&gt;
&lt;li&gt;Reserved IPv4 public address.&lt;/li&gt;
&lt;li&gt;A configuration that consists of a DNS entry of type A mapping your IPv4 address to your domain.&lt;/li&gt;
&lt;li&gt;Some infrastructure stuff that would deliver your website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nowadays, we must have an IPv6 address as well! There are certain areas in the world where IPv4 connections only are not supported anymore due to addresses exhaustion. Let's see how we can do it using Terraform to apply it on GKE Ingress!&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserving IPv4 and IPv6 addresses
&lt;/h2&gt;

&lt;p&gt;We can use the resource &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address"&gt;&lt;code&gt;google_compute_global_address&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_global_address"&lt;/span&gt; &lt;span class="s2"&gt;"gke_ingress_ipv6"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"external-address-gke-ingress-ipv6"&lt;/span&gt;
  &lt;span class="n"&gt;ip_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IPV6"&lt;/span&gt;
  &lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EXTERNAL"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_global_address"&lt;/span&gt; &lt;span class="s2"&gt;"gke_ingress_ipv4"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"external-address-gke-ingress-ipv4"&lt;/span&gt;
  &lt;span class="n"&gt;ip_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IPV4"&lt;/span&gt;
  &lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EXTERNAL"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After their creation, you can check them out by accessing &lt;a href="https://console.cloud.google.com/networking/addresses/list"&gt;VPC Network and then External IP addresses through the Web Console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yBvXMG-2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r61skmvd7sipn405oyp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yBvXMG-2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r61skmvd7sipn405oyp9.png" alt="It shows a list of external IP addresses containing two rows."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating certificate managers
&lt;/h2&gt;

&lt;p&gt;The following &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_managed_ssl_certificate"&gt;GCP resource&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It allows us to create certificate managers. To illustrate our fictional sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_managed_ssl_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"jasmine_certs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;beta&lt;/span&gt;

  &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jasmine-certs"&lt;/span&gt;

  &lt;span class="n"&gt;managed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;domains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"agrabah.com"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have everything to create our Ingress 🚀.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the GKE Ingress
&lt;/h2&gt;

&lt;p&gt;Here we'll follow the guide &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress"&gt;Configuring Ingress for external load balancing&lt;/a&gt;. First, let's start understanding which annotations we must use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static IP address
&lt;/h3&gt;

&lt;p&gt;To use only one of the reserved addresses, we should use the annotation &lt;code&gt;kubernetes.io/ingress.global-static-ip-name&lt;/code&gt;. Its description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use this annotation to specify that the load balancer should use a static external IP address that you previously created.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the way, I wrote only one address because, sadly, &lt;a href="https://github.com/kubernetes/ingress-gce/issues/87#issue-283984077"&gt;this annotation supports only one at the current time&lt;/a&gt;. We will circumvent that later 😉.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate managers
&lt;/h3&gt;

&lt;p&gt;There are two ways to bind a certificate manager with the Ingress. If you see the guide &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/managed-certs"&gt;Using Google-managed SSL certificates&lt;/a&gt;, you will see a &lt;code&gt;ManagedCertificate&lt;/code&gt; resource; that's not our case. As we created our certificate managers on GCP, we must use the &lt;code&gt;ingress.gcp.kubernetes.io/pre-shared-cert&lt;/code&gt; annotation. Its specification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can upload certificates and keys to your Google Cloud project. Use this annotation to reference the certificates and keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We created only one certificate manager, but let's suppose we had two certificate managers, this annotation would have the following value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"ingress.gcp.kubernetes.io/pre-shared-cert"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cert_manager_1,cert_manager_2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Terraform manifest
&lt;/h3&gt;

&lt;p&gt;Wrapping everything up, this is our resource &lt;a href="https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/ingress"&gt;&lt;code&gt;kubernetes_ingress&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_ingress"&lt;/span&gt; &lt;span class="s2"&gt;"sample_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sample-ingress"&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;

    &lt;span class="n"&gt;annotations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"kubernetes.io/ingress.global-static-ip-name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"external-address-gke-ingress-ipv4"&lt;/span&gt;
      &lt;span class="s2"&gt;"ingress.gcp.kubernetes.io/pre-shared-cert"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jasmine-certs"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"agrabah.com"&lt;/span&gt;
      &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"agrabah-np-service"&lt;/span&gt;
            &lt;span class="n"&gt;service_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can execute &lt;code&gt;terraform apply&lt;/code&gt; followed by your confirmation. After its creation, if you run the command &lt;code&gt;kubectl -n production get ingress&lt;/code&gt;, you'll see something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ kubectl -n production get ingress
NAME               CLASS    HOSTS         ADDRESS             PORTS   AGE
sample-ingress     &amp;lt;none&amp;gt;   agrabah.com   34.X.X.X            80      42d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're almost there. Now we're at the part where we have to make our hands dirty 😬.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual configuration
&lt;/h3&gt;

&lt;p&gt;As this configuration hasn't been supported yet, we have two approaches: either &lt;a href="https://github.com/kubernetes/ingress-gce/issues/87#issuecomment-659270146"&gt;create two Ingresses&lt;/a&gt; or configure only one manually. Let's do the latter. When you create an Ingress, a native load balancer is automatically made for you. You can get its name through the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get ingress sample-ingress -o jsonpath='{.metadata.annotations.ingress\.kubernetes\.io/url-map}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's open it on &lt;a href="https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list"&gt;the page Load balancing in Network Services&lt;/a&gt;. You'll see the frontend table more or less like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zWwaPnyl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8m9yxkua54pcopf2aef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zWwaPnyl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8m9yxkua54pcopf2aef.png" alt="It shows a table of frontends configured to the load balancer."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click on edit and then click on frontend configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QTlmjOW4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3mn5w9gpnf7eujxy6zk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QTlmjOW4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3mn5w9gpnf7eujxy6zk.png" alt="You have four options to configure you load balancer. The one highlighted is the frontend one."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the panel &lt;strong&gt;Frontend configuration&lt;/strong&gt;, we can click on &lt;strong&gt;Add Frontend IP and port&lt;/strong&gt; and then configure two new entries for ports 80 and 443 for the IPv6 address that is missing. You can base your configuration following what has been set for you automatically. Sample:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nmz3wS69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5w3gyo1y0npynxyff495.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nmz3wS69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5w3gyo1y0npynxyff495.png" alt="It lists 4 items of the frontend configuration, including two rows that were configured as an example."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving the new setup, it should be working accordingly if you access your website either through IPv4 or IPv6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible caveats 🤏
&lt;/h2&gt;

&lt;p&gt;A friend of mine said that this setup wasn't working as expected two years ago because GKE would override what you had done manually. It's been more than one month that I released a project with this approach, and so far, so good. I create some new hosts and certificate managers on the Ingress, and GKE only applied the new configuration and left what had been set intact. Be careful and do your tests as well 👍.&lt;/p&gt;

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

&lt;p&gt;At the end of the blog entry where I posted about &lt;a href="https://www.willianantunes.com/blog/2021/05/gke-ingress-how-to-fix-a-502-bad-gateway-error/"&gt;how to fix a 502 error returned by GKE Ingress&lt;/a&gt;, I described an issue regarding health check configuration that has been opened for over three years. The one I mentioned here &lt;a href="https://github.com/kubernetes/ingress-gce/issues/87"&gt;to support multiple addresses it's been opened for over four years&lt;/a&gt;. I think GKE Ingress is a remarkable resource. It can help you quickly release an application using K8S and cloud-native features wrapped in abstracted manifests, but it seems a bit left aside in some aspects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/willianantunes/tutorials/tree/master/2021/05/ingress-ipv4-ipv6"&gt;You can check the entire code out on GitHub&lt;/a&gt;. As always, don't forget to execute &lt;code&gt;terraform destroy&lt;/code&gt; after your test! See you next time ✌.&lt;/p&gt;

&lt;p&gt;Posted listening to &lt;a href="https://www.youtube.com/watch?v=LvdLovAaYzM"&gt;Toy Soldiers, Martika&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>ingress</category>
      <category>gke</category>
      <category>terraform</category>
    </item>
    <item>
      <title>GKE Ingress: How to fix a 502 bad gateway error</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Fri, 28 May 2021 12:56:58 +0000</pubDate>
      <link>https://dev.to/willianantunes/gke-ingress-how-to-fix-a-502-bad-gateway-error-39ca</link>
      <guid>https://dev.to/willianantunes/gke-ingress-how-to-fix-a-502-bad-gateway-error-39ca</guid>
      <description>&lt;p&gt;Past few days, I was configuring a GKE cluster for my personal projects. Some of them had to be accessed through the internet; hence I created an ingress to do this job. Here's &lt;a href="https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/ingress" rel="noopener noreferrer"&gt;an example of ingress using Terraform&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_ingress"&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sample-ingress"&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;

    &lt;span class="n"&gt;annotations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"ingress.gcp.kubernetes.io/pre-shared-cert"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"spacejam-cert-manager"&lt;/span&gt;
      &lt;span class="s2"&gt;"kubernetes.io/ingress.global-static-ip-name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-honest-public-ip"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"spacejam.com"&lt;/span&gt;
      &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"spacejam-np-service"&lt;/span&gt;
            &lt;span class="n"&gt;service_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&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="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;The service named &lt;code&gt;spacejam-np-service&lt;/code&gt; was bound to a deployment like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spacejam-web-deployment&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spacejam-web-deployment&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spacejam-web-deployment&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spacejam-web-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/agrabah-project/spacejam:latest-prd&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spacejam-configmap&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health-check"&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
            &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that it does not have a readiness probe entry 🤔. After creating all of the manifests, I tried to access the host &lt;code&gt;spacejam.com&lt;/code&gt;, but I received a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502" rel="noopener noreferrer"&gt;502 error&lt;/a&gt; 😯.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the service running correctly?
&lt;/h2&gt;

&lt;p&gt;My first idea was to check the service. Is it healthy or not? The command &lt;code&gt;kubectl describe deployments spacejam-web-deployment&lt;/code&gt; returned that it was fine, then I ran another command &lt;code&gt;kubectl port-forward deployment/spacejam-web-deployment 8000:8000&lt;/code&gt; so I could access the service directly through &lt;code&gt;http://localhost:8000/health-check&lt;/code&gt;. Everything was working as expected. My second idea was to understand how the ingress checks if a service is healthy or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking backend health status and finding the root cause
&lt;/h3&gt;

&lt;p&gt;When you create an ingress, GKE will create a native load balancer afterward. You can know the LB's name through the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get ingress sample-ingress -o jsonpath='{.metadata.annotations.ingress\.kubernetes\.io/url-map}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I opened its configuration, and I saw that one of my backends was unhealthy 👀:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttrmsl1foels5myrrp4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttrmsl1foels5myrrp4e.png" alt="The image shows all the backend services of the ingress native load balancer."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I could understand the root cause of the error when I noticed which path the backend service was using:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98mcqj8c4yc415dbrj64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98mcqj8c4yc415dbrj64.png" alt="Health check attributes of a given backend service. The path attribute has the value of "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The targeted service returns 404 instead of 200 for the path &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding GKE behavior
&lt;/h2&gt;

&lt;p&gt;Looking over &lt;em&gt;&lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#overview" rel="noopener noreferrer"&gt;GKE Ingress for HTTP(S) Load Balancing&lt;/a&gt;&lt;/em&gt; guide, the section &lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#def_inf_hc" rel="noopener noreferrer"&gt;Default and inferred parameters&lt;/a&gt; shows us that the ingress will check the POD's spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="err"&gt;.readinessProbe.httpGet.path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then using it value to get the path to verify the service health, but if it's empty, it will use the default value &lt;code&gt;/&lt;/code&gt;. Moreover, this statement is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you expose one or more Services through an Ingress using the default Ingress controller, GKE creates a Google Cloud external HTTP(S) load balancer or a Google Cloud internal HTTP(S) load balancer. Both of these load balancers support multiple backend services on a single URL map. Each of the backend services corresponds to a Kubernetes Service, and each backend service must reference a Google Cloud health check. This health check is different from a Kubernetes liveness or readiness probe because the health check is implemented outside of the cluster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fixing the problem
&lt;/h2&gt;

&lt;p&gt;To quickly solve it, I added the following to my deployment manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health-check"&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
  &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterward, the backend service was updated to the correct path:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k4f4yhoe9i5e2cli35c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k4f4yhoe9i5e2cli35c.png" alt="Health check attributes of a given backend service. The path attribute has the value of "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing-up
&lt;/h2&gt;

&lt;p&gt;There is a considerable debate on this very topic in the issue &lt;a href="https://github.com/kubernetes/ingress-gce/issues/42" rel="noopener noreferrer"&gt;Ingress Healthcheck Configuration&lt;/a&gt; on &lt;a href="https://github.com/kubernetes/ingress-gce" rel="noopener noreferrer"&gt;kubernetes/ingress-gce&lt;/a&gt; repository. Looking through issues on GitHub is an excellent way to understand how an application or service is evolving. It's been more than three years, and it's not resolved yet.&lt;/p&gt;

&lt;p&gt;In my next blog entry ✍, I'll explain how I configured IPv4 and IPv6 public addresses using only one ingress on GKE. Till next time!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>ingress</category>
      <category>gke</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Production-ready shell startup scripts: The Set Builtin</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Thu, 27 May 2021 12:09:21 +0000</pubDate>
      <link>https://dev.to/willianantunes/production-ready-shell-startup-scripts-the-set-builtin-48dm</link>
      <guid>https://dev.to/willianantunes/production-ready-shell-startup-scripts-the-set-builtin-48dm</guid>
      <description>&lt;p&gt;When you build an application and make it available through container technology, either you have an &lt;a href="https://docs.docker.com/engine/reference/builder/#entrypoint"&gt;ENTRYPOINT&lt;/a&gt; or &lt;a href="https://docs.docker.com/engine/reference/builder/#cmd"&gt;CMD&lt;/a&gt; instructions at the end of its Dockerfile. Depending on which framework you're using and some requirements you have, sometimes it's better to have a bash script responsible for running your project. When it's available, generally, you'll see a bunch of commands that are executed, like &lt;a href="https://github.com/willianantunes/django-multiple-schemas/blob/9b44d21c31c51b86d7089ac429fff8a14f8899b6/scripts/start-development.sh"&gt;the following script I created for the project Django Multiple Schemas&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

python manage.py makemigrations
python manage.py migrate
python manage.py seed &lt;span class="nt"&gt;--create-super-user&lt;/span&gt;

python manage.py runserver 0.0.0.0:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's suppose the command &lt;code&gt;python manage.py migrate&lt;/code&gt; failed its execution. What would happen 🤔? The answer is counter-intuitive, but the script would run fine, even with the error 🤯. It would execute the command &lt;code&gt;python manage.py seed --create-super-user&lt;/code&gt; followed by &lt;code&gt;python manage.py runserver 0.0.0.0:8000&lt;/code&gt;. How to fix that? Let's know a bit about some arguments of &lt;a href="https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin"&gt;The Set Builtin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exit immediately if any command returns a non-zero status
&lt;/h2&gt;

&lt;p&gt;Let's suppose the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

python the_set_builtin/sample_1.py
python the_set_builtin/sample_2_force_error.py
python the_set_builtin/sample_3.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we execute it, we'll have the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-argument-e-with-bug
Creating the-set-builtin_why-use-argument-e-with-bug_1 ... done
Attaching to the-set-builtin_why-use-argument-e-with-bug_1
why-use-argument-e-with-bug_1  | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-e-with-bug_1  | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-e-with-bug_1  | Let me force an error 👀
why-use-argument-e-with-bug_1  | I am the /app/the_set_builtin/sample_3.py!
the-set-builtin_why-use-argument-e-with-bug_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exit code is 0 😠. Now, if you include the option &lt;code&gt;set -e&lt;/code&gt; and execute it again, the output changes, fixing the unexpected behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-argument-e-with-fix 
Creating the-set-builtin_why-use-argument-e-with-fix_1 ... done
Attaching to the-set-builtin_why-use-argument-e-with-fix_1
why-use-argument-e-with-fix_1  | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-e-with-fix_1  | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-e-with-fix_1  | Let me force an error 👀
the-set-builtin_why-use-argument-e-with-fix_1 exited with code 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the exit code is 1 🥳. According to the documentation about the argument &lt;code&gt;-e&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exit immediately if a pipeline, which may consist of a single simple command, a list, or a compound command returns a non-zero status.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This argument is quite good! It can protect us from bugs in production. Though it can solve many potential problems, we may need an environment variable. To illustrate, see the &lt;code&gt;PORT&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

python manage.py migrate
python manage.py collectstatic &lt;span class="nt"&gt;--no-input&lt;/span&gt;

gunicorn &lt;span class="nt"&gt;-cpython&lt;/span&gt;:gunicorn_config &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; aladdin.wsgi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the variable is not provided, our application might be running without a critical parameter. Is there another argument that can stop the script if this variable is missing?&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat unset variables as an error when substituting
&lt;/h2&gt;

&lt;p&gt;Now we have the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

python the_set_builtin/sample_1.py
python the_set_builtin/sample_2_force_error.py
python the_set_builtin/sample_3.py
python the_set_builtin/sample_4_env_variable.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALADDIN_WISH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
python the_set_builtin/sample_5.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run, you'll get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-argument-u-with-bug
Starting the-set-builtin_why-use-argument-u-with-bug_1 ... done
Attaching to the-set-builtin_why-use-argument-u-with-bug_1
why-use-argument-u-with-bug_1  | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-u-with-bug_1  | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-u-with-bug_1  | Let me force an error 👀
why-use-argument-u-with-bug_1  | I am the /app/the_set_builtin/sample_3.py!
why-use-argument-u-with-bug_1  | I am the /app/the_set_builtin/sample_4_env_variable.py!
why-use-argument-u-with-bug_1  | Look! I received the following arguments: ['the_set_builtin/sample_4_env_variable.py', '']
why-use-argument-u-with-bug_1  | Length: 2
why-use-argument-u-with-bug_1  | I am the /app/the_set_builtin/sample_5.py!
the-set-builtin_why-use-argument-u-with-bug_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the option &lt;code&gt;set -u&lt;/code&gt;, here is what you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-argument-u-with-fix
Starting the-set-builtin_why-use-argument-u-with-fix_1 ... done
Attaching to the-set-builtin_why-use-argument-u-with-fix_1
why-use-argument-u-with-fix_1  | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-u-with-fix_1  | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-u-with-fix_1  | Let me force an error 👀
why-use-argument-u-with-fix_1  | I am the /app/the_set_builtin/sample_3.py!
why-use-argument-u-with-fix_1  | ./scripts/with-argument-u.sh: line 10: ALADDIN_WISH: unbound variable
the-set-builtin_why-use-argument-u-with-fix_1 exited with code 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's rocking 🤘! Let's see the explanation of &lt;code&gt;-u&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat unset variables and parameters other than the special parameters ‘@’ or ‘*’ as an error when performing parameter expansion. An error message will be written to the standard error, and a non-interactive shell will exit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Including both options in our scripts (&lt;code&gt;set -eu&lt;/code&gt;) and we are almost good to go! I wrote "almost" because we can add another layer of protection. Let's see the last one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pipeline should produce a failure return code if any command fails
&lt;/h2&gt;

&lt;p&gt;To illustrate, imagine you have the script below. Notice that we're using &lt;code&gt;set -e&lt;/code&gt; argument to make it safer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin&lt;/span&gt;
&lt;span class="c"&gt;# • -e:  Exit immediately if a command exits with a non-zero status.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

this-is-a-fake-command-my-friend | &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"You...are late."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"A thousand apologies, O patient one."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case the second command fails in the pipeline, here's the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-option-pipefail-with-bug
Starting the-set-builtin_why-use-option-pipefail-with-bug_1 ... done
Attaching to the-set-builtin_why-use-option-pipefail-with-bug_1
why-use-option-pipefail-with-bug_1  | ./scripts/without-option-pipefail.sh: line 7: this-is-a-fake-command-my-friend: command not found
why-use-option-pipefail-with-bug_1  | You...are late.
why-use-option-pipefail-with-bug_1  | A thousand apologies, O patient one.
the-set-builtin_why-use-option-pipefail-with-bug_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Including the option &lt;code&gt;set -e -o pipefail&lt;/code&gt;, the output changes to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶ docker-compose up why-use-option-pipefail-with-fix
Starting the-set-builtin_why-use-option-pipefail-with-fix_1 ... done
Attaching to the-set-builtin_why-use-option-pipefail-with-fix_1
why-use-option-pipefail-with-fix_1  | ./scripts/with-option-pipefail.sh: line 9: this-is-a-fake-command-my-friend: command not found
why-use-option-pipefail-with-fix_1  | You...are late.
the-set-builtin_why-use-option-pipefail-with-fix_1 exited with code 127
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its explanation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Shield you from working outside working hours
&lt;/h2&gt;

&lt;p&gt;Wrapping everything up, this is the entry your bash script should have at the top: &lt;code&gt;set -eu -o pipefail&lt;/code&gt;. Always insert it in all of your scripts, always. This simple protection will help you a lot during &lt;a href="https://www.google.com/search?q=what+is+tshoot"&gt;TSHOOT&lt;/a&gt; 🙏. One example is when you're running your application without a migration that should have been applied.&lt;/p&gt;

&lt;p&gt;You can check out &lt;a href="https://github.com/willianantunes/tutorials/tree/master/2021/05/the-set-builtin"&gt;all the code I put in this article on GitHub&lt;/a&gt;. There you'll find a &lt;a href="https://github.com/willianantunes/tutorials/blob/605a9bbebf5ec2857a33800275d0518155eb86e9/2021/05/the-set-builtin/docker-compose.yaml"&gt;docker-compose file&lt;/a&gt; with all the services testing each of the arguments I showed here 🤙.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>shell</category>
      <category>scripts</category>
    </item>
    <item>
      <title>The easiest way to run a container on GCE with Terraform</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Tue, 25 May 2021 20:29:58 +0000</pubDate>
      <link>https://dev.to/willianantunes/the-easiest-way-to-run-a-container-on-gce-with-terraform-1d51</link>
      <guid>https://dev.to/willianantunes/the-easiest-way-to-run-a-container-on-gce-with-terraform-1d51</guid>
      <description>&lt;p&gt;Recently I developed a worker that would listen to published tasks through a database. For this sole purpose, I used &lt;a href="https://django-q.readthedocs.io/en/latest/"&gt;Django Q&lt;/a&gt;. When I finished its development, I started wondering how I would enable it in production. As my whole stack is on GCP for personal projects, I found an exciting way to deploy it with Google Compute Engine using a container image. Let's see how we can do it pretty quickly with the help of &lt;a href="https://github.com/hashicorp/terraform"&gt;Terraform&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some limits you must be aware of before we start
&lt;/h2&gt;

&lt;p&gt;There are &lt;a href="https://cloud.google.com/compute/docs/containers/deploying-containers#limitations"&gt;some limitations&lt;/a&gt; that you should pay attention to before spending any time on this solution. These two, in particular, can bring trouble to you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can only deploy one container for each VM instance. Consider Google Kubernetes Engine if you need to deploy multiple containers per VM instance.&lt;/li&gt;
&lt;li&gt;You can only deploy containers from a public repository or from a private Container Registry repository that you have access to. Other private repositories are not supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our example, I'll use a private image from &lt;a href="https://cloud.google.com/container-registry"&gt;Container Registry&lt;/a&gt;, and for that, we'll need a user that can read and download it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create a service account that can download images on Container Registry
&lt;/h2&gt;

&lt;p&gt;You can &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account"&gt;create a service account with Terraform&lt;/a&gt; too, but let's stick with the command line. To make the account, you can issue the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud iam service-accounts create custom-gce-dealer &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--display-name&lt;/span&gt; &lt;span class="s2"&gt;"Custom GCE Dealer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You must check which storage is used for your container registry. Let's suppose it's &lt;code&gt;artifacts.agrabah-project.appspot.com&lt;/code&gt;. Then you can execute the following command, using the generated ID for your service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gsutil iam ch &lt;span class="se"&gt;\&lt;/span&gt;
serviceAccount:custom-gce-dealer@agrabah-project.iam.gserviceaccount.com:roles/storage.objectViewer &lt;span class="se"&gt;\&lt;/span&gt;
gs://artifacts.agrabah-project.appspot.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all we need to check out images from the container registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform manifests
&lt;/h2&gt;

&lt;p&gt;I've found a &lt;a href="https://github.com/terraform-google-modules/terraform-google-container-vm"&gt;module that handles the metadata needed to set up the container configuration for the resource google_compute_instance&lt;/a&gt;. Let's use it to create our own module; thus, our &lt;code&gt;main.tf&lt;/code&gt; file will not be immense and difficult to understand. First, let's start a project with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root-folder-of-your-project/ &amp;lt;--- Main project
│
├── gce-with-container/  &amp;lt;--- Our custom module
|   |
│   ├── main.tf
│   └── variables.tf
│
├── main.tf &amp;lt;--- We'll use gce-with-container here
├── terraform.tfvars &amp;lt;--- Values for what we defined in variables.tf
├── variables.tf &amp;lt;--- terraform.tfvars has the values for each defined variables
└── versions.tf  &amp;lt;-- Here you will find the terraform block which specifies the required provider version and required Terraform version for this configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom module
&lt;/h3&gt;

&lt;p&gt;The folder &lt;code&gt;gce-with-container&lt;/code&gt; contains our custom module. I've checked &lt;a href="https://github.com/terraform-google-modules/terraform-google-container-vm/tree/5e69eafaaaa8302c5732799e32d1da5c17b7b285/examples"&gt;all the examples available on terraform-google-container-vm&lt;/a&gt; to create my own. Let's see how it's defined the &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# https://www.terraform.io/docs/language/values/locals.html&lt;/span&gt;
  &lt;span class="n"&gt;instance_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%s-%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&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="n"&gt;env_variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;var_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var_value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env_variables&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var_name&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var_value&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;####################&lt;/span&gt;
&lt;span class="c1"&gt;##### CONTAINER SETUP&lt;/span&gt;

&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"gce-container"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# https://github.com/terraform-google-modules/terraform-google-container-vm&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-google-modules/container-vm/google"&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;

  &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom_command&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env_variables&lt;/span&gt;
    &lt;span class="n"&gt;securityContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;privileged&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;privileged_mode&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;tty&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate_tty&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;restart_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Always"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;####################&lt;/span&gt;
&lt;span class="c1"&gt;##### COMPUTE ENGINE&lt;/span&gt;

&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"vm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_name&lt;/span&gt;
  &lt;span class="c1"&gt;# gcloud compute machine-types list | grep micro | grep us-central1-a&lt;/span&gt;
  &lt;span class="c1"&gt;# e2-micro / 2 / 1.00&lt;/span&gt;
  &lt;span class="c1"&gt;# f1-micro / 1 / 0.60&lt;/span&gt;
  &lt;span class="c1"&gt;# gcloud compute machine-types list | grep small | grep us-central1-a&lt;/span&gt;
  &lt;span class="c1"&gt;# e2-small / 2 / 2.00&lt;/span&gt;
  &lt;span class="c1"&gt;# g1-small / 1 / 1.70&lt;/span&gt;
  &lt;span class="n"&gt;machine_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"f1-micro"&lt;/span&gt;
  &lt;span class="c1"&gt;# If true, allows Terraform to stop the instance to update its properties.&lt;/span&gt;
  &lt;span class="n"&gt;allow_stopping_for_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;boot_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;initialize_params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source_image&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;network&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network_name&lt;/span&gt;

    &lt;span class="n"&gt;access_config&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;gce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;declaration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata_value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm_container_label&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client_email&lt;/span&gt;
    &lt;span class="n"&gt;scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"https://www.googleapis.com/auth/cloud-platform"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created some custom variables (see the file &lt;code&gt;variables.tf&lt;/code&gt;) and a helper to make the configuration of environment variables easier (see &lt;code&gt;locals.env_variables&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Main project
&lt;/h3&gt;

&lt;p&gt;Our &lt;code&gt;main.tf&lt;/code&gt; file in our root folder has the boilerplate part that configures the provider (see &lt;code&gt;variables.tf&lt;/code&gt; and &lt;code&gt;terraform.tfvars&lt;/code&gt;) and the module we created previously. I'm only using the variable &lt;code&gt;custom_command&lt;/code&gt; because the container image I provided already has a &lt;a href="https://docs.docker.com/engine/reference/builder/#cmd"&gt;CMD entry&lt;/a&gt; on its Dockerfile; thus, it must be overridden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"google"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;
  &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;region&lt;/span&gt;
  &lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"gce-worker-container"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./gce-with-container"&lt;/span&gt;

  &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcr.io/${var.project}/jafar@sha256:6b71cebad455dae81af9fcb87a4c8b5bca2c2b6b2c09cec21756acd0f1ae7cec"&lt;/span&gt;
  &lt;span class="n"&gt;privileged_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;activate_tty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;custom_command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"./scripts/start-worker.sh"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;env_variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;Q_CLUSTER_WORKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;
    &lt;span class="no"&gt;DB_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-database-host"&lt;/span&gt;
    &lt;span class="no"&gt;DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5432"&lt;/span&gt;
    &lt;span class="no"&gt;DB_ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"django.db.backends.postgresql"&lt;/span&gt;
    &lt;span class="no"&gt;DB_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db_production"&lt;/span&gt;
    &lt;span class="no"&gt;DB_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jafar_prd"&lt;/span&gt;
    &lt;span class="no"&gt;DB_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"role_jafar_prd"&lt;/span&gt;
    &lt;span class="no"&gt;DB_PASS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"this-is-my-honest-password"&lt;/span&gt;
    &lt;span class="no"&gt;DB_USE_SSL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"True"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;instance_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jafar-worker"&lt;/span&gt;
  &lt;span class="n"&gt;network_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="c1"&gt;# This has the permission to download images from Container Registry&lt;/span&gt;
  &lt;span class="n"&gt;client_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"custom-gce-dealer@${var.project}.iam.gserviceaccount.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! You can type &lt;code&gt;terraform init&lt;/code&gt; at the root folder and then &lt;code&gt;terraform apply&lt;/code&gt; followed by your confirmation. It's a good idea to access the created VM and check if everything is okay through the command &lt;code&gt;docker logs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W2AferC7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztfq6lke2o2j7hxebv9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W2AferC7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztfq6lke2o2j7hxebv9v.png" alt="It shows the result of the docker command after the machine is up and running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see the container with the image &lt;code&gt;gcr.io/gce-containers/konlet:v.0.11-latest&lt;/code&gt;, you don't have to worry 👀. It's the job responsible for downloading the one you configured 😅.&lt;/p&gt;

&lt;p&gt;In this article I used Terraform v0.14.9. &lt;a href="https://github.com/willianantunes/tutorials/tree/master/2021/05/gce-container-terraform"&gt;You can check the entire code out on GitHub&lt;/a&gt;. Don't forget to execute &lt;code&gt;terraform destroy&lt;/code&gt; after your test! See you next time ✌.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>googlecloud</category>
      <category>docker</category>
    </item>
    <item>
      <title>You should configure env variables in one place</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Sun, 23 May 2021 14:57:52 +0000</pubDate>
      <link>https://dev.to/willianantunes/you-should-configure-env-variables-in-one-place-m</link>
      <guid>https://dev.to/willianantunes/you-should-configure-env-variables-in-one-place-m</guid>
      <description>&lt;p&gt;Recently I had to create a new JavaScript project. I chose a framework and some libraries that I haven't used before. First, I tried to find lessons learned and projects on GitHub targeting the tools I decided on and then use them as a way to create my own project. Suddenly, I noticed a problem in all examples and even in big projects: they had &lt;code&gt;process.env&lt;/code&gt; been invoked in many different files.&lt;/p&gt;

&lt;p&gt;This approach may be seen as harmless because it's relatively trivial and works; nevertheless, isn't it better to help people who get in touch if your project regarding its settings right away?&lt;/p&gt;

&lt;h2&gt;
  
  
  Explicit is better than implicit
&lt;/h2&gt;

&lt;p&gt;When you are learning Python, sooner or later, you will face &lt;a href="https://www.python.org/dev/peps/pep-0020/" rel="noopener noreferrer"&gt;The Zen of Python&lt;/a&gt;. To give you an idea, if you've ever had to deal with projects where many developers were involved, you will probably sympathize with the statements contained in this easter egg. I use them as a means to endorse some practices in this article.&lt;/p&gt;

&lt;p&gt;One of its statements is exactly &lt;em&gt;explicit is better than implicit&lt;/em&gt;. Never, ever expect that somebody will understand your intentions, try to always leave this in doubt. To illustrate, if your team is growing, including people who have never programmed with the targeted language or framework, one of your concerns should be reducing cognitive load across projects. There are many ways to minimize this impact. One is creating a place where all your environment variables are defined. Let's see an example in the following image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe7pbhz5rbizhswyrpwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe7pbhz5rbizhswyrpwx.png" alt="There is one kind of getting values of environment variables, but you can compare with another one, that can be better."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of searching which files have &lt;code&gt;process.env&lt;/code&gt;, we can simply look at the &lt;code&gt;settings.js&lt;/code&gt; file and then understand what the application needs to run correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Readability counts, and a lot
&lt;/h2&gt;

&lt;p&gt;This is another statement of the zen. Having all your environment variables in one place counts a lot. That is easy for someone with minimal "domain context" to read and quickly understand what your application does. As a result, it's pretty simple to statically find where the variables are being used. You can do that with &lt;code&gt;process.env&lt;/code&gt; as well. Still, you would have trouble adding some spices to protect you from problems across many files. Let's see one ahead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors should never pass silently
&lt;/h2&gt;

&lt;p&gt;Let's say the variables &lt;code&gt;PARTNER_API_ENDPOINT&lt;/code&gt; and &lt;code&gt;ACTIVE_MQ_HOST&lt;/code&gt; must be present in production, and then you forgot to add them, but your application can still be built. Can you imagine the problem that you would have when it's identified because of customer complaints? Let' solve that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getEnvOrRaiseException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EnvironmentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Environment variable &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not set!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evalEnvAsBoolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valueAsLowerCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trueValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;y&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;trueValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueAsLowerCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PARTNER_API_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getEnvOrRaiseException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PARTNER_API_ENDPOINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACTIVE_MQ_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getEnvOrRaiseException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ACTIVE_MQ_HOST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DATABASE_USE_SSL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evalEnvAsBoolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_USE_SSL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PARTNER_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ACTIVE_MQ_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DATABASE_USE_SSL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about now? It can simply break your pipeline during build time. Your project will never advance to production without the required environment variables again. Is something unexpected? Throw an exception 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Now is better than never
&lt;/h2&gt;

&lt;p&gt;How about trying to use some of the zen statements in your work or even in your life? By the way, &lt;em&gt;now is better than never&lt;/em&gt; is another declaration that I took from it. See you next time!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>defensiveprogramming</category>
    </item>
    <item>
      <title>Why did I create a blog from scratch?</title>
      <dc:creator>Willian Antunes</dc:creator>
      <pubDate>Sat, 22 May 2021 16:00:12 +0000</pubDate>
      <link>https://dev.to/willianantunes/why-did-i-create-a-blog-from-scratch-46eb</link>
      <guid>https://dev.to/willianantunes/why-did-i-create-a-blog-from-scratch-46eb</guid>
      <description>&lt;p&gt;If I had to recommend someone to start a blog, taking into consideration things like time to market, huge ecosystems, tutorials, and no expected advanced knowledge in programming at all, I would tell them to start right away with &lt;a href="https://www.blogger.com/"&gt;Blogger&lt;/a&gt;, or if more options are needed, then &lt;a href="https://wordpress.com/"&gt;Wordpress&lt;/a&gt; would be the best choice probably. Now, if the idea is to know how stuff works, and utilize a technology that handles not only on the front-end but on the back-end also, thinking carefully in terms of its usage on the job market, I'd advise using a technology built on top of JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why JavaScript and not PHP or another language?
&lt;/h2&gt;

&lt;p&gt;Let's dive a bit into some more concerns. What I usually do is look over data. To illustrate, what I've been doing to answer such questions so far is the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/topics/wordpress?l=php"&gt;Search for projects through hashtags on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Analyze the latest survey called &lt;a href="https://www.jetbrains.com/lp/devecosystem-2020/"&gt;The State of Developer Ecosystem made by JetBrains&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Study carefully the latest &lt;a href="https://insights.stackoverflow.com/survey/2020"&gt;developer survey made by StackOverflow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.com/tags"&gt;Which tags are most answered on StackOverflow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Scan which jobs are offered on the job market. LinkedIn is an excellent place to look over.&lt;/li&gt;
&lt;li&gt;Determine how expensive the technology is to deploy and serve its purpose.&lt;/li&gt;
&lt;li&gt;Does the technology have documentation?&lt;/li&gt;
&lt;li&gt;Consulting with skilled contacts 😅.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no silver bullet to choose a technology to create a blog, and some people might prefer a language that they already know, so this can be quite complex. Some may prefer a technology built from Java, Ruby, and so forth. But the thing is, you have to look carefully what technologies have companies been using so far, and start with those.&lt;/p&gt;

&lt;p&gt;My research pointed out that would be the best idea to use JavaScript either using Gatsby or Next.JS as the framework for the blog. Nowadays I'm more used to Python, though, so what should I do? I mean, getting out of our comfort zone is quite challenging, but it's necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing new technologies opens your mind
&lt;/h2&gt;

&lt;p&gt;When people discuss this topic, I like to compare it to learning a new language. In the beginning, it can be dull, monotonous, boring, but the game starts to change when you see your evolution, and people start to tell you about it. It's satisfying! When you notice, you will have a new tool to use to communicate with people and even apply it in your job. The same goes for technology. We must strive to learn new technologies, and understand other possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you study technologies through playgrounds, leave them open-source
&lt;/h2&gt;

&lt;p&gt;Isn't it amazing when you need to create/build something and then find a playground project to base your work on? Apart from that, when you create an open-source project, it can be used as your portfolio too. So use it wisely! Sell yourself! You will reap the fruits of your hard labor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words and honest advice
&lt;/h2&gt;

&lt;p&gt;One thing is discovering new tools and using them on your personal projects, and another entirely different thing is applying them to your job. Know the consequences because there are many variables to consider. If you want to switch services to use &lt;a href="https://www.ruby-toolbox.com/projects/hanami"&gt;Hanami&lt;/a&gt; instead of &lt;a href="https://www.ruby-toolbox.com/projects/rails"&gt;Rails&lt;/a&gt;, you are supposed to answer at least the following questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Will it bring business value?&lt;/li&gt;
&lt;li&gt;Will the customers be positively impacted by it?&lt;/li&gt;
&lt;li&gt;Does it have support from the community?&lt;/li&gt;
&lt;li&gt;Does it have support from companies out there?&lt;/li&gt;
&lt;li&gt;Is it hard to find people that can work with it?&lt;/li&gt;
&lt;li&gt;Is it arduous to teach people that have never been in contact with the language or the framework?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are others of course, but you get the point 😁.&lt;/p&gt;

&lt;p&gt;This is the first blog entry and I expect to post once per month at least. I hope I can achieve this goal! 😄&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>comfortzone</category>
      <category>advice</category>
    </item>
  </channel>
</rss>
