<?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: Twin Sun</title>
    <description>The latest articles on DEV Community by Twin Sun (@twinsun).</description>
    <link>https://dev.to/twinsun</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%2F913527%2Fff227007-ffa4-4145-b8a6-e9bec03cb162.jpg</url>
      <title>DEV Community: Twin Sun</title>
      <link>https://dev.to/twinsun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/twinsun"/>
    <language>en</language>
    <item>
      <title>How We Saved a Client $100,000 Annually by Optimizing Tests</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Thu, 16 Feb 2023 15:54:34 +0000</pubDate>
      <link>https://dev.to/twinsun/how-we-saved-a-client-100000-annually-by-optimizing-tests-pn2</link>
      <guid>https://dev.to/twinsun/how-we-saved-a-client-100000-annually-by-optimizing-tests-pn2</guid>
      <description>&lt;h2&gt;
  
  
  What is the purpose of automated testing?
&lt;/h2&gt;

&lt;p&gt;Our team is a strong proponent of &lt;a href="https://twinsunsolutions.com/blog/building-better-apps-automated-tests/" rel="noopener noreferrer"&gt;automated testing&lt;/a&gt;.&lt;br&gt;
Automated tests ensure that our code is working as expected, and that it will continue working even if our app changes over time.&lt;/p&gt;

&lt;p&gt;Every application we build has a comprehensive suite of &lt;a href="https://en.wikipedia.org/wiki/Unit_testing" rel="noopener noreferrer"&gt;unit tests&lt;/a&gt;, &lt;a href="https://martinfowler.com/bliki/IntegrationTest.html" rel="noopener noreferrer"&gt;integration tests&lt;/a&gt;, and &lt;a href="https://circleci.com/blog/what-is-end-to-end-testing/" rel="noopener noreferrer"&gt;end-to-end (E2E) tests&lt;/a&gt;.&lt;br&gt;
We run these tests every time we change code or deploy a new version of the app.&lt;br&gt;
Tests ensure we don't break existing functionality when we make changes, and they help us catch bugs before they reach production.&lt;br&gt;
In short, automated tests are a safety net for building excellent apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worsening performance constraints with a growing test suite
&lt;/h2&gt;

&lt;p&gt;Testing is a critical part of our development process with significant benefits, but there are some costs associated with the practice.&lt;br&gt;
As an app matures, the number of tests in a test suite will grow.&lt;br&gt;
Some of our apps have thousands of automated tests.&lt;br&gt;
The more tests you have, the longer it takes to run the test suite.&lt;br&gt;
In some instances, software developers will need to wait on the results of the test suite before moving on to other work.&lt;br&gt;
That wait time can thereby reduce developer productivity.&lt;/p&gt;

&lt;p&gt;We found test execution time to be a significant bottleneck for one of our clients.&lt;br&gt;
While working on several web applications for the client, we found ourselves waiting an hour for a Java application's test suite to complete.&lt;br&gt;
This hindered our own productivity and slowed down development work.&lt;/p&gt;

&lt;p&gt;The client's team was also frustrated by the long test execution time but had grown accustomed to waiting for the test suite, as they had dealt with this problem for a long time.&lt;br&gt;
Unfortunately, it's not a matter of just waiting for tests to pass.&lt;br&gt;
Sometimes code changes introduce test failures, meaning developers would need to wait an hour to find out if their changes broke anything.&lt;br&gt;
A team of approximately 20 developers was spending hours each day waiting for tests simply so they could do their job!&lt;/p&gt;

&lt;h2&gt;
  
  
  The costs of slow tests
&lt;/h2&gt;

&lt;p&gt;This time adds up quickly, especially across such a large project team.&lt;br&gt;
While idle time is not inherently bad, it becomes a problem when &lt;a href="https://twinsunsolutions.com/blog/five-focusing-steps/" rel="noopener noreferrer"&gt;your project's constraint&lt;/a&gt;—your bottleneck—is idle.&lt;br&gt;
Work can only be done as quickly as the constraint allows.&lt;br&gt;
And more often than not, developers are likely the constraint on most software projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Financial impact
&lt;/h3&gt;

&lt;p&gt;So what is the cost of waiting for tests to pass?&lt;br&gt;
Let's perform some naive calculations.&lt;br&gt;
If each developer pushes code changes twice a day, and each test run takes an hour, then the team is idle for 200 person-hours each week.&lt;br&gt;
That's 10,400 person-hours per year.&lt;br&gt;
If your total cost (salary, benefits, equipment, office space, etc.) for a developer is $100,000 per year, that idle time would add up to $500,000 in lost opportunity each year!&lt;/p&gt;

&lt;p&gt;Now, thankfully, those naive calculations do not represent the lost opportunity cost our client experience.&lt;br&gt;
In reality, developers had adapted to pushing their code less frequently, and only a subset of the team was regularly working on the slowest Java application.&lt;br&gt;
However, a similar back-of-the-napkin calculation demonstrated that the client was leaving nearly $200,000 per year on the table due to slow test suite execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Productivity impact
&lt;/h3&gt;

&lt;p&gt;The financial impact of slow tests is significant, but there is also a productivity impact.&lt;br&gt;
When developers are waiting for tests to pass, they are not working on other tasks.&lt;br&gt;
Or, if they do decide to work on another task while they wait, they may not be as productive as they would be if they were focused on a single task.&lt;/p&gt;

&lt;p&gt;Consider test failures.&lt;br&gt;
If a developer introduces a code change that causes a test to fail, they will need to wait for the test suite to complete before they can fix the test.&lt;br&gt;
In the meantime, they have moved on to another task and are no longer thinking about the problematic code.&lt;br&gt;
Once they are notified of the test failure, they have to once again switch their focus to the code that caused the failure.&lt;br&gt;
&lt;a href="https://www.atlassian.com/blog/productivity/context-switching" rel="noopener noreferrer"&gt;Context switching&lt;/a&gt; requires time, so switching back and forth between tasks will reduce the developer's productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we improved test suite performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Identifying the problem
&lt;/h3&gt;

&lt;p&gt;We began investigating ways to improve test suite performance.&lt;br&gt;
The first thing we noticed was the amount of integration tests.&lt;br&gt;
Integration tests verify that different parts of the application work together as expected.&lt;br&gt;
However, these tests are often slower than unit tests because they require setting up a database, starting a web server, and other time-consuming tasks.&lt;/p&gt;

&lt;p&gt;Integration tests are very valuable for catching problems prior to deployment, and we wanted to maintain the quality of the test suite.&lt;br&gt;
Therefore, removing integration tests was not an option.&lt;br&gt;
Instead, we needed to find a way to run the integration tests faster.&lt;br&gt;
Looking at system metrics during test execution, we quickly identified the performance bottleneck: the CPU.&lt;/p&gt;

&lt;p&gt;CPU utilization was consistently at 100% during test execution, but only for one CPU core.&lt;br&gt;
Most processors now have multiple cores, and each core can run its own processes.&lt;br&gt;
The test suite was not set up to take advantage of this capability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelizing tests to utilize available hardware
&lt;/h3&gt;

&lt;p&gt;We made the single-process discovery on a development machine.&lt;br&gt;
After reviewing the CI server's configuration, we realized it also only executed one test at a time.&lt;br&gt;
This made our next step clear: we would parallelize the test suite.&lt;br&gt;
Instead of running a single test at a time, we would run as many tests as possible at the same time.&lt;/p&gt;

&lt;p&gt;It's important to recognize that the test server is doing other work aside from running tests.&lt;br&gt;
Operating system processes still need to utilize CPU time.&lt;br&gt;
Therefore, you generally won't want to dedicate every CPU core to running tests.&lt;br&gt;
Instead, a good rule of thumb is to start your parallel test efforts with &lt;code&gt;(N/2 + 1)&lt;/code&gt; processes, where &lt;code&gt;N&lt;/code&gt; is the number of CPU cores.&lt;br&gt;
For example, if your test server has an 8-core CPU, you would start with 5 parallel processes.&lt;br&gt;
If you're unsure of whether you can get more out of the system, you can always try increasing the number of parallel processes, but the &lt;code&gt;(N/2 + 1)&lt;/code&gt; rule is a sound starting point.&lt;/p&gt;

&lt;p&gt;Since the test suite included integration tests, we needed to make sure that the tests would not interfere with each other.&lt;br&gt;
This meant giving each process its own database and web server.&lt;br&gt;
We used &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; to create a separate database for each process, and started a web server bound to a unique port number for each process (e.g., &lt;code&gt;8080 + N&lt;/code&gt; where &lt;code&gt;N&lt;/code&gt; represents the process number).&lt;br&gt;
Starting up multiple databases and seeding them with required test data was a slow process, taking up to 60 seconds.&lt;br&gt;
However, since we can reuse the same database for multiple test runs, we only needed to do this once per process.&lt;br&gt;
Over the course of test suite execution, we still realized significant performance improvements.&lt;/p&gt;

&lt;p&gt;Parallelizing test suite execution brought test time down from an hour to 20 minutes on existing hardware.&lt;br&gt;
What a drastic improvement!&lt;br&gt;
This still falls short of the general Extreme Programming (XP) guideline of a &lt;a href="https://martinfowler.com/articles/continuousIntegration.html" rel="noopener noreferrer"&gt;ten minute build&lt;/a&gt;, but was a significant decrease in the time it took this team to receive feedback on their code changes.&lt;/p&gt;

&lt;p&gt;At this point, we recalculated the lost opportunity cost of slow tests.&lt;br&gt;
Whereas the client's lost opportunity cost from idle time was previously approaching $200,000 per year, it was now closer to $60,000 per year, amounting to an overall reduction of $140,000 in lost opportunity cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding more hardware
&lt;/h3&gt;

&lt;p&gt;After parallelizing the test suite, we knew there were more ways to improve performance.&lt;br&gt;
We had already identified the CPU as the bottleneck, and parallel tests fully utilized the client's available hardware.&lt;br&gt;
Once we had exhausted the available CPU cores, we knew additional hardware could further decrease build times.&lt;br&gt;
We provided the client with information about our findings and recommended they add more hardware to execute test even faster.&lt;/p&gt;

&lt;p&gt;On large teams, multiple test servers become just as useful as more powerful hardware.&lt;br&gt;
While we had focused on parallelizing the test suite, and the overall execution time was significantly lower, the build server was limited to running only one instance of the test suite at a time.&lt;br&gt;
That meant that if multiple developers pushed code changes at the same time, they'd have to wait for their turn to test their code changes on the build server.&lt;br&gt;
Multiple build servers would alleviate this constraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further improvements
&lt;/h3&gt;

&lt;p&gt;Beyond the aforementioned improvements, there are other ways to improve test suite performance.&lt;br&gt;
Optimizing individual tests, eliminating unnecessary tests, and generally improving application performance are all ways to decrease test suite execution time.&lt;br&gt;
However, those improvements can be a much more significant investment than the relatively simple changes we made to the test suite.&lt;br&gt;
Parallelizing tests and buying more hardware is incredibly cost-effective compared to optimizing individual tests.&lt;br&gt;
Whereas our approach took a few days to implement, optimizing hundreds or thousands of individual tests could take weeks or months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Life with a faster test suite
&lt;/h2&gt;

&lt;p&gt;The client was thrilled with the results of our work.&lt;br&gt;
Decreased test execution time meant developers could get feedback on their code changes faster.&lt;br&gt;
This allowed them to be more productive, and they were able to push code changes more frequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build quickly with confidence
&lt;/h2&gt;

&lt;p&gt;Automated testing ensures you can deploy new code with confidence.&lt;br&gt;
However, slow test suites can be a major productivity drain.&lt;br&gt;
You can improve test suite performance by parallelizing your tests and adding more hardware, increasing your team's productivity while preserving the benefits of automated testing.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Announcing Harvest Timer and Work Logs for Jira Cloud</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Thu, 16 Feb 2023 15:47:43 +0000</pubDate>
      <link>https://dev.to/twinsun/announcing-harvest-timer-and-work-logs-for-jira-cloud-4ae</link>
      <guid>https://dev.to/twinsun/announcing-harvest-timer-and-work-logs-for-jira-cloud-4ae</guid>
      <description>&lt;p&gt;NASHVILLE, January 16, 2023 – Twin Sun is pleased to announce the launch of &lt;a href="https://twinsunsolutions.com/products/harvest-timer-work-logs-for-jira-cloud/" rel="noopener noreferrer"&gt;Harvest Timer and Work Logs for Jira Cloud&lt;/a&gt;, a new time-saving app that helps teams sync their Harvest time entries with their Jira work logs. &lt;br&gt;
This app allows users to see up-to-date time tracking estimates within Jira, ensuring that team members know how much time has been spent on each task.&lt;/p&gt;

&lt;p&gt;With Harvest Timer and Work Logs for Jira Cloud, users can start and stop Harvest timers from any Jira issue details screen, and add manual Harvest time entries if necessary.&lt;br&gt;
The app also offers project-specific defaults so team members can quickly log time to the appropriate Harvest task within each Jira project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb1b27qpqi4lhwjlocqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb1b27qpqi4lhwjlocqs.png" alt="Our Harvest Timer and Work Logs for Jira Cloud app in action." width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twin Sun uses Harvest for time tracking but wanted to see time logged to Harvest reflected in Jira. &lt;br&gt;
That's why they created this app: to make it easy for teams to sync their Harvest time entries with their Jira work logs, and to see up-to-date time tracking estimates within Jira.&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;
        We are thrilled to be able to offer this app to teams everywhere. 
        We know firsthand how frustrating it can be to juggle multiple tools for time tracking and work logging. 
        With Harvest Timer and Work Logs for Jira Cloud, we've created a solution that simplifies the process and helps teams stay organized and on top of their tasks.
    &lt;/p&gt;
    &lt;p&gt;
        Jami Couch, CTO
    &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Find the &lt;a href="https://marketplace.atlassian.com/apps/1229874/harvest-timer-and-work-logs-for-jira" rel="noopener noreferrer"&gt;Harvest Timer and Work Logs for Jira Cloud on the Atlassian Marketplace&lt;/a&gt; and streamline your team's time tracking today.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>react</category>
      <category>javascript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How Generative AI Will Spawn Amazing New App Ideas</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 23 Jan 2023 17:20:22 +0000</pubDate>
      <link>https://dev.to/twinsun/how-generative-ai-will-spawn-amazing-new-app-ideas-7o7</link>
      <guid>https://dev.to/twinsun/how-generative-ai-will-spawn-amazing-new-app-ideas-7o7</guid>
      <description>&lt;p&gt;Generative AI, or &lt;a href="https://en.wikipedia.org/wiki/Artificial_intelligence"&gt;artificial intelligence&lt;/a&gt; able to generate novel content, has the potential to revolutionize mobile and web apps. This new technology is exciting because content is an important part of any app.&lt;/p&gt;

&lt;p&gt;With generative AI, you can create content that is more engaging and more relevant to your users. This includes text, images, music, and other types of multimedia. Even entire conversations can be generated by AI. In this article, we'll explore how generative AI can help people spawn amazing new app ideas and bring those ideas to life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition of Generative AI
&lt;/h2&gt;

&lt;p&gt;Generative AI is a type of artificial intelligence that is able to generate new content based on a set of input parameters. This is in contrast to other types of AI. &lt;a href="https://en.wikipedia.org/wiki/Supervised_learning"&gt;Supervised learning&lt;/a&gt;, for example, relies on training data to make predictions. Instead of predicting things or categorizing things, generative AI creates things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of Generative AI
&lt;/h2&gt;

&lt;p&gt;Generative AI is already being used in a variety of industries and applications. Below are several examples of ways this incredible technology is being used today.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating mockups and prototypes of product designs.&lt;/strong&gt; Generative AI can be used to quickly and easily generate mockups or prototypes of product designs, such as app interfaces or website layouts. This gives designers and developers a sense of how the product will look and function before building it, saving time and resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generating marketing materials.&lt;/strong&gt; Generative AI can be used to create marketing materials such as promotional videos, social media posts, or advertisements. This helps businesses reach potential customers and generate interest in their products or services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Writing news articles.&lt;/strong&gt; Generative AI can be used to write news articles or other types of content. This helps media companies and other organizations produce content more efficiently, or cover topics that would be difficult or time-consuming for human writers to research and write about.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Translating text from one language to another.&lt;/strong&gt; Generative AI can be used to translate text from one language to another, helping to make information more accessible to a wider audience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generating personalized recommendations.&lt;/strong&gt; Generative AI can be used to personalize recommendations for users, such as generating playlists or meal plans. This improves the user experience and make it easier for people to find content or products that are relevant to their interests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Providing real-time transcriptions of speech.&lt;/strong&gt; Generative AI can be used to provide real-time transcriptions of speech, adding captions to videos and transcripts to audio recordings. This makes content more accessible to people who are deaf or hard of hearing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generating responses to customer inquiries.&lt;/strong&gt; Generative AI can be used to generate responses to customer inquiries in a customer service setting. This enables businesses to provide faster and more efficient customer support.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond these examples, there are many other ways that generative AI can be used to create new content. Creating new art, replicating human voices, and generating new music are just a few of the other applications of this technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generative AI's Potential for New Apps
&lt;/h2&gt;

&lt;p&gt;Generative AI has the potential to be a powerful tool for app development in a number of ways. Every step of product development, from ideation to development to marketing, can be expedited or enhanced by generative AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating New App Ideas
&lt;/h3&gt;

&lt;p&gt;First, it can be used to generate new app ideas and concepts. For example, an AI can generate a list of potential app ideas based on user input, such as a specific problem or need that the app is intended to address. It can also create rough prototypes of app interfaces and features, which can then be refined and developed further by human designers and developers.&lt;/p&gt;

&lt;p&gt;Consider the following interaction with &lt;a href="https://openai.com/blog/chatgpt/"&gt;OpenAI's ChatGPT&lt;/a&gt;, a chat bot powered by &lt;a href="https://openai.com/blog/gpt-3-apps/"&gt;GPT-3&lt;/a&gt;. I asked the bot for ideas for a new app that can utilize OpenAI's text generation API within the B2B space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GMB3PUpE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5nzkzft015ix1l1f6qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GMB3PUpE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5nzkzft015ix1l1f6qw.png" alt="Chatting with ChatGPT about app ideas" width="797" height="1007"&gt;&lt;/a&gt;&lt;/p&gt;
Example of ChatGPT generating new app ideas.



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;ChatGPT is particularly interesting for text generation as it retains the context of your conversation. This means that followup questions can be asked to get more specific information about the app ideas that were generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aicTj1FE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpp15n69tz713ahzgecp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aicTj1FE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpp15n69tz713ahzgecp.png" alt="Asking ChatGPT to elaborate on an app idea" width="797" height="935"&gt;&lt;/a&gt;&lt;/p&gt;
Example of a followup question and response in a ChatGPT conversation.



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Once you have an idea you like, you can ask ChatGPT to produce additional content to pitch your idea to your team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hs0Ft-Me--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hfmtbgruwbkz24n4vcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hs0Ft-Me--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hfmtbgruwbkz24n4vcw.png" alt="Asking ChatGPT to help pitch the app idea internally" width="797" height="992"&gt;&lt;/a&gt;&lt;/p&gt;
ChatGPT's draft of a press release for a new product.



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;You can take the idea even further with ChatGPT, asking it to assist you in obtaining verbal commitments to purchase from your first customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v-ti8zl---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24ntj6z5w66pd26c1pjy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v-ti8zl---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24ntj6z5w66pd26c1pjy.png" alt="ChatGPT writing a phone script" width="797" height="1322"&gt;&lt;/a&gt;&lt;/p&gt;
Sales script questions generated by ChatGPT.



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Improved App Designs and User Experiences
&lt;/h3&gt;

&lt;p&gt;Generative AI can also be used to optimize the design and functionality of an app. New AI tools could be used to create mockups or prototypes of app interfaces, which can help developers see what their app might look like and get a sense of how it might function. It could also be used to generate new app features or functionality, such as by suggesting new features to add to an existing app or coming up with entirely new app concepts.&lt;/p&gt;

&lt;p&gt;While the output of these tools is not a replacement for human designers, it can be used to help designers and developers come up with new ideas and improve their designs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GoXeaNeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xthe4ecn66svgvn7b124.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GoXeaNeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xthe4ecn66svgvn7b124.png" alt="ChatGPT helping brainstorm a UX" width="797" height="1315"&gt;&lt;/a&gt;&lt;/p&gt;
Brainstorming a user sign up flow for the new app idea.



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The above example was not particularly innovative or outside of the realm of what a human might devise, but it does show how generative AI can be used to assist designers in their work. Instead of an app designer spending their own time devising a signup flow from scratch, ChatGPT can give our designer a rough idea they can iterate upon (with or without ChatGPT's continued assistance).&lt;/p&gt;

&lt;p&gt;Text generation is not the only generative AI tool worth considering for improving your app ideas. Image diffusion models such as &lt;a href="https://openai.com/dall-e-2/"&gt;DALL-E 2&lt;/a&gt; are capable of producing unique, high quality images of practically anything you can imagine, which can power interesting capabilities within your app. &lt;a href="https://uizard.io/"&gt;Uizard&lt;/a&gt; is a free tool that generates mockups of app interfaces, which can be used to help designers and developers visualize what their app might look like and get a sense of how it might function with less effort than was previously required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Personalized App Content
&lt;/h3&gt;

&lt;p&gt;App personalization can be taken further than ever with the help of generative AI. The user experience can be tailored to the individual user, with anything in the app catering to the user's needs and preferences. AI services can generate customized recommendations or tailored content based on the user's preferences and history. &lt;/p&gt;

&lt;p&gt;Certain user characteristics, behaviors, or actions could be used as inputs to an AI model to generate personalized content. For example, messages encouraging the user to make a purchase could be generated to use a different tone or different vocabulary based on the user's personality. A personalized onboarding experience could be generated based on the user's demographic information: a Millennial might be greeted with a different message than a Baby Boomer. AI could even determine that the user may be interested in certain features more than others and adjust the screen layout to prioritize the features that the user is most likely to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Accessibility and Inclusiveness
&lt;/h3&gt;

&lt;p&gt;Generative AI can also be used to generate assistive features within an app, such as automatic captions for videos or audio transcriptions for voice notes. Complex articles can be summarized into more digestible text, aiding comprehension for people with reading difficulties. The text of an app can be automatically translated into other languages, making it more accessible to people who speak different languages. These capabilities could help make apps more accessible and user-friendly for people with disabilities or other needs.&lt;/p&gt;

&lt;p&gt;As an example, let's ask ChatGPT to simplify that last paragraph into a more digestible version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZBsTC1ba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9l43jq7w20f1os7ghz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZBsTC1ba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9l43jq7w20f1os7ghz8.png" alt="ChatGPT iteratively simplifying text" width="797" height="677"&gt;&lt;/a&gt;&lt;/p&gt;
Progressively simplified summarization via ChatGPT.



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and Limitations
&lt;/h2&gt;

&lt;p&gt;While generative AI has the potential to be a powerful tool for app development, there are a few challenges and limitations to consider.&lt;/p&gt;

&lt;p&gt;One challenge is the need for high-quality data. Generative AI must be trained on a data set that reflects the type of content you wish to generate. This is not a small task. GPT-3 was trained on &lt;a href="https://en.wikipedia.org/wiki/GPT-3"&gt;175 billion parameters&lt;/a&gt;, or 800 gigabytes of data.&lt;/p&gt;

&lt;p&gt;Depending on your desired use case, you may only need to use an existing trained model, eliminating the data problem. However, some applications that use generative AI may require user input to also be of a high level of quality. For example, if you wish to generate professional images of app designs, standard models may need more guidance than a general text-to-image diffusion model provides. An &lt;a href="https://iterative-refinement.github.io/palette/"&gt;image-to-image model&lt;/a&gt;, which depends on the user providing a starting image to improve upon, would require users to provide reasonably drawn designs to begin with. Uizard, for example, requires users to build a wireframe to help their AI generate a high-quality mockup of an app interface.&lt;/p&gt;

&lt;p&gt;Another challenge is bias in trained models. AI models will reflect whatever biases were present during training, either due to underrepresented or overrepresented concepts in the training data or some bias introduced in the training method itself. Failing to recognize these biases can lead to &lt;a href="https://algorithmwatch.org/en/google-vision-racism/"&gt;unintended consequences&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Beyond training biases, generative AI models can produce incorrect or misleading information. Large language models such as GPT-3 are designed to generate human-like text but are not necessarily knowledgeable on any particular topic. As a result, they may sound authoritative on a topic when in fact they are not designed to actually know the topic and are just writing reasonable sounding but factually incorrect responses that are similar to concepts they learned from their training data. This could lead people to believe or act upon incorrect information because the AI appears to know what it's talking about.&lt;/p&gt;

&lt;p&gt;In addition, generative AI used for open-ended responses to end users' questions could provide problematic responses. For example, a customer support chat bot intended to help users troubleshoot common problems with an app may encounter an unsolvable problem. In some cases, the AI may suggest a solution that is not helpful or appropriate, such as suggesting the user try another app. As another example, GPT-3 may give a programmer a code snippet that does not work due to changes made in utilized software libraries since the model was trained.&lt;/p&gt;

&lt;p&gt;Finally, there are practical limitations to consider when using generative AI. Some bloggers are using generative text to write blog posts, but most models have a limited amount of text they can generate at once. Therefore, it's currently easy to detect a purely generated post, and a purely AI-generated post is unlikely to rank well in organic search results. Long-form content is also difficult to produce without a human collaborator guiding the AI through multiple rounds of writing prompts.&lt;/p&gt;

&lt;p&gt;When incorporating generative AI into your app idea, it's important to test out ways the AI will be used. If you are using a text-to-image model to generate app designs, you should test the quality of the designs it produces. The way you anticipate the model working—or the way users want to use your app—may not reflect how the model actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generative AI Tools and Platforms
&lt;/h2&gt;

&lt;p&gt;There are a number of generative AI tools and platforms that can be used to power new capabilities in apps. While text generation is perhaps the simplest to understand, there are many other types of generative AI that can be used to power new features in your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generative Text Tools
&lt;/h3&gt;

&lt;p&gt;Generative text tools can create idea lists, suggest names for products, create personalized content, and write documentation for your app idea.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openai.com/blog/openai-api/"&gt;Open AI's GPT-3&lt;/a&gt; is a large language model that can be used to generate text, images, and code. The Open AI API lets you access GPT-3 programmatically, which is perfect for powering new capabilities in your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chat.openai.com"&gt;ChatGPT&lt;/a&gt; is a chat bot that uses GPT-3 to generate responses to user questions. It's a great way to experiment with GPT-3 and see how it works. ChatGPT is a particularly powerful tool for brainstorming new ideas, as it retains the context of previous questions and answers within "conversations." Simply write messages to ChatGPT to ask questions, to instruct it to write for you, or to ask it to summarize text for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generative Image Tools
&lt;/h3&gt;

&lt;p&gt;Generative image tools can create app designs, offer design inspiration, and generate unique images for your blog posts. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://openai.com/blog/dall-e-2/"&gt;Open AI's DALL-E 2&lt;/a&gt; is a text-to-image model that can generate images based on text descriptions. Similar to GPT-3, DALL-E 2 can be accessed via the Open AI API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://midjourney.com/"&gt;Midjourney&lt;/a&gt; is another image diffusion model. It is a web-based service that can generate impressive scenes with relatively simple text prompts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stability.ai/blog/stable-diffusion-public-release"&gt;Stable Diffusion&lt;/a&gt; is a freely distributed image diffusion model that can be used to generate images from text prompts. Several web services have launched to make it easy to use the model, or you can run Stable Diffusion on your own computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/DreamBooth"&gt;DreamBooth&lt;/a&gt; lets you train a generative AI model on images of yourself to generate new images of yourself through text-to-image tools like Stable Diffusion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://uizard.io/"&gt;Uizard&lt;/a&gt; is a free tool that generates mockups of app interfaces, which can be used to help designers and developers visualize what their app might look like and get a sense of how it might function with less effort than was previously required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Generative Tools
&lt;/h3&gt;

&lt;p&gt;There are countless other generative AI tools that can be used to power even more interesting app ideas. Generative AI models exist for all sorts of tasks and creative work. The following represents a small sample of the broad range of tools available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.topazlabs.com/topaz-video-ai"&gt;Topaz Video AI&lt;/a&gt; performs video enhancement tasks such as deinterlacing, upscaling, and motion interpolation. This can help you increase the resolution of low-quality videos, restore old footage, or create smooth slow motion effects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aiva.ai/"&gt;AIVA&lt;/a&gt; is a creative music assistant that can help you generate novel music in several different styles and genres.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://murf.ai/text-to-speech"&gt;Murf&lt;/a&gt; is a text-to-speech tool that generates realistic voices to read text you provide. This can be used for voiceover work, to create audio books, or to generate audio presentations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; is a programming assistant that can suggest entire functions or code snippets based on existing code. Copilot greatly speeds up development of simple features and can be used to guide development of complex features.&lt;/p&gt;

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

&lt;p&gt;Generative AI has the potential to be a powerful tool for app development, helping product managers, designers, and developers come up with new ideas and bring those ideas to life. While there are challenges and limitations to consider, such as the need for high-quality data and the potential for bias or incorrect information, the potential benefits of using generative AI are significant. As this technology continues to advance, we can expect to see even more exciting and innovative app ideas being developed using generative AI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>apps</category>
      <category>mobile</category>
      <category>startup</category>
    </item>
    <item>
      <title>Default to Deny for More Secure Apps</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Wed, 18 Jan 2023 15:32:18 +0000</pubDate>
      <link>https://dev.to/twinsun/default-to-deny-for-more-secure-apps-4o80</link>
      <guid>https://dev.to/twinsun/default-to-deny-for-more-secure-apps-4o80</guid>
      <description>&lt;p&gt;Every product we build deals with user authorization.&lt;br&gt;
Users may only access certain features or data based on their permissions within the app.&lt;br&gt;
While we want to ensure users can access everything they should, we also want to ensure they can't access anything they shouldn't.&lt;br&gt;
This is why we default to deny all user privileges when starting a new app.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Default to Deny?
&lt;/h2&gt;

&lt;p&gt;Network security practitioners are familiar with a similar concept.&lt;br&gt;
The National Institute of Standards and Technology (NIST) issued security guidance that describes a  &lt;a href="https://csf.tools/reference/nist-sp-800-53/r4/sc/sc-7/sc-7-5/"&gt;"deny by default / allow by exception"&lt;/a&gt; security control.&lt;br&gt;
The idea is simple: deny all network traffic by default and only allow traffic that is explicitly authorized.&lt;br&gt;
This concept is more broadly known as the &lt;a href="https://csrc.nist.gov/glossary/term/least_privilege"&gt;principle of least privilege&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;"Default to deny" is our application of this principle to mobile and web apps. &lt;br&gt;
When we start a new app, users are initially denied access to all features and data.&lt;br&gt;
We then explicitly grant access to the things the user is permitted to access.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why is Default to Deny Important?
&lt;/h2&gt;

&lt;p&gt;Defaulting to deny is an important safeguard for user data.&lt;br&gt;
Let's say we have an app that lets users purchase books.&lt;br&gt;
Users save their credit card information in the app so they don't have to re-enter it every time they make a purchase.&lt;/p&gt;

&lt;p&gt;If we let every user access everything by default, what might happen?&lt;br&gt;
We could miss all of the ways that credit card data could be accessed.&lt;br&gt;
A malicious user could probably find a way to steal credit card numbers from other user accounts.&lt;br&gt;
We certainly don't want that!&lt;br&gt;
So why don't we start with the complete opposite position?&lt;/p&gt;

&lt;p&gt;If we deny all access by default, no one can steal credit card information.&lt;br&gt;
Users also wouldn't be able to access their own credit card details.&lt;br&gt;
That isn't &lt;em&gt;great&lt;/em&gt;, but it's much better than letting anyone see all credit card numbers.&lt;br&gt;
And opening up user permissions just a little bit is much easier than trying to find all the ways sensitive data might be accessed.&lt;/p&gt;

&lt;p&gt;By defaulting to deny, we can be confident that no one is doing anything unless it is explicitly allowed.&lt;br&gt;
Our users' data is protected from accidental exposure and malicious attacks.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Implement Default to Deny
&lt;/h2&gt;

&lt;p&gt;As an example of how to default to deny, consider a Ruby on Rails app (&lt;a href="https://twinsunsolutions.com/blog/why-we-use-ruby-on-rails-for-web-apps"&gt;as we tend to do&lt;/a&gt;).&lt;br&gt;
The primary way a user interacts with the app is through API endpoints powered by controllers.&lt;br&gt;
We use &lt;a href="https://github.com/varvet/pundit"&gt;Pundit&lt;/a&gt;, a popular authorization library for Rails, to manage user permissions.&lt;/p&gt;

&lt;p&gt;Using a &lt;code&gt;BaseController&lt;/code&gt; class, we can define an &lt;code&gt;after_action&lt;/code&gt; that ensures an authorization check is performed on all requests by default.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pundit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Authorization&lt;/span&gt;
  &lt;span class="n"&gt;after_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_authorized&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can define a base Pundit policy that denies all access by default.&lt;br&gt;
Note that we don't need to define any actions in the policy.&lt;br&gt;
Pundit will automatically check for a policy method that matches the controller action.&lt;br&gt;
However, actions are defined in our example to make it clear that we are denying access by default.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePolicy&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can have any controller inherit from &lt;code&gt;BaseController&lt;/code&gt; and Pundit will deny all access to all actions.&lt;br&gt;
For each model class we define, we can add a new Pundit policy that inherits from &lt;code&gt;BasePolicy&lt;/code&gt;.&lt;br&gt;
Then we can explicitly allow access to the actions we want to allow.&lt;br&gt;
Consider the following policy example for a &lt;code&gt;Book&lt;/code&gt; model.&lt;br&gt;
All users can use the &lt;code&gt;index&lt;/code&gt; or &lt;code&gt;show&lt;/code&gt; actions, but only administrators can use the &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, or &lt;code&gt;destroy&lt;/code&gt; actions.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BasePolicy&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Starting With Security
&lt;/h2&gt;

&lt;p&gt;Default to deny is a simple concept that can have a big impact on the security of your app.&lt;br&gt;
By denying all access by default, you can be confident that no one is doing anything unless it is explicitly allowed.&lt;br&gt;
This is especially important for apps that deal with sensitive user data.&lt;br&gt;
Consider defaulting to deny when starting your next app and see how much more confident you feel about your authorization rules.&lt;/p&gt;

</description>
      <category>security</category>
      <category>rails</category>
      <category>authorization</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Building Better Apps with Automated Tests</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 05 Dec 2022 16:54:44 +0000</pubDate>
      <link>https://dev.to/twinsun/building-better-apps-with-automated-tests-1ha4</link>
      <guid>https://dev.to/twinsun/building-better-apps-with-automated-tests-1ha4</guid>
      <description>&lt;p&gt;Automated testing is an essential software development practice.&lt;br&gt;
Agile practitioners rely on automated tests to ensure that their apps are always in a working state.&lt;br&gt;
Often, developers who are new to automated testing raise concerns about the practice.&lt;br&gt;
However, once you become acclimated to testing, you will find that it is a valuable tool that speeds up development.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Automated Testing?
&lt;/h2&gt;

&lt;p&gt;Automated testing is the practice of writing code that tests your code.&lt;br&gt;
Typically, automated tests are run as part of a continuous integration (CI) pipeline.&lt;br&gt;
That means that every time a developer makes a code change, the automated tests ensure that all previously written code still works as expected.&lt;/p&gt;

&lt;p&gt;There are several types of automated tests.&lt;br&gt;
The most common type of test is a unit test.&lt;br&gt;
Unit tests are written to test a single "unit" of your code: it tests a small, self-contained piece of functionality.&lt;br&gt;
These tests are quick to write and run, and generally make up the majority of automated tests for an application.&lt;/p&gt;

&lt;p&gt;Integration tests test the "integration" (interaction) between two or more units of code.&lt;br&gt;
For example, an integration test might test that a user can log in to your application.&lt;br&gt;
Such a test would require that the user interface, the authentication system, and the database all work together as expected.&lt;/p&gt;

&lt;p&gt;End-to-end (E2E) tests test the entire application.&lt;br&gt;
Ideally, E2E tests fully test functionality from the user's perspective.&lt;br&gt;
As an example, an E2E test might test that a user can log in to your application, navigate to a specific page, and see the expected content.&lt;/p&gt;

&lt;p&gt;Integration tests are more complex to write and slower to run than unit tests.&lt;br&gt;
E2E tests are even more complex and slower to run.&lt;br&gt;
This is why unit tests make up the majority of automated tests for an application.&lt;br&gt;
Integration and E2E tests are incredibly valuable.&lt;br&gt;
However, due to the trade-offs involved, they are often used to test only the most critical functionality that can not be adequately covered by unit tests.&lt;/p&gt;
&lt;h2&gt;
  
  
  An Example of an Automated Test
&lt;/h2&gt;

&lt;p&gt;Here's a simple example of an automated unit test in a &lt;a href="https://twinsunsolutions.com/blog/why-we-use-ruby-on-rails-for-web-apps"&gt;Ruby on Rails&lt;/a&gt; social media app.&lt;br&gt;
This test ensures that a &lt;code&gt;Post&lt;/code&gt; model is associated with a &lt;code&gt;user&lt;/code&gt; and has a valid &lt;code&gt;body&lt;/code&gt; attribute.&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="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :model&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;belong_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_length_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is_at_most&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how simple this test is.&lt;br&gt;
You can see that it's a unit test because it only tests a single "unit" of code: a &lt;code&gt;Post&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;it&lt;/code&gt; statements are assertions.&lt;br&gt;
Assertions are statements that test the behavior of your code.&lt;br&gt;
They're a central part of automated testing: the assertions are what ensure that your application's code is working as expected.&lt;/p&gt;

&lt;p&gt;The above test is written using the &lt;a href="https://rspec.info/"&gt;RSpec&lt;/a&gt; testing framework.&lt;br&gt;
While RSpec is a popular choice for Ruby on Rails apps, there are plenty of testing frameworks to choose from for most programming languages and frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Automated Testing
&lt;/h2&gt;

&lt;p&gt;When your team adopts automated testing, you will quickly realize several benefits.&lt;br&gt;
A tested code base is unlikely to have regressions.&lt;br&gt;
Regressions are bugs that are introduced in old code when new code is added, typically due to unintended side effects.&lt;/p&gt;

&lt;p&gt;Automated tests also help you when you need to change existing code to support new features.&lt;br&gt;
This is known as "refactoring."&lt;br&gt;
If your app is adequately covered by a well-written test suite, refactoring carries little risk.&lt;br&gt;
When you make a breaking code change, your tests catch the problem.&lt;/p&gt;

&lt;p&gt;You will write better code with automated tests as well.&lt;br&gt;
When you write a test, you are forced to think about how your code will be used.&lt;br&gt;
That means writing code in a modular way that is easy to understand.&lt;br&gt;
Modular code has benefits beyond testability.&lt;br&gt;
A modular code base is easier to understand, maintain, and extend.&lt;/p&gt;

&lt;p&gt;Automated tests also speed up development.&lt;br&gt;
While there is an initial learning curve when you first start writing tests, the time you spend writing tests will quickly pay off.&lt;br&gt;
As you write more tests, you will find that you can make changes to your code with confidence.&lt;br&gt;
You will have clearer ideas about how to structure your code, and you will be able to test your changes more quickly.&lt;br&gt;
Automated tests aren't just reserved for catching problems.&lt;br&gt;
You can also use them to experiment with new ideas and to try out different approaches to solving a problem.&lt;br&gt;
Instead of clicking around in an app, you can write a test case and run it over and over as you program until you get the intended result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reservations About Automated Testing
&lt;/h2&gt;

&lt;p&gt;The time investment for automated testing is a common concern.&lt;br&gt;
New developers can not easily see the benefits of automated testing when it initially slows down their work.&lt;br&gt;
However, within a few weeks, most developers begin seeing the benefits of automated testing.&lt;br&gt;
They become faster and more confident in their work.&lt;/p&gt;

&lt;p&gt;Another common concern is that automated tests prevent developers from making changes to the code.&lt;br&gt;
This can be true in a sense: tests prevent developers from breaking existing functionality when they make new changes.&lt;br&gt;
That can be frustrating when you're new to automated testing.&lt;br&gt;
Instead of thinking of automated tests as a barrier to change, think of them as a safety net.&lt;br&gt;
They ensure that you can make changes to your code without breaking existing functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persuading Your Team to Invest in Automated Testing
&lt;/h2&gt;

&lt;p&gt;Management is often hesitant to invest in automated testing.&lt;br&gt;
"There's no time right now" is a common refrain.&lt;br&gt;
Managers and developers alike will say that the team can adopt better practices like automated testing after the next big release.&lt;br&gt;
However, in practice, what you don't start today will rarely happen later.&lt;/p&gt;

&lt;p&gt;If you face resistance in adopting automated testing, you can try to convince your team by showing them the benefits.&lt;br&gt;
Start by testing your own code.&lt;br&gt;
When other developers make code contributions, run your tests against their changes to ensure that the tests still pass.&lt;br&gt;
If you find a test failure, inform the team member and let them know how you discovered the problem.&lt;/p&gt;

&lt;p&gt;As you gain experience with automated testing, you will be able to build complex features more quickly than before.&lt;br&gt;
Testers will find fewer bugs in your code, and you will be able to fix the bugs they do find more quickly.&lt;br&gt;
The quality and speed of your work will increase, making a clear case for the rest of the team to adopt automated testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Started with Automated Testing
&lt;/h2&gt;

&lt;p&gt;To get started with testing, search for a popular testing framework for your programming language.&lt;br&gt;
PHP has &lt;a href="https://phpunit.de/"&gt;PHPUnit&lt;/a&gt;, for example.&lt;br&gt;
Java has &lt;a href="https://junit.org/"&gt;JUnit&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://twinsunsolutions.com/blog/twice-the-speed-half-the-price-flutter-apps-winner-for-startups"&gt;Flutter apps&lt;/a&gt; use &lt;a href="https://flutter.dev/docs/cookbook/testing/integration/introduction"&gt;Flutter Driver&lt;/a&gt;.&lt;br&gt;
No matter your language or framework, there is a testing framework that will work for your app.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>programming</category>
      <category>unittest</category>
    </item>
    <item>
      <title>How to Use Developer Checklists to Increase Your Confidence</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 14 Nov 2022 22:49:53 +0000</pubDate>
      <link>https://dev.to/twinsun/how-to-use-developer-checklists-to-increase-your-confidence-1mn8</link>
      <guid>https://dev.to/twinsun/how-to-use-developer-checklists-to-increase-your-confidence-1mn8</guid>
      <description>&lt;p&gt;Checklists are a step-by-step list of instructions to follow.&lt;br&gt;
They can help you plan for handling complex events or multi-step procedures.&lt;br&gt;
A good checklist increases your confidence in what you are doing.&lt;/p&gt;

&lt;p&gt;They're great tools for rare events, complex procedures, and stressful situations.&lt;br&gt;
A friend once told me that checklists help you think while you are not stressed so you don't have to think when you are stressed.&lt;/p&gt;

&lt;p&gt;Other industries make extensive use of checklists.&lt;br&gt;
However, outside of larger DevOps teams with extensive playbooks, most software development teams do not routinely use checklists.&lt;/p&gt;

&lt;p&gt;The airline industry and &lt;a href="https://www.newyorker.com/magazine/2007/12/10/the-checklist"&gt;healthcare professionals&lt;/a&gt; have embraced checklists.&lt;br&gt;
They have found that checklists reduce errors and improve outcomes.&lt;br&gt;
Air travel is so safe in part because of checklists used by pilots and maintenance crews.&lt;br&gt;
Healthcare professionals have found that checklists reduce complications and improve outcomes for their patients.&lt;/p&gt;

&lt;p&gt;We have realized similar benefits in software development.&lt;br&gt;
Checklists mitigate the risk of human error and improve the quality of our work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Developer Checklist?
&lt;/h2&gt;

&lt;p&gt;A developer checklist is a list of steps for performing a software development task.&lt;br&gt;
There are plenty of scenarios in software development where checklists are useful.&lt;br&gt;
&lt;a href="https://twinsunsolutions.com/blog/what-to-expect-from-code-review"&gt;Code reviews&lt;/a&gt;, &lt;a href="https://twinsunsolutions.com/blog/deep-dive-migrating-from-data-center-to-aws"&gt;server migrations&lt;/a&gt;, and release management are just a few examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use a Developer Checklist?
&lt;/h2&gt;

&lt;p&gt;As a concrete example, a server migration requires several manual steps to complete.&lt;br&gt;
Rehearsing those steps repeatedly is important.&lt;br&gt;
Rehearsals help you identify and resolve issues before the final migration.&lt;br&gt;
As you perform migration steps during rehearsal, you can write a checklist composed of every step you take.&lt;br&gt;
All subsequent rehearsals can use the checklist.&lt;br&gt;
Then, by the time you're conducting final migration, you have a tested and complete checklist to follow.&lt;br&gt;
The opportunity for human error during this likely stressful event is greatly reduced!&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using a Checklist
&lt;/h2&gt;

&lt;p&gt;While checklists may not eliminate your stress during complex procedures, they can reduce stress.&lt;br&gt;
You will feel more confident running through a list of steps than winging it.&lt;/p&gt;

&lt;p&gt;As we already mentioned, checklists also mitigate the risk of making errors.&lt;br&gt;
If you are following a well-written checklist, you know which step comes next.&lt;br&gt;
You also know the outcome of each step.&lt;br&gt;
In other words, there are no surprises when you follow a checklist.&lt;/p&gt;

&lt;p&gt;Additionally, checklists are reusable.&lt;br&gt;
When you write a checklist for a complex procedure, you can reuse it for similar future procedures.&lt;br&gt;
A server migration checklist is a good example.&lt;br&gt;
While each server migration may be unique, the majority of the work required is similar.&lt;br&gt;
Having a starting point for a checklist will help you recall all required activities for a migration, even if you have to change a few steps here and there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of Developer Checklists
&lt;/h2&gt;

&lt;p&gt;At Twin Sun, we use developer checklists for any events that are rare or complex.&lt;br&gt;
Here are some examples of developer checklists we have written:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new developer setup checklist&lt;/li&gt;
&lt;li&gt;code review checklist&lt;/li&gt;
&lt;li&gt;new client project setup checklist&lt;/li&gt;
&lt;li&gt;server migration checklists&lt;/li&gt;
&lt;li&gt;app deployment checklists&lt;/li&gt;
&lt;li&gt;AWS infrastructure setup checklists&lt;/li&gt;
&lt;li&gt;postmortem checklist&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started with Developer Checklists
&lt;/h2&gt;

&lt;p&gt;If you're just getting started with checklists, consider starting with a checklist for a simple task.&lt;br&gt;
For example, documenting new developer setup for your team can be a good starting point.&lt;br&gt;
Even if you don't plan on a new developer joining your team, you can follow the checklist the next time you set up a new computer.&lt;/p&gt;

&lt;p&gt;As you follow and maintain the checklist over a few attempts, you'll find that new developer setup is completed more quickly and confidently.&lt;br&gt;
Common gotchas go away as you document steps for resolving them.&lt;br&gt;
The time you spend writing a checklist will quickly pay dividends as it speeds up complex procedures.&lt;/p&gt;

</description>
      <category>checklists</category>
      <category>professionalimprovement</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>New Jekyll Plugin for Link Attributes</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Fri, 11 Nov 2022 16:01:21 +0000</pubDate>
      <link>https://dev.to/twinsun/new-jekyll-plugin-for-link-attributes-2g97</link>
      <guid>https://dev.to/twinsun/new-jekyll-plugin-for-link-attributes-2g97</guid>
      <description>&lt;p&gt;Our website uses &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;, a static site framework. &lt;br&gt;
I chose Jekyll to help us manage a large amount of content.&lt;br&gt;
Managing all of our site content in a code repository means we can manipulate all sorts of things with a little bit of code.&lt;/p&gt;

&lt;p&gt;One thing I've wanted to do is ensure that all of our &lt;a href="https://moz.com/learn/seo/external-link"&gt;external links&lt;/a&gt; default to having reasonable attributes.&lt;br&gt;
For example, all of our external links have a &lt;code&gt;rel="noopener"&lt;/code&gt; attribute to prevent &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/noopener"&gt;tabnabbing&lt;/a&gt;.&lt;br&gt;
I also  wanted to add a &lt;code&gt;target="_blank"&lt;/code&gt; attribute so external links open in a new tab instead of sending visitors away from our site.&lt;/p&gt;

&lt;p&gt;Adding the attributes to every link on the site was cumbersome, and we would miss some links sometimes.&lt;br&gt;
Though my primary goal was to increase consistency across all of our pages, proper link attributes also offer some minor &lt;a href="https://moz.com/blog/nofollow-sponsored-ugc"&gt;SEO benefits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I found a Jekyll plugin that would do most of what I wanted, but it was not compatible with the latest Jekyll version.&lt;br&gt;
Instead of settling for a partial solution and trying to make it work with our site, I decided to create a new plugin.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/twinsunllc/jekyll-link-attributes"&gt;jekyll-link-attributes plugin&lt;/a&gt; automatically adds your desired &lt;code&gt;rel&lt;/code&gt; and &lt;code&gt;target&lt;/code&gt; attributes to all external links on your site.&lt;br&gt;
This happens during the build process, ensuring we catch every external link every time.&lt;/p&gt;

&lt;p&gt;The plugin's default configuration is a good place to start, but it is easy to customize your link attributes.&lt;br&gt;
You can add any &lt;code&gt;rel&lt;/code&gt; or &lt;code&gt;target&lt;/code&gt; attribute you like and exclude links that you don't want to modify.&lt;br&gt;
If you use Jekyll, &lt;a href="https://github.com/twinsunllc/jekyll-link-attributes"&gt;give it a try&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>jekyl</category>
      <category>ruby</category>
      <category>seo</category>
      <category>rubygem</category>
    </item>
    <item>
      <title>Deep Dive: Migrating from a Data Center to AWS</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Thu, 03 Nov 2022 15:47:37 +0000</pubDate>
      <link>https://dev.to/twinsun/deep-dive-migrating-from-a-data-center-to-aws-48oa</link>
      <guid>https://dev.to/twinsun/deep-dive-migrating-from-a-data-center-to-aws-48oa</guid>
      <description>&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%2Fqm73l2ipj9vdsjskexge.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%2Fqm73l2ipj9vdsjskexge.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
A long-term client of ours had managed their web applications in a colocation data center for several years.&lt;br&gt;
Their apps ran on &lt;a href="https://martinfowler.com/bliki/SnowflakeServer.html" rel="noopener noreferrer"&gt;snowflake servers&lt;/a&gt;: unique, manually configured servers that could not be easily replaced.&lt;br&gt;
Snowflake servers pose a disaster recovery risk, but that concern was secondary to the amount of time the IT department was spending troubleshooting problems on their physical servers.&lt;/p&gt;

&lt;p&gt;Diagnosing problems with each application was troublesome: IT administrators were (understandably) unfamiliar with the ins and outs of every web app.&lt;br&gt;
Some of their infrastructure was inherited from a previous team, who left little documentation behind.&lt;br&gt;
Keeping the lights on was becoming a larger and larger burden.&lt;br&gt;
Downtime and degraded performance issues began increasing in frequency and severity.&lt;/p&gt;

&lt;p&gt;As their web applications gained popularity, the existing infrastructure failed to keep up.&lt;br&gt;
Response times increased drastically during high-traffic times, with servers sometimes becoming entirely unresponsive.&lt;br&gt;
This led to a frustrating user experience for the majority of users, including internal stakeholders who depended on some of these web apps for their daily work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Analyzing their infrastructure
&lt;/h2&gt;

&lt;p&gt;The client's IT manager reached out to us to see if we could help migrate 16 of their sites to a cloud hosting provider and improve infrastructure reliability.&lt;br&gt;
We already maintained a few of their web applications, so they knew we would be familiar with what was needed to support a migration.&lt;br&gt;
We also have plenty of experience managing complex services and web apps hosted on &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;Amazon Web Services (AWS)&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The IT team's goals for the new cloud-based infrastructure were (1) to reduce the time investment required to maintain their infrastructure, and (2) to minimize downtime.&lt;br&gt;
We began reviewing their existing setup with these goals in mind.&lt;br&gt;
As we dug in deeper, we realized that there were a few issues with the existing setup that needed to be addressed as part of the migration.&lt;/p&gt;

&lt;p&gt;First, many of the web applications were making hundreds of database calls for each request.&lt;br&gt;
These apps ran well enough most of the time, but database connections quickly became the constraint during high-traffic times.&lt;br&gt;
This performance bottleneck was exacerbated by the fact that several web apps were hosted on each server: a surge in traffic to one of the sites would bring down several of them.&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Ftop-load-average-38.png" alt="Output from running the console command  raw `top` endraw , which shows a load average of 38.67."&gt;
        Extremely high load average on a web server during a high-traffic period
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Second, some of the applications were as unique as the servers they were hosted on.&lt;br&gt;
The sites used a mix of older web development frameworks.&lt;br&gt;
This meant we'd need to review each application to uncover any dependencies or constraints that might need to be accounted for in a server migration.&lt;br&gt;
We'd have to take a slightly different approach to configuring and migrating each application.&lt;/p&gt;

&lt;p&gt;Third, several of the apps had databases that were in unexpected or corrupted states.&lt;br&gt;
Some databases lacked foreign key integrity checks, and some tables had become corrupt due to the server running out of disk space from time to time.&lt;br&gt;
We'd definitely need to fix the corrupted tables, and decided we'd turn on foreign key integrity checks to hopefully avoid any future data integrity problems.&lt;/p&gt;
&lt;h2&gt;
  
  
  Preparing a migration plan
&lt;/h2&gt;

&lt;p&gt;We identified steps we needed to take to address these issues and ensure the migrations would be a success.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Map the existing architecture to discover dependencies&lt;/li&gt;
&lt;li&gt;Map out the new cloud-based architecture&lt;/li&gt;
&lt;li&gt;Make any needed app modifications to support the new infrastructure&lt;/li&gt;
&lt;li&gt;Optimize web app performance&lt;/li&gt;
&lt;li&gt;Automate deployments to the new AWS infrastructure&lt;/li&gt;
&lt;li&gt;Correct issues with the existing database servers&lt;/li&gt;
&lt;li&gt;Document the migration plan for each app&lt;/li&gt;
&lt;li&gt;Perform several dry runs of the migration plan&lt;/li&gt;
&lt;li&gt;Migrate and validate each app&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Map the existing architecture
&lt;/h3&gt;

&lt;p&gt;We began by investigating the existing infrastructure for each web app.&lt;br&gt;
Thankfully, most applications were simple and had few dependencies.&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fdata-center-simple-architecture.png" alt="Architecture diagram showing a simple web server and database"&gt;
        Existing architecture for a simple web app
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;A few applications, however, had more dependencies.&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fdata-center-complex-architecture.png" alt="Architecture diagram showing a web app with third-party integrations"&gt;
        A slightly more complex architecture
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Generally these dependencies did not significantly alter our new architecture, but did require additional testing prior to the production migration.&lt;/p&gt;
&lt;h3&gt;
  
  
  Planning the cloud-based architecture
&lt;/h3&gt;

&lt;p&gt;As our team planned to take over day-to-day management of the new AWS infrastructure, we focused on designing a resilient, scalable, and manageable architecture.&lt;br&gt;
We also wanted to ensure that our client wasn't locked in to using a particular cloud provider.&lt;br&gt;
Therefore, we decide to Dockerize each web app and utilize Elastic Beanstalk's Elastic Container Service (ECS) platform.&lt;/p&gt;

&lt;p&gt;Elastic Beanstalk simplifies deployments, environment configuration, and horizontal scaling.&lt;br&gt;
Combined with ECS's containerization support, we could keep all server configuration encapsulated within Docker containers.&lt;br&gt;
Since Docker is so widely supported across cloud providers, we could reasonably move to another provider at our client's discretion with minimal adjustments.&lt;/p&gt;

&lt;p&gt;This automated and repeatable configuration approach was in stark contrast to the existing production environments.&lt;br&gt;
The existing infrastructure was manually configured, with many service dependencies residing on the snowflake servers.&lt;br&gt;
Running all services from a single server would not be feasible on Amazon Web Services, requiring us to devise a new architecture.&lt;/p&gt;

&lt;p&gt;For example, the existing web app servers sent their own email via sendmail.&lt;br&gt;
This is not permissible in AWS due to their anti-spam policies, so we needed to utilize AWS Simple Email Service (SES).&lt;br&gt;
Since the web apps only sent transactional emails, we decided to keep email services in AWS.&lt;br&gt;
However, other third-party email service providers such as SendGrid or Mailgun would have met our needs just as well.&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Faws-architecture.png" alt="Architecture diagram of a scalable cloud-based architecture utilizing Amazon Web Services"&gt;
        The new cloud-based architecture
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;This architecture looks very different from the existing setup.&lt;br&gt;
Our approach to cloud-based hosting is completely different from the snowflake servers we were replacing.&lt;br&gt;
Leveraging everything that cloud-based infrastructure offers requires a change in how you model systems.&lt;/p&gt;

&lt;p&gt;Instead of treating each server as special or necessary, our new servers would be treated as &lt;a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/" rel="noopener noreferrer"&gt;cattle, not pets&lt;/a&gt;.&lt;br&gt;
That is, we expect we will have hardware failures.&lt;br&gt;
To combat this inevitability, we designed an architecture to treat all resources as replaceable.&lt;/p&gt;

&lt;p&gt;Instead of setting up a single server to host each web app, we created repeatable configurations that can be applied to new servers as needed.&lt;br&gt;
For example, if a web server is determined to be "unhealthy" or unresponsive, we can have the load balancer provision a new web server, apply our configuration, and replace the unhealthy one.&lt;br&gt;
Similarly, if a single server is not enough to handle a traffic surge, the load balancer can provision as many servers as needed to handle the load.&lt;br&gt;
That is, we expect the needs of the system to grow as our client's business continues to grow.&lt;/p&gt;

&lt;p&gt;That dynamic and responsive approach to infrastructure is a key benefit of cloud-based hosting.&lt;br&gt;
You would have to over-provision hardware in your own data center to reap the same benefits.&lt;/p&gt;
&lt;h3&gt;
  
  
  Performance analysis
&lt;/h3&gt;

&lt;p&gt;To ensure that we right-sized our new infrastructure, we evaluated resource utilization on the existing infrastructure.&lt;br&gt;
We identified the ten most active endpoints on each web app and focused our performance analysis on those endpoints.&lt;br&gt;
Several of the web apps used Google Analytics, which we used to reveal a list of the most visited pages over the past 12 months.&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fga-example-pageviews-list.png" alt="Screenshot of a Google Analytics report showing the most popular pages on a site"&gt;
        List of the most popular pages in Google Analytics
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Some web apps did not have any sort of analytics or application performance monitoring in place, so we analyzed traffic patterns using Apache server logs.&lt;br&gt;
This involved a bit of shell scripting to count the number of visits for each endpoint:&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;# count the number of visits to each path, sorted by descending visit count&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; /var/log/apache2/access.log&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"GET /[^ ]+"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 6 | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-nr&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then used platform-specific performance analysis tools to determine which of these endpoints needed the most attention.&lt;br&gt;
The highest-traffic sites we needed to optimize used Laravel, a popular PHP framework.&lt;br&gt;
We used &lt;a href="https://github.com/barryvdh/laravel-debugbar" rel="noopener noreferrer"&gt;Laravel Debugbar&lt;/a&gt; to analyze those web apps.&lt;/p&gt;

&lt;p&gt;You can see that we had some work to do:&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fdebugbar-1549-queries.png" alt="Screenshot of Laravel Debugbar with 1549 database queries"&gt;
        Debugbar analysis showing 1549 database queries for a single page load
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;This was the most extreme example, with over 1,500 database calls to load a rather simple page.&lt;br&gt;
However, several popular pages on each site would make hundreds of database calls:&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fdebugbar-786-queries.png" alt="Screenshot of Laravel Debugbar with 786 database queries"&gt;
        Debugbar analysis showing 786 database queries for a typical page
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;There were enough of these unoptimized pages that we decided a caching layer would be more cost-effective than individually optimizing each page.&lt;/p&gt;
&lt;h3&gt;
  
  
  Refactoring to support the new architecture
&lt;/h3&gt;

&lt;p&gt;Naturally, some of these architectural changes would require changing how the web apps interact with other services.&lt;br&gt;
Since we moved supporting services off of the web app servers themselves, we needed to make integrations with those services more configurable.&lt;/p&gt;

&lt;p&gt;Most of our refactoring was simple: making hard-coded values configurable, or turning certain features on or off depending on environment.&lt;br&gt;
Sending email through SES meant specifying an SMTP hostname and credentials, and in some cases upgrading email delivery to use TLS for improved security.&lt;br&gt;
We introduced &lt;a href="https://memcached.org/" rel="noopener noreferrer"&gt;Memcached&lt;/a&gt; to cache database query results and server-side rendered views.&lt;/p&gt;

&lt;p&gt;Caching rendered views did require adjusting how some components were rendered.&lt;br&gt;
The web apps did not have a built-in mechanism for view caching, so we had to implement our own.&lt;br&gt;
We had a generic caching solution ready for the highest-traffic sites with a few hours of work.&lt;/p&gt;
&lt;h3&gt;
  
  
  Performance optimization
&lt;/h3&gt;

&lt;p&gt;Our caching solution helped us take pages with hundreds of database calls down to 10-15 database calls for most pages:&lt;/p&gt;


    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftwinsunsolutions.com%2Fassets%2Fimages%2Fposts%2F2022-09-09%2Fdebugbar-15-queries.png" alt="Screenshot of Laravel Debugbar with 15 database queries"&gt;
        Debugbar analysis showing a page reduced from 786 database queries to 15
    
    


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;We analyzed app performance by using the web app as a single user.&lt;br&gt;
This certainly led to needed improvements in the user experience, but the question still remained: how do we ensure that we plan the right capacity for each web app on AWS?&lt;/p&gt;
&lt;h3&gt;
  
  
  Load testing
&lt;/h3&gt;

&lt;p&gt;We set up staging environments in AWS to test our new infrastructure.&lt;br&gt;
Starting with some relatively modest capacity choices (1-2 &lt;code&gt;t3.small&lt;/code&gt; instances per web app), we performed load tests on the most popular pages on each site.&lt;br&gt;
We built some simple load testing scripts that wrapped &lt;a href="https://jmeter.apache.org/" rel="noopener noreferrer"&gt;Apache JMeter&lt;/a&gt; commands:&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'ruby-jmeter'&lt;/span&gt;

&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'example'&lt;/span&gt;
&lt;span class="n"&gt;environments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;aws: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;base_url: &lt;/span&gt;&lt;span class="s1"&gt;'https://staging.example.com'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;datacenter: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;base_url: &lt;/span&gt;&lt;span class="s1"&gt;'https://production.example.com'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;thread_counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;environments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:base_url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;thread_counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;thread_count&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="n"&gt;thread_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;loops: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Home Page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="ss"&gt;contains: &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;title&amp;gt;Website Name Goes Here&amp;lt;/title&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="s1"&gt;'main'&lt;/span&gt;
                    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="ss"&gt;contains: &lt;/span&gt;&lt;span class="s1"&gt;'Text rendered from database query results goes here.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="s1"&gt;'body'&lt;/span&gt;
                &lt;span class="k"&gt;end&lt;/span&gt;
                &lt;span class="c1"&gt;# additional pages and actions were performed here.&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;log: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;thread_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-threads.jtl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above produced several logs, one for each thread count and environment.&lt;br&gt;
We reviewed these logs to compare the performance of our new infrastructure against the existing infrastructure.&lt;/p&gt;

&lt;p&gt;Even though we configured Autoscaling in Elastic Beanstalk, we performed load testing in our AWS environments against a single &lt;code&gt;t3.small&lt;/code&gt; instance for each web app to make a reasonable comparison to the existing data center's performance.&lt;/p&gt;

&lt;h4 id="average-response-time"&gt;Average Response Time (milliseconds)&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;1 Thread&lt;/th&gt;
&lt;th&gt;5 Threads&lt;/th&gt;
&lt;th&gt;10 Threads&lt;/th&gt;
&lt;th&gt;20 Threads&lt;/th&gt;
&lt;th&gt;50 Threads&lt;/th&gt;
&lt;th&gt;100 Threads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;216&lt;/td&gt;
&lt;td&gt;265&lt;/td&gt;
&lt;td&gt;439&lt;/td&gt;
&lt;td&gt;883&lt;/td&gt;
&lt;td&gt;1939&lt;/td&gt;
&lt;td&gt;1973&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Center&lt;/td&gt;
&lt;td&gt;1950&lt;/td&gt;
&lt;td&gt;1435&lt;/td&gt;
&lt;td&gt;1173&lt;/td&gt;
&lt;td&gt;N/A*&lt;/td&gt;
&lt;td&gt;N/A*&lt;/td&gt;
&lt;td&gt;N/A*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;
    * We discontinued load testing on the Data Center environment, as it began to significantly increase &lt;br&gt;
    load average and could have had an adverse impact on production sites hosted on the server.
&lt;/p&gt;

&lt;p&gt;The AWS environment benefited from our caching solution and other architectural changes, so these improved performance metrics were expected.&lt;br&gt;
With our performance optimizations in place, we found that a single &lt;code&gt;t3.small&lt;/code&gt; instance was sufficient for each web app.&lt;br&gt;
In fact, several web apps were able to run on a &lt;code&gt;t3.micro&lt;/code&gt; based on their lower memory profiles.&lt;/p&gt;
&lt;h2&gt;
  
  
  Database improvements
&lt;/h2&gt;

&lt;p&gt;As described earlier, we had several database issues that needed to be cleaned up before we could complete our migrations.&lt;br&gt;
This was relatively straightforward.&lt;br&gt;
We addressed missing foreign key constraints, testing against a local copy of the production database to ensure our new reference checks did not conflict with any existing data.&lt;/p&gt;

&lt;p&gt;There were also a few tables that had become corrupted due to disk space issues on the production database servers.&lt;br&gt;
The servers would routinely run out of disk space, requiring someone to manually clean up old files.&lt;br&gt;
Unfortunately, sometimes disk space would run out during a write to a table.&lt;br&gt;
This would cause the table to become corrupted.&lt;br&gt;
MySQL thankfully has built-in tools to fix corrupted database tables, so fixing these tables was as simple as running a command on the affected databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;REPAIR&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;example_table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We made our corrections to the existing production environments to minimize downtime during the AWS migration.&lt;br&gt;
Fixing the database issues prior to migration helped us perform several migration dry runs to ensure the final move would go as smoothly as possible.&lt;/p&gt;
&lt;h3&gt;
  
  
  Writing migration checklists
&lt;/h3&gt;

&lt;p&gt;Once we were confident that our new infrastructure would serve our client's needs, we prepared for the production migrations.&lt;br&gt;
We planned to migrate one site at a time and ensure each one performed well before migrating the next one.&lt;br&gt;
This approach reduced complexity and increased our confidence with each migration.&lt;/p&gt;

&lt;p&gt;Since we had 16 web apps to migrate and many of them had very different migration steps, we knew there could be ample opportunity for human error during each migration.&lt;br&gt;
Dockerizing the web apps eliminated concerns about misconfiguring a server.&lt;br&gt;
Though, there were still several manual steps for each migration.&lt;br&gt;
We needed to turn on maintenance pages, sync data to the new database servers, sync storage directories, update DNS, and proxy traffic from the old server to the new environment (to catch any straggling DNS clients).&lt;/p&gt;

&lt;p&gt;There were dozens of small steps to take during each migration.&lt;br&gt;
We wrote a checklist to capture each step.&lt;br&gt;
Additionally, we documented all of our validation steps.&lt;br&gt;
These validation tests would give us a clear indication that our migrations were successful.&lt;/p&gt;

&lt;p&gt;Further, we planned for contingencies in case something unexpected occurred.&lt;br&gt;
We outlined steps to roll back to the data center environment if needed.&lt;br&gt;
We fully expected our migrations to succeed, given our thorough testing and planning, but wanted to minimize the risk of surprises.&lt;br&gt;
A plan to revert to the data center environment would allow us to quickly restore service to our clients if something unexpected occurred.&lt;br&gt;
While we never needed to execute the rollback plan, it did give us peace of mind during each migration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating migration steps
&lt;/h2&gt;

&lt;p&gt;Our migration checklists were rather long and could contribute to manual errors during each migration.&lt;br&gt;
With so many migrations to perform, we wanted to minimize the opportunities for error as much as possible.&lt;br&gt;
Any one failure could push back our overall schedule and shake our client's confidence.&lt;br&gt;
We decided to automate several manual steps to mitigate potential problems.&lt;/p&gt;

&lt;p&gt;While we knew not every step could be reasonably automated, several could.&lt;br&gt;
Loading data from the existing environment and into the new environment required several manual steps.&lt;br&gt;
A combination of &lt;code&gt;mysql&lt;/code&gt;, &lt;code&gt;rsync&lt;/code&gt;, and &lt;code&gt;scp&lt;/code&gt; commands were needed to copy databases and storage directories for each site.&lt;br&gt;
We authored shell scripts to automate these steps.&lt;/p&gt;

&lt;p&gt;These scripts were simple.&lt;br&gt;
Most replaced just a few commands.&lt;br&gt;
However, each script reduced opportunities for error and helped speed up the slowest steps (copying data).&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;##&lt;/span&gt;
&lt;span class="c"&gt;# Copy the storage directory for a site environment from the legacy production server for client-hosted sites.&lt;/span&gt;
&lt;span class="c"&gt;# Usage: client-copystorage [site hostname] [path to the desired storage directory]&lt;/span&gt;
&lt;span class="c"&gt;##&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-lt&lt;/span&gt; 2 &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Must supply an environment name and a path to a storage directory. Example usage:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  client-copystorage SITE_HOSTNAME PATH_TO_STORAGE_DIRECTORY"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"172.10.100.42"&lt;/span&gt;
rsync &lt;span class="nt"&gt;-vaz&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;:/var/www/html/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;/shared/storage/*"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the time we migrated the first site, we had scripts for syncing storage directories, copying databases, loading environment variables, and connecting to AWS RDS databases by their associated Elastic Beanstalk environment name.&lt;br&gt;
As we wrote these scripts for the very first site migration, we realized we could also automate preparation tasks such as Dockerizing each web app.&lt;br&gt;
We automated copying existing server configurations, dependencies, and application configuration files to speed up each site's prep work.&lt;/p&gt;

&lt;p&gt;The first few web apps would take us up to a week to prepare for migration.&lt;br&gt;
By the end of it, we were migrating a new web app every 2 days, primarily thanks to our scripts and checklists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dry runs and final checks
&lt;/h2&gt;

&lt;p&gt;We established a rule early on for each migration: we must have three consecutive dry runs through the migration checklist before scheduling the production migration.&lt;br&gt;
Repeatedly running through our checklists drastically reduced the chances of something going wrong during the production move.&lt;br&gt;
By the time we scheduled a migration, we knew we had worked out all the kinks and could follow our documented steps.&lt;/p&gt;

&lt;p&gt;A friend of mine once told me that checklists allow you to think when you aren't stressed so you don't have to think when you are stressed.&lt;br&gt;
Giving ourselves time to plan for problem scenarios made every production migration feel very predictable.&lt;br&gt;
Rehearsing our steps beforehand gave us a calm confidence during each migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Successfully migrating the production web apps
&lt;/h2&gt;

&lt;p&gt;Once we completed dry runs, we'd move the production site.&lt;br&gt;
Our step-by-step migration procedure, automated scripts, and validation checklists made each migration a success.&lt;/p&gt;

&lt;p&gt;The benefits were nearly immediate for the client's IT department.&lt;br&gt;
They no longer had to manually reconfigure or troubleshoot servers.&lt;br&gt;
The old servers could be shut down and removed from the colocation data center to reduce costs, decreasing overall monthly spend on infrastructure.&lt;/p&gt;

&lt;p&gt;A combination of performance optimizations, more resilient architecture, and AWS CloudWatch alarms has significantly reduced the time investment for managing these web applications.&lt;br&gt;
Health checks ensure that each new deployment is in a healthy state before serving end user traffic.&lt;br&gt;
Autoscaling ensures each web app has the resources allocated to economically handle its current level of traffic.&lt;/p&gt;

&lt;p&gt;On the old infrastructure, downtime and service degradation were common.&lt;br&gt;
Months after moving to the new infrastructure, we have maintained a spotless uptime record with virtually no manual intervention.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>datacenter</category>
      <category>servermigration</category>
      <category>snowflakeservers</category>
    </item>
    <item>
      <title>Why We Use Ruby on Rails for Web Apps</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 03 Oct 2022 14:46:52 +0000</pubDate>
      <link>https://dev.to/twinsun/why-we-use-ruby-on-rails-for-web-apps-1epe</link>
      <guid>https://dev.to/twinsun/why-we-use-ruby-on-rails-for-web-apps-1epe</guid>
      <description>&lt;p&gt;New clients often ask why we use Ruby on Rails to build web apps. Unless they know a Rails developer, they probably haven't heard of it. And Rails developers usually have very strong opinions about Rails.&lt;/p&gt;

&lt;p&gt;Oftentimes clients' questions about Ruby on Rails are thinly veiled concerns: does Rails perform well? Can it scale with my business? Is it easy to use?&lt;/p&gt;

&lt;p&gt;The truth is that Ruby on Rails is incredibly similar to many other MVC frameworks in many programming languages. There's plenty of online documentation, community support, and available libraries to simplify common tasks, much like what exists for most web development frameworks.&lt;/p&gt;

&lt;p&gt;Could we use something else? Sure! In fact, we do use other languages and frameworks from time to time, typically when helping clients salvage  existing projects they started elsewhere. We've even &lt;a href="https://twinsunsolutions.com/case-studies/rent-one/"&gt;built expertise around other frameworks&lt;/a&gt; because it was the best decision for our clients.&lt;br&gt;
When we have the option, though, we reach for Ruby on Rails to build new web apps. Our development team is quickly productive on new projects thanks to its capabilities and our deep experience with Ruby on Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Rails experience
&lt;/h2&gt;

&lt;p&gt;I first tried Ruby on Rails in 2012. At the time I was mostly using Java's Spring framework to build web apps, and had previously used a few PHP frameworks. Ruby's syntax felt intuitive. Ruby on Rails, as &lt;a href="https://rubyonrails.org/doctrine#optimize-for-programmer-happiness"&gt;the framework's website&lt;/a&gt; claims, is optimized for programmer happiness. As any Enterprise Java developer will tell you, this is a marked departure from some web development frameworks.&lt;/p&gt;

&lt;p&gt;Rails takes a "convention over configuration" approach.&lt;br&gt;
Once a developer learns the conventions, they quickly become productive in Rails. This was certainly the case for me.&lt;br&gt;
Initially, I felt like many things in Rails happened by way of magic. Other frameworks I used had their own conventions, but did not shy from requiring plenty of configuration to get things running.&lt;/p&gt;

&lt;p&gt;Within a few weeks, I felt more than comfortable in Ruby on Rails: I wanted to use Rails exclusively. A decade later, I still prefer Ruby on Rails over any other web development framework I've tried (and I've tried a lot).&lt;/p&gt;

&lt;p&gt;But all of that is only interesting to developers: who cares about how the sausage is made?&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivering results for our clients
&lt;/h2&gt;

&lt;p&gt;When we build an app, we prioritize attaining our clients' goals. Our clients want great products, not necessarily technological marvels that programmers fawn over. The Rails development experience lends itself to achieving results.&lt;/p&gt;

&lt;p&gt;Rarely do we find ourselves muddling around in Rails, trying to figure out how to make a feature work. With Ruby on Rails, we &lt;em&gt;know&lt;/em&gt; how the feature should work. The framework's conventions give us a straightforward roadmap with sane defaults for most features. That means we can focus on features themselves: building a frictionless user experience that helps our clients attain their goals.&lt;/p&gt;

&lt;p&gt;Ruby on Rails has immense community support. There are open source libraries (gems) for most popular third-party services, making integration with those services as simple as possible. Community-driven knowledge bases enable developers to accomplish practically any task with Ruby on Rails. This means that we can use Rails to build almost any feature you might envision for your product.&lt;/p&gt;

&lt;p&gt;Our experience with Rails translates into product reliability as well. We have used Rails to build dozens of production applications, so we know more about how those applications work than we know about any other app development framework.&lt;/p&gt;

&lt;p&gt;In other words, when we use Rails to build your web app, you get the very best of our web development expertise applied to your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web app performance
&lt;/h2&gt;

&lt;p&gt;When Rails was first released in 2003, it was criticized for its performance. Rails apps were noticeably slower than other web apps. Ruby is an interpreted language, which is naturally slower than compiled languages. However, the performance of the Rails framework and Ruby itself have both significantly improved over the past two decades.&lt;/p&gt;

&lt;p&gt;In practice, we have seen no meaningful difference in performance when comparing resource utilization between Rails and other non-Ruby frameworks for our real-world applications.&lt;br&gt;
There are also numerous ways to optimize performance when you do hit a performance bottleneck, regardless of language or framework. There has yet to be an application we've built where &lt;em&gt;any&lt;/em&gt; modern framework—Rails, Laravel, or otherwise—has hamstrung app performance.&lt;/p&gt;

&lt;p&gt;Typically, what developers dismiss as framework-specific performance woes are due to other issues in the app.&lt;br&gt;
For example, excessive database queries, memory consumption, slow disk access, or expensive network requests are much more likely to cause performance issues than your choice of framework. None of those issues are Rails-specific, and all of them have solutions (caching, query optimization, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  Popularity and transferability
&lt;/h2&gt;

&lt;p&gt;A few clients have wondered how building their product in Ruby on Rails might impact hiring efforts for their own in-house team. We know that outsourcing to our team is rarely a permanent solution for smaller organizations, and clients deserve the freedom to take their product in-house or to another agency.&lt;/p&gt;

&lt;p&gt;Experienced Rails developer availability is dependent on your local job market. In Nashville, for example, experienced C# developers are more common than Ruby developers due to the prevalence of healthcare companies in the area that rely on C# applications, but it's still possible to hire people with Ruby on Rails experience. Ruby on Rails is also easy for an experienced web developer to pick up within a few weeks. Most modern MVC frameworks (.NET, Spring, Symfony, etc.) share design patterns and concepts that are very similar to those of Ruby on Rails. &lt;/p&gt;

&lt;p&gt;Our first-hand experience tells us that newer developers can learn Ruby on Rails, too: it's the first framework we introduce to our developers when they join the team. Twin Sun was the first professional programming job for some of our developers, and those team members learned Rails fundamentals in a matter of weeks.&lt;/p&gt;

&lt;p&gt;Our compliance with standard Rails conventions—coupled with other practices of ours such as automated testing, continuous integration, and documentation—make the process of transferring a project to your in-house team very straightforward. If a developer knows Rails, they already know how our projects work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Rails apps give you a head start
&lt;/h2&gt;

&lt;p&gt;When we start a new web app project, we don't use the bare-bones app template provided by the Ruby on Rails maintainers. Instead, we have built our own base application to speed up development of the most common features we see in new apps.&lt;/p&gt;

&lt;p&gt;Things like user login, authorization, and email notifications are generally the same across most apps. Those capabilities are baked in to our base app. Things like login screens still need to be designed for your app, but the core functionality is there as soon as we start the project. Our clients benefit from us using these core features that have been tested and proven in dozens of other apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails works well for us
&lt;/h2&gt;

&lt;p&gt;Do we have to use Ruby on Rails to build a new web app? No, there are plenty of other tools that can get the job done, and we've used many of them.&lt;/p&gt;

&lt;p&gt;However, we have invested our time and effort into building our expertise in Rails. You can take advantage of our expertise and build your web app with confidence.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Ruby on Rails App with a Legacy Database</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 26 Sep 2022 15:51:10 +0000</pubDate>
      <link>https://dev.to/twinsun/building-a-ruby-on-rails-app-with-a-legacy-database-2ekk</link>
      <guid>https://dev.to/twinsun/building-a-ruby-on-rails-app-with-a-legacy-database-2ekk</guid>
      <description>&lt;p&gt;We recently started a new API project for an existing web application. &lt;br&gt;
The existing app has no API, and our client wants to create a fresh mobile app experience.&lt;br&gt;
Our new API will support the mobile app, and will exist alongside the existing web app.&lt;/p&gt;

&lt;p&gt;The existing app is large and complex, and introducing a RESTful API that meets the mobile app's requirements would prove difficult in the existing code base.&lt;br&gt;
We believed that a new project for the API would be the simplest path forward.&lt;br&gt;
However, we need to utilize data from the existing app without impacting the existing user experience.&lt;br&gt;
Therefore, we decided to access the existing database from our new API project.&lt;/p&gt;

&lt;p&gt;Our preferred tech stack uses Ruby on Rails, so we wanted to find a way to connect to the legacy database from a Rails app.&lt;br&gt;
Broadly, we knew that we needed to map the existing database schema into our Rails project, capturing the schema in &lt;code&gt;db/schema.rb&lt;/code&gt;.&lt;br&gt;
From there, we could generate model classes mapped to the schema, and then use the models to create our desired API endpoints.&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating Model Classes
&lt;/h2&gt;

&lt;p&gt;This is where the &lt;a href="https://github.com/frenesim/schema_to_scaffold"&gt;Schema to Scaffold gem&lt;/a&gt; came in.&lt;br&gt;
It produces &lt;code&gt;rails scaffold&lt;/code&gt; commands that can be used to generate a model and associated controller for a given schema.&lt;br&gt;
This gem rapidly speeds up the process of mapping legacy database schemas to Rails models.&lt;/p&gt;

&lt;p&gt;To start, we needed to connect our app to a copy of the legacy database.&lt;br&gt;
We took a backup of our staging environment's legacy database and loaded it in our local development environment.&lt;br&gt;
Then we connected the rails app to the local copy of the database and ran the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:schema:dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated a &lt;code&gt;db/schema.rb&lt;/code&gt; file that we could use to generate our models.&lt;br&gt;
The schema is used by the Schema to Scaffold gem to print out &lt;code&gt;rails generate scaffold&lt;/code&gt; commands for generating our models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scaffold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then copied the rails generator command list from the &lt;code&gt;scaffold&lt;/code&gt; command's console output to generate our models.&lt;br&gt;
Depending on your use case, generating all scaffolding for each model may be excessive.&lt;br&gt;
You can tweak the commands before running them to only generate the models if so desired by running &lt;code&gt;rails generate model&lt;/code&gt; instead of &lt;code&gt;rails generate scaffold&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Within a few moments, this leaves you with dependable ActiveRecord interfaces for accessing your legacy database.&lt;br&gt;
It's that easy!&lt;/p&gt;
&lt;h2&gt;
  
  
  Mapping Relationships
&lt;/h2&gt;

&lt;p&gt;This wasn't the end of our work, however.&lt;br&gt;
When you're interfacing with a legacy database through a Rails app, you will likely need to make some adjustments to the generated model classes.&lt;br&gt;
Foreign keys or ID columns may not match the Rails naming conventions, requiring you to map the columns to the correct model attributes.&lt;br&gt;
We found several columns in our project that were named using &lt;a href="https://en.wikipedia.org/wiki/Camel_case"&gt;camel casing&lt;/a&gt; instead of &lt;a href="https://en.wikipedia.org/wiki/Snake_case"&gt;snake casing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, this app had the concept of many types of records belonging to a &lt;code&gt;Tenant&lt;/code&gt;.&lt;br&gt;
The database column used for this foreign key was named &lt;code&gt;tenantId&lt;/code&gt; instead of &lt;code&gt;tenant_id&lt;/code&gt; (what ActiveRecord would expect).&lt;br&gt;
The Schema to Scaffold gem reasonably does not know what to do with this column, so we updated the &lt;code&gt;has_one&lt;/code&gt; relationships with &lt;code&gt;Tenant&lt;/code&gt; records to use the correct column name:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:filters&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Filter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s1"&gt;'tenantId'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hardcoded Relationships
&lt;/h2&gt;

&lt;p&gt;We also had a special &lt;code&gt;Tenant&lt;/code&gt; record: a certain &lt;code&gt;Tenant&lt;/code&gt; was a placeholder that translated in code to "all tenants may use this".&lt;br&gt;
In the old application, they'd look for this special record by a hard-coded ID of &lt;code&gt;9&lt;/code&gt;.&lt;br&gt;
This meant that our Rails application could not simply rely on &lt;code&gt;has_many&lt;/code&gt; or &lt;code&gt;belongs_to&lt;/code&gt; relationships to find all data that should be accessible from another &lt;code&gt;Tenant&lt;/code&gt;.&lt;br&gt;
In some places, it made sense to use a scope instead of a &lt;code&gt;has_many&lt;/code&gt; relationship:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Filter&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s1"&gt;'tenantId'&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:for_tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenantId = 9 OR tenantId = ?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# example usage: Filter.for_tenant(tenant)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hardcoded &lt;code&gt;Tenant&lt;/code&gt; with an ID of &lt;code&gt;9&lt;/code&gt; also presented concerns in our test suite: the ID field is an auto-incrementing integer database column, so it's possible we could have a conflict with that ID in our tests.&lt;br&gt;
To circumvent this potential issue, we seeded our test database with a placeholder &lt;code&gt;Tenant&lt;/code&gt; record with a hardcoded ID.&lt;/p&gt;

&lt;p&gt;Instead of hardcoding the ID to circumvent this issue, we could have performed a database migration to adjust how to designate records as belonging to all tenants.&lt;br&gt;
However, this would have required adjusting behavior in the original application as well, which could have lead to undesirable side effects.&lt;br&gt;
We are talking about a &lt;em&gt;legacy&lt;/em&gt; database, after all!&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Forward
&lt;/h2&gt;

&lt;p&gt;Now we have a complete integration between our legacy database and our Ruby on Rails app.&lt;br&gt;
The existing web application can continue working as-is with no modifications.&lt;br&gt;
Meanwhile, our new API endpoints can take advantage of the niceties of Rails while leveraging the shared database.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>database</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>Securing Sensitive API Calls with Nginx</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 29 Aug 2022 17:48:14 +0000</pubDate>
      <link>https://dev.to/twinsun/securing-sensitive-api-calls-with-nginx-49do</link>
      <guid>https://dev.to/twinsun/securing-sensitive-api-calls-with-nginx-49do</guid>
      <description>&lt;p&gt;A common web application architecture relies on a client-facing app that interacts with an API server. For example, you may build a React app that interacts with an API. We’ve worked on a number of projects where we interact with a third-party API that we do not control.&lt;/p&gt;

&lt;p&gt;In these instances, we sometimes wish to add an extra layer of authorization between end users and the API. This is necessary when the API permits the client to take irreversible action—such as deletion of data—with no way to restrict or grant privileges to specific end users.&lt;/p&gt;

&lt;p&gt;There are a few ways to augment the API’s authorization with our own. We typically take one of two approaches. If the API service is one piece of a number of backend services we need to control, we will build a middleware application that adds our own layer of user authentication and authorization. That solution can add a lot of time and complexity when we’re looking to deliver a simple web app. Alternatively, if the API service is the only backend service we use for the application, we can implement a simpler solution. Instead of building middleware to manage privileges, we can configure nginx as a reverse proxy between end users and the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference between proxies and reverse proxies
&lt;/h2&gt;

&lt;p&gt;A proxy is a service that acts as an intermediary between the client requesting a resource and the service that provides a resource. There are two types of proxies: regular (“forward”) proxies and reverse proxies. A forward proxy typically obscures which client is requesting a resource. In contrast, a reverse proxy will obscure which service is providing a resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typical API interactions
&lt;/h2&gt;

&lt;p&gt;Before we demonstrate how a reverse proxy helps us solve our security concerns, let’s consider a typical API interaction. Imagine we have a React app that needs to retrieve data from a weather API. The interaction diagram for requesting data from the weather API may look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZlixFcC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ira5mfd94ybp8m47yjwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZlixFcC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ira5mfd94ybp8m47yjwo.png" alt="Example illustration of a simple client-server interaction" width="118" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The React app client requests data from the weather API, which returns data that the client can present to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse proxied API interactions
&lt;/h2&gt;

&lt;p&gt;Continuing with our example, let’s imagine that we are using a paid weather service API. We are charged for every API call we make. For this scenario, let’s pretend we’re okay with our users making as many requests as they like to the API. However, we’d like to prevent third party websites from taking our API authentication token and using it for their own applications. We would not want to get charged for a competing weather site’s API usage.&lt;/p&gt;

&lt;p&gt;So let’s introduce a reverse proxy between our client and the weather API. Instead of storing our API authentication token in our React app, we can store the token securely on our reverse proxy server. Our React app will send API requests to the reverse proxy. The reverse proxy, in turn, will add the API authentication token to our request before forwarding the request to the weather API. The weather API responds to the request, returning our weather data to the reverse proxy. The reverse proxy then sends that data back to the client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWoQ7pdb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb219gv3mfinbqwvt0bd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWoQ7pdb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb219gv3mfinbqwvt0bd.png" alt="Example illustration of a client requesting data from an API through a reverse proxy" width="121" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By never directly calling the weather API from our React app, we have hidden our API authentication token from our end users. They can’t view the source code in their browser to uncover our API token, because it’s simply not there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to authenticate API calls through an nginx reverse proxy
&lt;/h2&gt;

&lt;p&gt;Setting up a reverse proxy may sound daunting at first. However, nginx makes configuring a reverse proxy rather easy.&lt;/p&gt;

&lt;p&gt;Let’s imagine that the weather API requires us to send an &lt;code&gt;X-Weather-API-Token&lt;/code&gt; header containing our API authentication token with every API request. We can tell our nginx server to forward requests from &lt;code&gt;/weather-api/&lt;/code&gt; to the actual weather service API, and to add the required API token header to the forwarded request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /weather-api/ {
    proxy_cache off;
    proxy_set_header X-Weather-API-Token "your-super-secret-api-token";
    proxy_pass https://weather.example.com/api/current-weather.json;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when our React app sends a request to the &lt;code&gt;/weather-api/&lt;/code&gt; URL hosted by our nginx reverse proxy, nginx will add the &lt;code&gt;X-Weather-API-Token&lt;/code&gt; to our request and send the request to the API endpoint located at &lt;code&gt;https://weather.example.com/api/current-weather.json&lt;/code&gt;. The API endpoint will return the data we want to the reverse proxy, and then the reverse proxy will send that data back to our React app.&lt;/p&gt;

&lt;p&gt;It’s that easy! Of course, this is a simple example. We did not cover how to ensure no third-party sites hit our reverse proxy. However, you have some options: limit requests to certain origins (hostnames or IP addresses) with a Content Security Policy, set up Basic Authentication, or create a custom authentication mechanism for nginx to use.&lt;/p&gt;

&lt;p&gt;This approach has helped us add an extra layer of protection for third-party APIs and has helped us limit client access to certain endpoints. Of course, reverse proxies have plenty of other uses beyond securing an endpoint. You can transform data, combine data from multiple resources, load balance requests between multiple servers, cache data, compress data, and even chunk (or “spoon feed”) large requests.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>proxy</category>
      <category>api</category>
    </item>
    <item>
      <title>Flutter: Our Platform of Choice</title>
      <dc:creator>Twin Sun</dc:creator>
      <pubDate>Mon, 22 Aug 2022 18:58:00 +0000</pubDate>
      <link>https://dev.to/twinsun/flutter-our-platform-of-choice-37e3</link>
      <guid>https://dev.to/twinsun/flutter-our-platform-of-choice-37e3</guid>
      <description>&lt;h2&gt;
  
  
  What is Flutter?
&lt;/h2&gt;

&lt;p&gt;Flutter is a cross platform mobile UI Framework created by Google. Flutter allows developers to create incredible looking apps that work on both Android and iOS devices using a single code base. As of December 2018, Flutter is officially on version 1.0.&lt;/p&gt;

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

&lt;p&gt;Generally speaking, Flutter is designed to seriously decrease the time and effort it takes to develop a product on both major mobile platforms (Android and iOS). It makes maintaining a product much easier as well, as the majority (if not all) of the code is shared across both platforms.&lt;/p&gt;

&lt;p&gt;For Twin Sun, Flutter allows us to exceed our clients’ goals and expectations. We have all come from a world that avoided hybrid and cross-platform approaches, as it was always more financially beneficial to charge for two separate applications for each platform.&lt;/p&gt;

&lt;p&gt;When founding Twin Sun, we all agreed on our goal, to take on software for people who deserve better. Too many companies, specifically start-ups, often times end up paying far too much for their product for a number of reasons. This inflated costs typically starts with the concept of “native applications always work better than hybrid”. This is a line I have said to hundreds of clients before, and I believed it, until now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Flutter demolishes this concept with astounding results. We began using Flutter for an internal project in May of 2018, and after seeing the power and potentially cost savings, we have began using Flutter for client projects as well. On products that require both Android and iOS, we have seen an average of a 48% reduction in our pricing compared to building each platform separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functionality
&lt;/h2&gt;

&lt;p&gt;Flutter provides the same level of functionality you would get with native applications. The applications compile into native code, which essentially makes them indistinguishable from a full native application. The community has latched on to Flutter support with companies like Square announcing partnerships. Flutter also easily supports native code as well, for any unique scenarios that require native interoperability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;Flutter allows us to move very quickly and deliver astonishing results in the process. By removing the hurdles encountered with a team of native iOS and Android developers, we can move quickly on ideas and collaborate better than ever with a single code base across our entire team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Flutter
&lt;/h2&gt;

&lt;p&gt;To say flutter has received support is an understatement. Flutter is being used in many production apps with millions of users, such as Alibaba, Google Ad Words, and many more.&lt;/p&gt;

&lt;p&gt;Due to the incredible support and community around Flutter, there are currently projects to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build Mac, Windows, and Linux desktop applications&lt;/li&gt;
&lt;li&gt;Build full web applications&lt;/li&gt;
&lt;li&gt;Even run on Raspberry Pis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that in the near future, sharing a single code base can extend to the majority of major platforms beyond mobile.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
