<?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: Guillermo A. Fisher</title>
    <description>The latest articles on DEV Community by Guillermo A. Fisher (@guillermoandrae).</description>
    <link>https://dev.to/guillermoandrae</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%2F336335%2Faa8e45aa-7029-41a4-a163-c8c0d2776690.png</url>
      <title>DEV Community: Guillermo A. Fisher</title>
      <link>https://dev.to/guillermoandrae</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/guillermoandrae"/>
    <language>en</language>
    <item>
      <title>Building an Unflashy Serverless COVID-19 Tracker with AWS</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Sat, 28 Mar 2020 20:21:12 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/building-an-unflashy-serverless-covid-19-tracker-with-aws-34d7</link>
      <guid>https://dev.to/guillermoandrae/building-an-unflashy-serverless-covid-19-tracker-with-aws-34d7</guid>
      <description>&lt;h3&gt;
  
  
  Really? Why?
&lt;/h3&gt;

&lt;p&gt;To keep your mind off the severity of the current situation. To stay busy, and to use your skills to keep your family and friends informed. To learn something new while you keep your busy behind at home.&lt;/p&gt;

&lt;p&gt;So… a week or so ago, out of nowhere, I decided that I wanted to be able to get the most recent COVID-19 numbers without having to use a browser. There were a few states in particular that were important to me, and I wanted to be able to see the number of confirmed cases in those states by typing a command in a terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VKzHPPjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxSrksceaNMr7L5hupORmKg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VKzHPPjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxSrksceaNMr7L5hupORmKg.png" alt=""&gt;&lt;/a&gt;Boom, bap, bip.&lt;/p&gt;

&lt;p&gt;That’s all there was to it when I started. I figured I could ping &lt;a href="https://www.cdc.gov/"&gt;the CDC’s site&lt;/a&gt; to get the info, but I noticed that the department of health websites seemed to have the most up-to-date information. I took a look at a few of those DOH sites, viewed the source (the way only &lt;em&gt;very&lt;/em&gt; &lt;em&gt;cool people&lt;/em&gt; do when they surf the Interwebs), and determined that I could easily scrape some pages to get the info I wanted using regular expressions, DOM traversal, and super fancy string parsing.&lt;/p&gt;

&lt;p&gt;What started out as a CLI became a CLI + web API because I couldn’t help myself. &lt;a href="https://guillermoandraefisher.com/covid.html"&gt;An HTML page&lt;/a&gt; came to be because my wife wanted to know what I was working on, and I didn’t think she’d be too impressed by a JSON string. I went serverless because this was a green field project and it’s 2020 and my free time is too precious to be spent OS patching and because why in the world would I pay for a server and because &lt;em&gt;come on already!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s Involved
&lt;/h3&gt;

&lt;h4&gt;
  
  
  PHP… again
&lt;/h4&gt;

&lt;p&gt;I wanted to do this quickly, so I used the language that I knew best: PHP. I threw together a &lt;a href="https://github.com/guillermoandrae/php-skeleton"&gt;PHP skeleton&lt;/a&gt; to start the millions of unfinished projects I tend to bring to life, and I used it for this project. I also used &lt;a href="https://github.com/php-cache/filesystem-adapter"&gt;Flysystem’s&lt;/a&gt; &lt;a href="https://www.php-fig.org/psr/psr-6/"&gt;PSR-6&lt;/a&gt; implementation for filesystem caching — I was actually unaware of the PSR-6 vs. &lt;a href="https://www.php-fig.org/psr/psr-16/"&gt;PSR-16&lt;/a&gt; discussions that popped up over the years; I chose PSR-6 because I thought the provided CacheItemPoolInterface would work well in this situation, allowing me to easily change cache storage options if I felt the need.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other Stuff
&lt;/h4&gt;

&lt;p&gt;An older version of &lt;a href="https://bref.sh/"&gt;Bref&lt;/a&gt; (I’m not ready to make the jump over to the version that leverages the &lt;a href="https://serverless.com/"&gt;Serverless framework&lt;/a&gt;) made it easy for me to use &lt;a href="https://aws.amazon.com/serverless/sam/"&gt;AWS SAM&lt;/a&gt;to deploy an &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; function. I used &lt;a href="https://aws.amazon.com/api-gateway/"&gt;Amazon API Gateway&lt;/a&gt; to serve up the endpoint, and used &lt;a href="https://aws.amazon.com/cloudwatch/"&gt;Amazon CloudWatch&lt;/a&gt; to schedule the population of the cache, which lives in &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scraping
&lt;/h3&gt;

&lt;p&gt;This was the most interesting challenge because it required me to keep up with the way each state rolled out their changes. New York, for instance, made several changes to the way they displayed their data over time. Every few updates, they’d wrap the count in a different set of tags, or surround it with different characters. In most cases, though, it came down to doing some simple pattern matching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://the.url'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// grab the HTML&lt;/span&gt;
&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/total to (.*) confirmed cases/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I invariably ended up having to do some cleanup on whatever existed in &lt;code&gt;$matches[1]&lt;/code&gt;, but that ended up being pretty simple. I did some similar matching to get the timestamps for the updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting it All Together
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Deploying with Travis CI and AWS SAM
&lt;/h4&gt;

&lt;p&gt;To get the code deployed up to a Lambda function, I followed the steps I walked though in &lt;a href="https://medium.com/@guillermoandrae/automate-deployment-of-php-applications-to-aws-lambda-with-bref-aws-sam-and-travis-ci-90231dfae6ff"&gt;an older post about Bref and Lambda&lt;/a&gt;. The one new thing to note is that I had to set the Python version in the .travis.yml file to 3.6.7 or 3.7.1 due to updates in the AWS tools.&lt;/p&gt;

&lt;h4&gt;
  
  
  API Gateway Setup
&lt;/h4&gt;

&lt;p&gt;Once the Lambda function was deployed, I created a REST API endpoint using the API Gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7zl5v7ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5okgHMDF2cycy_aoQm2zgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7zl5v7ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5okgHMDF2cycy_aoQm2zgw.png" alt=""&gt;&lt;/a&gt;Go with REST, unless you don’t want to…&lt;/p&gt;

&lt;p&gt;When I clicked on “ &lt;strong&gt;Build&lt;/strong&gt; ” in the REST API box, I was led to a screen where I was able to add resources and methods to the API. Using the “ &lt;strong&gt;Actions&lt;/strong&gt; ” dropdown, I created a resource named casesby choosing the “ &lt;strong&gt;Create resource&lt;/strong&gt; ” option. I enabled &lt;a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing"&gt;CORS&lt;/a&gt; for good measure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z73TuIz4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlQvtswT18cOiJ-ujOCBs8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z73TuIz4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlQvtswT18cOiJ-ujOCBs8g.png" alt=""&gt;&lt;/a&gt;Cases? Yeah… that works…&lt;/p&gt;

&lt;p&gt;I then chose “ &lt;strong&gt;Create method&lt;/strong&gt; ” from the same dropdown to associate the GET HTTP method with that resource. I was able to point to my Lambda function from the screen that appeared. I used &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html"&gt;Lambda Proxy Integration&lt;/a&gt; to proxy incoming requests straight through to the Lambda function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qhrgLqNV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AFff7yOIIbQm-pb3rYo6EiA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qhrgLqNV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AFff7yOIIbQm-pb3rYo6EiA.png" alt=""&gt;&lt;/a&gt;HTTP methods make the world go ‘round&lt;/p&gt;

&lt;p&gt;Once it was all set up, I saw a no-fuss GET method configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pwQkVqFG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AdHuybuD--R4KWLdL_YvkPA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pwQkVqFG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AdHuybuD--R4KWLdL_YvkPA.png" alt=""&gt;&lt;/a&gt;Voila!&lt;/p&gt;

&lt;p&gt;I deployed the API by choosing “ &lt;strong&gt;Deploy API&lt;/strong&gt; ” from the “ &lt;strong&gt;Actions&lt;/strong&gt; ” dropdown, chose a stage name, and added additional stage meta data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3TTlQTgi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ASOuFXvwpSZZrVzCsYkKJBw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3TTlQTgi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ASOuFXvwpSZZrVzCsYkKJBw.png" alt=""&gt;&lt;/a&gt;Be practical here&lt;/p&gt;

&lt;p&gt;Once I was done deploying the API, I saw a screen that featured a link to it, and was also presented with options for things like caching and client certificates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qs5c3M9m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABXerE2Q_FMtJ9D_L78L5qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qs5c3M9m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ABXerE2Q_FMtJ9D_L78L5qw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re following along because you’re building an API of your own, then you’ll probably want to associate it with a customized domain name — if so, follow the steps in the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html"&gt;AWS docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use This as a Starting Point…
&lt;/h3&gt;

&lt;p&gt;I really didn’t do much for this project, and I’m sure &lt;em&gt;you&lt;/em&gt; can do much, much more with it. If you’re interested, you can save the data to a &lt;a href="https://aws.amazon.com/dynamodb/"&gt;DynamoDB&lt;/a&gt; table and use that data to build visualizations. You can ditch the scraping altogether and just use the data provided by &lt;a href="https://covidtracking.com/"&gt;The COVID Tracking Project&lt;/a&gt; (thanks for making me aware of it, &lt;a href="https://brandenbuilds.com/"&gt;Branden&lt;/a&gt;). You can build a more engaging application using React or Vue. Whatever you do, though, &lt;strong&gt;do it from the safety of your home&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you end up &lt;a href="https://github.com/guillermoandrae/coronavirus-reporter"&gt;using the code&lt;/a&gt;, or find this post useful, please let me know.&lt;/p&gt;

</description>
      <category>serverlessarchitecture</category>
      <category>php</category>
      <category>amazonapigateway</category>
      <category>bref</category>
    </item>
    <item>
      <title>Your First Steps with AWS</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Tue, 17 Mar 2020 16:30:43 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/your-first-steps-with-aws-4oa1</link>
      <guid>https://dev.to/guillermoandrae/your-first-steps-with-aws-4oa1</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c8rJQ75y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2An-qBxr_QNfdfEDGE" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c8rJQ75y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2An-qBxr_QNfdfEDGE" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@christianchen?utm_source=medium&amp;amp;utm_medium=referral"&gt;Christian Chen&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A recent tweet from &lt;a href="https://www.helenanderson.co.nz/"&gt;Helen Anderson&lt;/a&gt; prompted me to think of a few things that I’ve done in the past when creating new AWS accounts for myself and others. So I’ve put together a list of five first steps — this is by no means an exhaustive list, but it should help those of you who now find yourselves with enough time and elbow room (I don’t know about you, but I prefer to experiment/fail in solitude) to finally play around with AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Your Services
&lt;/h3&gt;

&lt;p&gt;Logging into the management console for the first time can be intimidating. At the time of this writing, there are a little less than 220 services available in AWS. You don’t need to worry about most of those services — you should only focus on the ones that are most relevant to you. If you’re new to AWS, you should review the &lt;a href="https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc"&gt;free tier documentation&lt;/a&gt; to help you make a decision about the services you’ll be using. My suggestions for beginners: &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt;, &lt;a href="https://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt;, &lt;a href="https://aws.amazon.com/iam/"&gt;IAM&lt;/a&gt;, &lt;a href="https://aws.amazon.com/cloudwatch/"&gt;CloudWatch&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/"&gt;Trusted Advisor&lt;/a&gt; (I’ll talk about the last 3 later on in this post).&lt;/p&gt;

&lt;p&gt;You can modify your console experience a bit by pinning services to the toolbar at the top of the page. Here’s an excerpt from the &lt;a href="https://aws.amazon.com/console/faq-console/"&gt;console FAQs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Select the pin icon beside the Resource Groups menu and drag and drop the service links you want to save as shortcuts. You have the option to display the service icon alone, the service name alone, or both together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kWKo9bZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Arllnkfh1XFiDQ73ZW90w-w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kWKo9bZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Arllnkfh1XFiDQ73ZW90w-w.png" alt=""&gt;&lt;/a&gt;Focus!&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an IAM User
&lt;/h3&gt;

&lt;p&gt;When you’re starting out with AWS, it can be tempting to use your root account to manage all of your resources — &lt;strong&gt; don’t&lt;/strong&gt;. You should use the root account sparingly, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#lock-away-credentials"&gt;keep the associated credentials safe&lt;/a&gt;, and create an IAM user. Here’s an excerpt from the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"&gt;IAM Best Practices&lt;/a&gt; documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create an IAM user for yourself as well, give that user administrative permissions, and use that IAM user for all your work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I recommend you stick to the advice detailed in the IAM Best Practices docs. If some of the concepts in that doc seem unfamiliar or daunting, then I suggest you focus first on these: grant yourself and others &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege"&gt;only the needed privileges&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#enable-mfa-for-privileged-users"&gt;enable multi-factor authentication (MFA)&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#configure-strong-password-policy"&gt;configure a strong password policy&lt;/a&gt; for all users in your account. &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html"&gt;Follow this guide&lt;/a&gt; to setup an admin user, then set up other users, groups, and roles whenever you can.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZlwTpNLg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AeQCDyXz9ofmPE6bMgKlBpQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZlwTpNLg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AeQCDyXz9ofmPE6bMgKlBpQ.png" alt=""&gt;&lt;/a&gt;Identify yourself!&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the CLI
&lt;/h3&gt;

&lt;p&gt;You may not think you need to use the CLI initially, but I highly recommend installing it anyway. Once you get more familiar with AWS, you’ll find that it’s just more efficient to do certain things using the command line. For example: I sometimes have to create upwards of fifteen users for &lt;a href="https://757colorcoded.org"&gt;757ColorCoded&lt;/a&gt; workshops. I could do that manually in the console, but it takes a fraction of the time for me to create those users with a simple BaSh for-loop. The CLI installation and configuration steps are &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html"&gt;well-documented&lt;/a&gt;. You might as well get it out of the way before you actually &lt;em&gt;need&lt;/em&gt; to install it. &lt;a href="https://www.youtube.com/watch?v=dL8UXQ6BIrc"&gt;That way you have it&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Billing Alarm
&lt;/h3&gt;

&lt;p&gt;Even if you’re using the free tier — actually, &lt;em&gt;especially&lt;/em&gt; if you’re using the free tier — you should set up a billing alarm in CloudWatch to make sure you’re not unknowingly spending money. It’s pretty easy to accidentally exceed some of the free tier usage limits, or forget about resources you’ve created. You might, for example, set up some beefy EC2 instances in Oregon for a high availability experiment and, hypothetically speaking of course, forget to shut down those instances for a few weeks. Then, in theory, you might get a shocking bill, because you usually use other regions for HA and don’t see any EC2 instances when you log into the console and go to &lt;em&gt;those&lt;/em&gt; regions (I mean why in the world would you randomly spin up 2 ridiculously over-provisioned servers in the west coast for no good reason????). That could possibly happen to someone.&lt;/p&gt;

&lt;p&gt;Anyway, the aforementioned tweet that inspired this post provides a link to a &lt;a href="https://www.josephwhyle.com/creating-a-simple-aws-billing-alarm/"&gt;pretty good guide&lt;/a&gt; put together by &lt;a href="https://www.josephwhyle.com/author/jw-blog/"&gt;Joseph Whyle&lt;/a&gt;, so you should check that out. I’ve included one of my alarms below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x-fuDE0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Aq9m9sbsAlkmPF7JMh4iAUw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x-fuDE0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Aq9m9sbsAlkmPF7JMh4iAUw.png" alt=""&gt;&lt;/a&gt;Watch your money!&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Trusted Advisor
&lt;/h3&gt;

&lt;p&gt;Trusted Advisor helps keep your account in line with AWS best practices. It makes recommendations about — among other things — security, performance, and cost optimization. The &lt;a href="https://console.aws.amazon.com/support/plans/home#/"&gt;Basic Trusted Advisor support plan&lt;/a&gt; is included in all AWS accounts (read: IT’S FREE). I’ve found it to be especially useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZufCOEW4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8Vm3geHEVD3oOJgnu1wDOQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZufCOEW4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8Vm3geHEVD3oOJgnu1wDOQ.png" alt=""&gt;&lt;/a&gt;Listen!&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Going!
&lt;/h3&gt;

&lt;p&gt;That’s all I’ve got for ya. I’m going to be playing around with &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/connect-to-a-data-source.html"&gt;Athena Federated Query&lt;/a&gt; a bit over the next few weeks. If you’ll be doing the same, or if you have any questions about anything I’ve written, you can comment here or ping me on &lt;a href="https://twitter.com/guillermoandrae"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tips</category>
      <category>firststeps</category>
      <category>awsfreetier</category>
      <category>aws</category>
    </item>
    <item>
      <title>An Upgrade: Part 3 — Querying a Data Lake in AWS with Amazon Athena</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Thu, 13 Feb 2020 13:38:19 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/an-upgrade-part-3-querying-a-data-lake-in-aws-with-amazon-athena-2aa2</link>
      <guid>https://dev.to/guillermoandrae/an-upgrade-part-3-querying-a-data-lake-in-aws-with-amazon-athena-2aa2</guid>
      <description>&lt;p&gt;A sizable chunk of time has passed since &lt;a href="https://medium.com/@guillermoandrae/an-upgrade-part-2-diving-deeper-into-dynamodb-924c621999a"&gt;Part 2&lt;/a&gt; of this series. In the months since my last post, I’ve started a new open source project (more to come later), &lt;a href="https://www.instagram.com/p/B7rdUnthVlB/"&gt;travelled&lt;/a&gt;, and stood up a small, manageable data lake. I’d like to say that my delay in bringing you the engrossing (or boring? YMMV) content you’ve come to expect from me is the result of me back-loading 2019 with a whirlwind of activity; the reality, I’m afraid, has more to do with indolence than anything else. Sorry.&lt;/p&gt;

&lt;p&gt;In this post, I’ll focus on my recent use of &lt;a href="https://aws.amazon.com/athena/"&gt;Amazon Athena&lt;/a&gt; — and, as a matter of necessity, &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt; and &lt;a href="https://aws.amazon.com/glue/"&gt;AWS Glue&lt;/a&gt; — to explore my &lt;a href="https://www.apple.com/ios/health/"&gt;Apple Health&lt;/a&gt; data and decide whether or not to incorporate it into my &lt;a href="https://guillermoandraefisher.com"&gt;unreasonably captivating personal website&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Bit About re:Invent 2019
&lt;/h3&gt;

&lt;p&gt;The aforementioned data lake is the same data lake I reviewed during the demo portion of my DevChat at &lt;a href="https://www.youtube.com/watch?v=vkI0C2lJUTY&amp;amp;list=PL2yQDdvlhXf8QaLx6G1UCP8XnbyrEvkSd"&gt;AWS re:Invent 2019&lt;/a&gt;, the &lt;a href="https://www.slideshare.net/GuillermoAFisher/the-beginners-guide-to-data-lakes-in-aws"&gt;slides&lt;/a&gt; for which are available on &lt;a href="https://www.slideshare.net/GuillermoAFisher"&gt;Slideshare&lt;/a&gt;. I won’t get into a full-blown re:Invent recap, but I will say that I had a great time at the conference — I’ve been using one word in particular to describe the experience: &lt;em&gt;overwhelming&lt;/em&gt;. I’m thankful to AWS for giving me the opportunity to attend and meet some &lt;a href="https://aws.amazon.com/blogs/aws/meet-the-newest-aws-heroes-including-the-first-data-heroes/"&gt;truly impressive people&lt;/a&gt;; I’m also thankful to &lt;a href="https://joinhandshake.com"&gt;Handshake&lt;/a&gt; for giving me the space to stretch myself, talk about the company’s mission, and walk purposefully around Vegas for a few days — which, incidentally, is a great way to &lt;a href="https://www.apple.com/watch/close-your-rings/"&gt;close activity rings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xPAHAIb2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ATrMwKH17v9zlfha6bD6xGg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xPAHAIb2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ATrMwKH17v9zlfha6bD6xGg.jpeg" alt=""&gt;&lt;/a&gt;I’m the stocky character pointing at the monitor. Photo by &lt;a href="https://twitter.com/rossbarich"&gt;Ross Barich&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking More Than Steps
&lt;/h3&gt;

&lt;p&gt;If you’ve got an iPhone, you’ve got Apple Health data; if you’ve got an Apple Watch, too, then you’ve got even more data. The &lt;a href="https://www.apple.com/ios/health/"&gt;Health app&lt;/a&gt; consolidates data about your physical activity, heart rate, etc. from your iPhone, Apple Watch, and other third-party apps into a singular data repository.&lt;/p&gt;

&lt;p&gt;You can use the Health app to &lt;a href="http://osxdaily.com/2019/05/20/export-health-data-from-iphone/"&gt;export that data&lt;/a&gt; so that you can play around with it yourself. What the export provides is an unwieldy set of XML files whose size is directly related to the duration of your relationship with your iPhone — if, for example, you’ve been an Apple fangirl since iOS8 (when the Health app was introduced), then you might have 5 years worth of data on your hands.&lt;/p&gt;

&lt;p&gt;Health app data isn’t easy to parse without employing some ETL wizardry. Before authoring while statements, I looked around to see if anyone had already decided to hazard an attempt at processing the files, and I quickly came across &lt;a href="http://www.markwk.com/"&gt;Mark Koester&lt;/a&gt;’s post entitled &lt;a href="http://www.markwk.com/data-analysis-for-apple-health.html"&gt;How to Export, Parse and Explore Your Apple Health Data With Python&lt;/a&gt;. I read through all of the rigor involved in making sense of the data, and realized that &lt;a href="https://aws.amazon.com/big-data/datalakes-and-analytics/"&gt;a subset of AWS services&lt;/a&gt; could be employed to reduce my level of effort if I introduced a data lake into the equation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Lakes Seem Complicated
&lt;/h3&gt;

&lt;p&gt;While the idea of deploying a data lake may seem daunting to the uninitiated, it can actually be a fairly straightforward affair, especially in use cases like this one.&lt;/p&gt;

&lt;p&gt;Before we dive into my setup, let’s first take a look at a definition. As AWS puts it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A data lake is a centralized repository that allows you to store all your structured and unstructured data at any scale. You can store your data as-is, without having to first structure the data, and run different types of analytics — from dashboards and visualizations to big data processing, real-time analytics, and machine learning to guide better decisions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key takeaway from that definition is that a data lake is just a &lt;em&gt;centralized repository&lt;/em&gt; — you don’t, for example, need &lt;a href="https://aws.amazon.com/redshift/"&gt;Redshift&lt;/a&gt; or &lt;a href="https://aws.amazon.com/quicksight/"&gt;QuickSight&lt;/a&gt; to build a data lake, but those tools can help you tease insights out of the data that is stored in your repository.&lt;/p&gt;

&lt;h4&gt;
  
  
  Data Storage &amp;amp; Ingestion
&lt;/h4&gt;

&lt;p&gt;I decided to keep my Apple Health data in S3, a service that is at the heart of data lakes — and a host of other services, really — that live in AWS. It’s a cost effective, scalable, durable solution that allows you to store all kinds of data and define &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-lifecycle.html"&gt;lifecycle policies&lt;/a&gt; to optimize storage costs. The data in my buckets is encrypted with the &lt;a href="https://aws.amazon.com/kms/"&gt;AWS Key Management Service&lt;/a&gt; (AWS-KMS). Pro tip: &lt;strong&gt;always&lt;/strong&gt; encrypt your data in a data lake both while its sitting around doing nothing (at rest) and while its moving through your system (in transit).&lt;/p&gt;

&lt;p&gt;A concept common among data lakes is the idea of &lt;em&gt;zones&lt;/em&gt;. You’ll see different names for zones used in data lakes across industries and use cases, but the main idea is that each zone represents data as it exists in varying states of refinement. Zones are generally represented by S3 buckets. For example, raw data — data in its original format — is kept in a raw zone.&lt;/p&gt;

&lt;p&gt;My solution has 3 zones: raw, refined, and curated. I stored my raw health data in a bucket at this path: S3://guillermoandrae-health-data/raw/2019–10–10(it’s worth mentioning here that the folder structure you use when storing your data can affect the performance of your Athena queries; if you’re interested in solutions more complex than the current one, you should do some reading on &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/partitions.html"&gt;partitioning&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;On the ingestion front: I don’t know of a way to automatically download my health data (I’m open to suggestions), so I manually downloaded it and pushed it to S3 through the AWS administrative console. You’d usually want to automate the upload process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yXNW3YNe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0AuNsL-FGMtdgMDrekxMIw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yXNW3YNe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0AuNsL-FGMtdgMDrekxMIw.png" alt=""&gt;&lt;/a&gt;Raw, refined, and curated zones.&lt;/p&gt;

&lt;h4&gt;
  
  
  Move &amp;amp; Transform
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/markwk/qs_ledger/tree/master/apple_health"&gt;Apple Health Extractor&lt;/a&gt; outputs a number of CSV files, which I stored in the refined zone. On the first go-round, I pushed the CSVs to that zone manually. In order to automate the execution of the script, I uploaded a modified version of the code to a &lt;a href="https://aws.amazon.com/lambda/"&gt;Lambda&lt;/a&gt; function that watches the raw bucket and dumps the CSVs out into the refined bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7w-S-s_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AayEA5h2b7bIICvcZVW1rLw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7w-S-s_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AayEA5h2b7bIICvcZVW1rLw.png" alt=""&gt;&lt;/a&gt;My Lambda function is a bucket stalker.&lt;/p&gt;

&lt;h4&gt;
  
  
  Crawling &amp;amp; Cataloging
&lt;/h4&gt;

&lt;p&gt;In my headier programming days, I spent a ton of time writing ETL (Extract, Transform, Load) code. I also had experiences with tools that claimed to magically transform data sets from one format to another, only to have to write code to clean up the shoddy job done by those tools. When I heard about Glue, I was definitely pessimistic; seeing it in action, though, was surprising.&lt;/p&gt;

&lt;p&gt;AWS Glue is a serverless, pay-as-you-go ETL service. You can set Glue up to crawl and catalog the data in your S3 buckets. Tell it the data location and data format, and Glue will &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/populate-data-catalog.html"&gt;populate your data catalo&lt;/a&gt;g, which is stored in a &lt;a href="https://prestodb.io/"&gt;Presto&lt;/a&gt; database that you don’t have to manage. Incredibly efficient, incredibly useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D4fdiyoY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ACByUpvVEMxm5bVZeXekESw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D4fdiyoY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ACByUpvVEMxm5bVZeXekESw.png" alt=""&gt;&lt;/a&gt;Creeeeeeeepy crawlers!&lt;/p&gt;

&lt;p&gt;You’ll need to follow the prompts, define an &lt;a href="https://aws.amazon.com/iam/"&gt;IAM&lt;/a&gt; role that can be used by Glue to access data in your buckets, and name the database where your data catalog will exist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2pCAnhkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AnIsgIofXYsBTseg8rkkbRg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2pCAnhkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AnIsgIofXYsBTseg8rkkbRg.png" alt=""&gt;&lt;/a&gt;No code to write, no servers to maintain, no databases to manage.&lt;/p&gt;

&lt;p&gt;Simple CSVs are easy for Glue to parse — there are a few &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/add-classifier.html#classifier-built-in"&gt;built-in classifiers&lt;/a&gt; for various data formats (including CSV) that allow you to get started ETL-ing stuff right away. I was able to crawl my data without issue.&lt;/p&gt;

&lt;h4&gt;
  
  
  Data Exploration
&lt;/h4&gt;

&lt;p&gt;With my data cataloged, I was ready to begin digging into it with Athena. Amazon Athena is a serverless interactive query service that can be used to analyze data stored in S3. Once a Glue data catalog is populated, you can use Athena to write SQL queries and perform ad-hoc data analysis. Recent updates to Athena make it possible to execute SQL queries across databases, object storage, and custom data sources using the &lt;a href="https://aws.amazon.com/blogs/big-data/query-any-data-source-with-amazon-athenas-new-federated-query/"&gt;federated query feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FI4uYeLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwWeQK56grJcqCwT-HjmnJQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FI4uYeLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwWeQK56grJcqCwT-HjmnJQ.png" alt=""&gt;&lt;/a&gt;Run SQL from an intuitive UI.&lt;/p&gt;

&lt;p&gt;I ran some queries to figure out which of the columns would be most useful. In the GIF above, I paid close attention to the type field and was able to figure out what kinds of metrics could be found in the health data. Further investigation led me to the unit, value, and date-related fields creationdate, startdate, and enddate. I was ultimately able to figure out how to get some aggregates together that might be useful (check out Mark Koester’s post for more on that).&lt;/p&gt;

&lt;h3&gt;
  
  
  So… Is This Data Helpful?
&lt;/h3&gt;

&lt;p&gt;Nah. It’s not. It’s kind of interesting, but it doesn’t tell as compelling a story as I thought it would. I couldn’t find any strong correlations between my physical activity and my social media activity. I’d intended to convert the refined data into &lt;a href="https://parquet.apache.org/"&gt;Parquet&lt;/a&gt; — a c&lt;a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS"&gt;olumnar&lt;/a&gt; storage format — and store the files in my curated zone, then import those files into Redshift for data warehousing purposes. I got as far as the Parquet conversion, but decided it wasn’t worth it to incur the costs associated with spinning up Redshift servers.&lt;/p&gt;

&lt;p&gt;And that’s fine, I think. I was able to test out a hypothesis, and it cost me just about nothing. It’s OK that the process led me to the conclusion that I am, in fact, quantifiably boring.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Final Word on Data Lakes
&lt;/h3&gt;

&lt;p&gt;I don’t want to be irresponsible, so I won’t end this post without pointing you to at least one good &lt;a href="https://www.youtube.com/watch?v=v5lkNHib7bw"&gt;data lake resource&lt;/a&gt; and mentioning &lt;a href="https://aws.amazon.com/blogs/big-data/building-securing-and-managing-data-lakes-with-aws-lake-formation/"&gt;AWS Lake Formation&lt;/a&gt;, which will handle a lot of the dirty work of setting up a data lake for you. I should also point out that my data lake lacked the level of automation and data governance that are necessary to maintain the integrity and usefulness of a data lake, so don’t mimic my setup for anything even mildly important.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;It’s time to start writing some code. In the next post, I’ll talk about the &lt;a href="https://docs.aws.amazon.com/aws-sdk-php/v3/api/"&gt;AWS SDK for PHP&lt;/a&gt; and my mysterious open source project.&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 4 in the series, friends!&lt;/p&gt;

</description>
      <category>athena</category>
      <category>glue</category>
      <category>aws</category>
      <category>amazons3</category>
    </item>
    <item>
      <title>An Upgrade: Part 2 — Diving Deeper into DynamoDB</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Fri, 27 Sep 2019 02:01:32 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/an-upgrade-part-2-diving-deeper-into-dynamodb-2dng</link>
      <guid>https://dev.to/guillermoandrae/an-upgrade-part-2-diving-deeper-into-dynamodb-2dng</guid>
      <description>&lt;p&gt;In &lt;a href="https://medium.com/@guillermoandrae/an-upgrade-part-1-devising-an-approach-9b05090a50a8"&gt;Part 1&lt;/a&gt; of this series, I shared my plan for rebuilding &lt;a href="https://guillermoandraefisher.com"&gt;my personal website&lt;/a&gt;. Before I start thinking about changes to user interfaces or HTTP responses, I need to clean up the mess I created with my poorly designed data model. In this post, I’ll focus on my experience with &lt;a href="https://aws.amazon.com/dynamodb/"&gt;Amazon DynamoDB&lt;/a&gt; and the role that service will continue to play in my site’s architecture. Let’s go over some core DynamoDB concepts so we can shine a bright light on my missteps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some Basics
&lt;/h3&gt;

&lt;p&gt;DynamoDB stores data in &lt;em&gt;tables&lt;/em&gt;. If you’re familiar with other popular database systems, chances are you’ve come across tables working with those systems, too — and the idea here is pretty similar. Tables are made up of zero or more &lt;em&gt;items&lt;/em&gt;. An item can be compared to a row in a relational database, and is made up of &lt;em&gt;attributes&lt;/em&gt; &lt;strong&gt;.&lt;/strong&gt; An attribute can be likened to a table column, and can be one of the following types: String, Binary, Number, Boolean, Null, List, Map, StringSet, BinarySet, and NumberSet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VqMrKtxN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A1it5xH8GpsOoFSW8DmuwPw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VqMrKtxN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A1it5xH8GpsOoFSW8DmuwPw.png" alt=""&gt;&lt;/a&gt;A table made up of items that happen to be fantastic songs.&lt;/p&gt;

&lt;p&gt;The example above is an excerpt from a Songs table. Artist, SongTitle, and AlbumTitle are attributes. The second item in that table is represented visually as a row whose attribute values are the following strings:Eric Lau, Cloudburst, and Quadrivium.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Being Ridiculous
&lt;/h3&gt;

&lt;p&gt;That all seems pretty familiar, right? And simple, too? If you’re like me, it sure does. And if you’re like me, you started &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html"&gt;reading the documentation&lt;/a&gt; to figure out how to get started. You read through all that jazz about tables and attributes and groggily declared to yourself, somewhere around 10 PM at night, “I &lt;em&gt;know&lt;/em&gt; this stuff”. And then you saw, later on in the documentation, mention of “primary keys” and “indexes”, and smirked — defiantly — at the thought of having to read more explanations of concepts that you’d already “mastered”. So you skipped it all and went straight to table creation… and just figured out the rest as you went along.&lt;/p&gt;

&lt;p&gt;Do &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; do any of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary Keys
&lt;/h3&gt;

&lt;p&gt;Without an understanding of how primary keys work in DynamoDB, you won’t be able to design your tables in a way that will allow you to efficiently retrieve data from them. To start, there are two kinds of primary keys in DynamoDB: &lt;em&gt;simple&lt;/em&gt; and &lt;em&gt;composite&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A simple primary key is a &lt;em&gt;partition key&lt;/em&gt; made up of one attribute. You’ll sometimes see the partition key referred to as the &lt;em&gt;hash key&lt;/em&gt; or &lt;em&gt;hash attribute&lt;/em&gt; because it’s used in an internal hash function that evenly distributes items across partitions. In tables that only have partition keys, each item must have its own unique partition key value.&lt;/p&gt;

&lt;p&gt;Composite keys are made up of two attributes — the &lt;em&gt;partition key&lt;/em&gt; and the &lt;em&gt;sort key&lt;/em&gt;. You’ll sometimes see the sort key referred to as the &lt;em&gt;range key&lt;/em&gt; or &lt;em&gt;range attribute&lt;/em&gt; &lt;strong&gt;,&lt;/strong&gt; as items with the same partition key are stored physically close together and sorted by the sort key value. In tables that have both partition keys and sort keys, two items can share the same partition key value so long as those two items also have different sort key values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jyHc825p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzyuZO1WHrf5M9dQn7HQOTg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jyHc825p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzyuZO1WHrf5M9dQn7HQOTg.png" alt=""&gt;&lt;/a&gt;There’s enough Little Brother for us all.&lt;/p&gt;

&lt;p&gt;Consider the Songs table again. It has a composite primary key, composed of the Artist attribute (partition key) and SongTitle attribute (sort key). Note that there are two songs with the same partition key values (Little Brother), but they also have different sort key values (Home and Shorty on the Lookout).&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving Items
&lt;/h3&gt;

&lt;p&gt;After you’ve defined your table’s primary key and have added items, you’ll eventually want to retrieve that data. That can be done in a number of ways; the primary operations you’ll use to retrieve a collection of data are the Scan and Query operations.&lt;/p&gt;

&lt;p&gt;The Scan operation can be used to retrieve all of the data in a table. You can apply a &lt;em&gt;filter expression&lt;/em&gt; to refine your results and define the attribute set that you’d like to see returned. A Scan can be executed without a primary key being provided in a request.&lt;/p&gt;

&lt;p&gt;The Query operation can be used to find items based on primary key values. In order to execute a Query operation, you must, at the very least, provide the name of a primary key and a corresponding value. You can also provide a sort key and use a comparator operator to further refine your results. More refinement can be done with &lt;em&gt;key condition expressions&lt;/em&gt; as well as filter expressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Being Ridiculous: The Aftermath
&lt;/h3&gt;

&lt;p&gt;I originally designed my tables without paying much attention to any of the stuff I’ve written here since the “Primary Keys” heading. I assumed DynamoDB worked the way other popular NoSQL solutions worked, and that silly assumption informed my approach.&lt;/p&gt;

&lt;p&gt;I decided to store all of my social media posts in a posts table that felt more like a MongoDB collection than a DynamoDB table. I used an attribute called id as the table’s partition key. No sort key. And in the absence of a sort key, I had to use the Scan operation to retrieve results. But the results were never sorted as expected. So I did some Googling and found this issue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-amplify/aws-sdk-ios/issues/346#issuecomment-205431758"&gt;How to order a scan results by createdAt · Issue #346 · aws-amplify/aws-sdk-ios&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a quote from the thread:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DynamoDB doesn’t support sorting in a scan operation, which makes senses [sic], as ordering in a full table scan is usually unnecessary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ugh. So I was forced to actually &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html"&gt;read more of the docs&lt;/a&gt;, and I found this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Query&lt;/em&gt; results are always sorted by the sort key value. If the data type of the sort key is &lt;em&gt;Number&lt;/em&gt;, the results are returned in numeric order. Otherwise, the results are returned in order of UTF-8 bytes. By default, the sort order is ascending. To reverse the order, set the &lt;em&gt;ScanIndexForward&lt;/em&gt; parameter to &lt;em&gt;false&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ugh * ugh. Sort keys open up retrieval options, including those made available through &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions"&gt;&lt;em&gt;key condition expressions&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Righting Wrongs
&lt;/h3&gt;

&lt;p&gt;A few months ago, I created a new table to hold posts, and that new table has a composite primary key. The partition key is the table’s source attribute (with values like Twitter, Instagram, etc.), and the sort key is the createdAt attribute (the post’s timestamp). Now that I can use the Query operation, I can sort results by timestamp and use key condition expressions to write more complex queries. I also plan to add at least one &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html"&gt;secondary index&lt;/a&gt; so I can query against other item attributes like body.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.guillermoandraefisher.com/posts"&gt;My API&lt;/a&gt; is still pointing to the old table. A bit of code is required to convert the different timestamp formats into one format I can use in the new table, so I have to sync the two tables using a script until I can get that conversion code into the /posts endpoint’s POST method and point to the new table (more on that later on in the series). Still, though, I’m in much better shape overall.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guillermo uses GUIs
&lt;/h3&gt;

&lt;p&gt;The last DynamoDB-related change I’ll be making involves the addition of a new tool into my workflow: &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.html"&gt;NoSQL Workbench for DynamoDB&lt;/a&gt;. The UI is pretty intuitive, and I like not having to log into the AWS admin console to interact with my tables. There’s an operation builder feature that is especially helpful for creating filter expressions and, of course, executing operations. The builder can generate Python, Node.js, and Java code for you as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dmgToNqd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ANDMGXDkHg18SzmZMCmRW8Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dmgToNqd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ANDMGXDkHg18SzmZMCmRW8Q.png" alt=""&gt;&lt;/a&gt;I like a good GUI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Now that I’ve got the data set up correctly, I need to create the data pipeline that will allow me to ultimately use &lt;a href="https://aws.amazon.com/quicksight/"&gt;Amazon QuickSight&lt;/a&gt; to make sense of my social media activity and health data. I’ll be setting all of that up with &lt;a href="https://aws.amazon.com/glue/"&gt;AWS Glue&lt;/a&gt; and &lt;a href="https://aws.amazon.com/athena/"&gt;Amazon Athena&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 3 in the series, friends!&lt;/p&gt;

</description>
      <category>nosql</category>
      <category>aws</category>
      <category>bigdata</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>An Upgrade: Part 1 — Devising an Approach</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Fri, 27 Sep 2019 01:39:17 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/an-upgrade-part-1-devising-an-approach-24f9</link>
      <guid>https://dev.to/guillermoandrae/an-upgrade-part-1-devising-an-approach-24f9</guid>
      <description>&lt;p&gt;For a little over a year and a half, &lt;a href="https://guillermoandraefisher.com"&gt;guillermoandraefisher.com&lt;/a&gt; has existed as a serverless application that is powered by a familiar blend of services: &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt;, &lt;a href="https://aws.amazon.com/api-gateway/"&gt;Amazon API Gateway&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/dynamodb/"&gt;Amazon DynamoDB&lt;/a&gt;. Things have changed significantly since I first deployed the index.html file — for me, and for some of the services I’ve been using. In this series of posts, I’ll discuss my mistakes, highlight relevant services, and walk through the overhaul of my small, drab corner of the Web.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Learning
&lt;/h3&gt;

&lt;p&gt;As a people manager, writing code isn’t something I do on a regular basis. Occasionally, though, some extraordinary circumstance forces me to enter scrupulously into a production code base and completely wreck a Scrum team’s velocity.&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__video-wrapper"&gt;
        &lt;div class="ltag__twitter-tweet__media--video-preview"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ndy_MFTq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/tweet_video_thumb/ECxFmVPU4AA04kj.jpg" alt="unknown tweet media content"&gt;
          &lt;img src="/assets/play-butt.svg" class="ltag__twitter-tweet__play-butt" alt="Play butt"&gt;
        &lt;/div&gt;
        &lt;div class="ltag__twitter-tweet__video"&gt;
          
            
          
        &lt;/div&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--PPtFNLCr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/943642831809155072/GRuASCdQ_normal.jpg" alt="Tess Rinearson profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Tess Rinearson
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @_tessr
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8bbACBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-99c56e7c338b4d5c17d78f658882ddf18b0bbde5b3f42f84e7964689e7e8fb15.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      “oh, the engineering manager has decided to start programming again” 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      21:59 PM - 24 Aug 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1165383036642312192" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1165383036642312192" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      1425
      &lt;a href="https://twitter.com/intent/like?tweet_id=1165383036642312192" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      6712
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;I’m tasked with developing people instead of applications, and that charge presents its own set of fascinating, difficult challenges that do not leave room for much else in my work day. However, I do need to keep up with trends in pertinent tech, and I find that iterating on my unfrequented personal website in my downtime is a low-pressure way for me to get hands-on experience with contemporary tools of the trade. At my domain, I am free to fail, often and spectacularly, without consequence. That freedom is fertile ground for foolishness (like unwarranted alliteration), and it allows me to entertain grandiose ideas such as re-architecting an application from the ground up… and blogging about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devising an Approach
&lt;/h3&gt;

&lt;p&gt;The home page of my website is made up of a few paragraphs of text, a small number of links, and footnotes that I personally think are fairly amusing. A RESTful-ish Web API called “Andrae” is available at the &lt;a href="https://api.guillermoandraefisher.com"&gt;api subdomain&lt;/a&gt; and flaunts three endpoints: /posts, which is where anyone interested can get their hands on my social media posts in JSON format; /elephpants, which is an example endpoint I put together for a talk about &lt;a href="https://php.net"&gt;PHP&lt;/a&gt; &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; functions built with &lt;a href="https://bref.sh"&gt;Bref&lt;/a&gt; (which I’ve &lt;a href="https://medium.com/@guillermoandrae/automate-deployment-of-php-applications-to-aws-lambda-with-bref-aws-sam-and-travis-ci-90231dfae6ff"&gt;lauded on Medium&lt;/a&gt; in the past); and /nicknames, which is a worthless collection of nicknames that I’m definitely going to delete but am mentioning here for posterity. My social media posts are pushed to the API with &lt;a href="https://zapier.com"&gt;Zapier&lt;/a&gt;. There’s a bare-bones search UI available at a search.html page that lets users dig through my old social media posts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EBGcxHKP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/667/1%2Ap3mZAHQVBImXRNrdHMU_fQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EBGcxHKP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/667/1%2Ap3mZAHQVBImXRNrdHMU_fQ.png" alt=""&gt;&lt;/a&gt;“Zaps” I’ve created to collect my social media activity in one place.&lt;/p&gt;

&lt;p&gt;That’s the current state of affairs. I have 4 goals for this project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I want to be able to answer questions about my social media activity using the data that I’ve been collecting. I’d like to marry it up with my &lt;a href="https://www.apple.com/ios/health/"&gt;health data&lt;/a&gt; to see if any interesting connections exist.&lt;/li&gt;
&lt;li&gt;I want to develop working proficiency with a new language: &lt;a href="https://golang.org/"&gt;Golang&lt;/a&gt;, aka Go.&lt;/li&gt;
&lt;li&gt;I want to limit myself to serverless (read: fully managed, scalable) services because I’m lazy &amp;amp; cheap… or efficient &amp;amp; cost-conscious. Whichever you prefer.&lt;/li&gt;
&lt;li&gt;I want to build a more engaging, more responsive, modern web application that features a proper RESTful API — and eventually a GraphQL API, just so I can say I’ve built one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  The Data Store
&lt;/h4&gt;

&lt;p&gt;I’m going to continue to use DynamoDB as my primary data store. Given that my traffic numbers are low, I’m in no danger of exceeding &lt;a href="https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Categories=categories%23databases&amp;amp;awsf.Free%20Tier%20Types=tier%23always-free"&gt;the AWS free tier&lt;/a&gt; usage thresholds anytime soon, which means it won’t cost me anything to store my data. Said another way: it’ll cost me $0.00 to keep my data in a fault-tolerant, highly available, managed NoSQL database.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Data Story
&lt;/h4&gt;

&lt;p&gt;To analyze my data, I’ll catalog it using &lt;a href="https://aws.amazon.com/glue/"&gt;AWS Glue&lt;/a&gt; and use &lt;a href="https://aws.amazon.com/athena/"&gt;Amazon Athena&lt;/a&gt; to do some exploring. I’ll also use &lt;a href="https://aws.amazon.com/quicksight/"&gt;Amazon QuickSight&lt;/a&gt; to put together visualizations.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Web API
&lt;/h4&gt;

&lt;p&gt;I’ve already mentioned Go — I’ll be using it to build at least one API endpoint. The others will be built with PHP and Bref. All endpoints, regardless of runtime, will be composed of Lambda functions that are exposed via the API Gateway.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Front End
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/guillermoandrae/"&gt;A change in my professional life&lt;/a&gt; has steered my attention towards &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;. I need to have some idea of what it’s like to work with it because I support people who work with it every day. I’m especially motivated because it seems jQuery has fallen out of favor with the JavaScript crowd, and I’m mildly embarrassed that I still rely on it so much. The React app will live in S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;I’m going to start this off by getting my data in order. I made some critical errors with DynamoDB when I started, but I’ve since learned a lot about the service, and I’m eager to apply what I’ve learned.&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 2 in the series, friends!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>data</category>
      <category>aws</category>
      <category>continuouslearning</category>
    </item>
    <item>
      <title>Automate Deployment of PHP Applications to AWS Lambda with Bref, AWS SAM, and Travis CI</title>
      <dc:creator>Guillermo A. Fisher</dc:creator>
      <pubDate>Sat, 25 May 2019 12:36:07 +0000</pubDate>
      <link>https://dev.to/guillermoandrae/automate-deployment-of-php-applications-to-aws-lambda-with-bref-aws-sam-and-travis-ci-179</link>
      <guid>https://dev.to/guillermoandrae/automate-deployment-of-php-applications-to-aws-lambda-with-bref-aws-sam-and-travis-ci-179</guid>
      <description>&lt;p&gt;If you’re a PHP developer whose cloud provider of choice is AWS, chances are you’ve suffered through a bit of serverless FOMO due to AWS Lambda’s lack of support for PHP. But thanks to the &lt;a href="https://aws.amazon.com/serverless/sam/"&gt;AWS Serverless Application Model&lt;/a&gt; (AWS SAM) and &lt;a href="https://bref.sh"&gt;Bref&lt;/a&gt; — both open source projects available on &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; — it’s now possible to deploy your PHP applications to Lambda without having to fiddle with Node.js shims. And with the help of &lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;, running automated tests and deployments is essentially a cinch. There are, however, a few gotchas.&lt;/p&gt;

&lt;h3&gt;
  
  
  I Don’t Know Who or What a “Bref” Is
&lt;/h3&gt;

&lt;p&gt;I didn’t, either, until a few weeks ago. &lt;a href="https://mnapoli.fr/"&gt;Matthieu Napoli&lt;/a&gt;’s “Bref” (which is the French word for “brief”) has been around for a while, and has caught the attention of some &lt;a href="https://akrabat.com/php-architect-serverless-php-with-bref-part-1/"&gt;prominent PHP-ers&lt;/a&gt;. In short:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bref provides the tools and documentation to easily deploy and run serverless PHP applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should take a look at the &lt;a href="https://bref.sh/docs"&gt;Bref documentation&lt;/a&gt; before you move on to familiarize yourself with the goals of the project, walk through the “Getting Started” steps, and read the recommendations concerning the project’s use (for example, don’t expect to use Bref to deploy your legacy application without experiencing the sort of difficulty you’d normally associate with deploying a legacy application).&lt;/p&gt;

&lt;h3&gt;
  
  
  Respect Due
&lt;/h3&gt;

&lt;p&gt;The work detailed in this post rests comfortably on the shoulders of unselfish giants; I would be remiss if I didn’t point you to &lt;a href="https://www.sysengcooking.com/posts/deploy-aws-lambda-functions-with-aws-sam-cli-and-travis-ci-2/"&gt;this post&lt;/a&gt; explaining what’s needed to deploy Python applications to AWS Lambda using AWS SAM and Travis CI. You’ll find that a lot of what is covered there was put to use here, and the author (&lt;a href="https://sysengcooking.com"&gt;Mike Vanbuskirk&lt;/a&gt;) does a great job covering the &lt;a href="https://www.sysengcooking.com/posts/deploy-aws-lambda-functions-with-aws-sam-cli-and-travis-ci-1/"&gt;pertinent prerequisites&lt;/a&gt;. In fact, I consider that series to be a prerequisite to this post, and suggest you read it before continuing, unless you’re impatient and just want to get to the goodies.&lt;/p&gt;

&lt;h3&gt;
  
  
  So… Travis CI?
&lt;/h3&gt;

&lt;p&gt;Alright. Into the fray. I’ve set up a sample application at &lt;a href="https://github.com/guillermoandrae/bref-hello-world"&gt;https://github.com/guillermoandrae/bref-hello-world&lt;/a&gt;, and will be using its .travis.yml file to explain this process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;language: generic
dist: xenial
before\_install:
  - phpenv global 7.2
install:
  - pip install --user awscli
  - pip install --user aws-sam-cli
script:
  - composer install --optimize-autoloader
  - composer test
  - composer package
deploy:
  provider: script
  script: composer deploy
  skip\_cleanup: true
  on:
    repo: guillermoandrae/bref-hello-world
env:
  global:
  - AWS\_DEFAULT\_REGION=us-east-1
  - secure: &amp;lt;obfuscated AWS Access Key ID&amp;gt;
  - secure: &amp;lt;obfuscated AWS Secret Key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Biggest Gotcha: The Build Environment
&lt;/h3&gt;

&lt;p&gt;Let’s start at the tippy top:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;language: generic
dist: xenial
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A cursory look at the configuration file reveals two points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I’m using &lt;a href="https://github.com/phpenv/phpenv"&gt;phpenv&lt;/a&gt;, which is a PHP version manager.&lt;/li&gt;
&lt;li&gt;I’m using &lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt;, which is the standard package manager for Python.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The build environment needs to have both PHP and Python installed in order for all of the necessary tasks to be successfully executed. If you’ve used Travis CI for your PHP projects before, you’ve probably specified the language at the top of the YAML file with a line like language: php, which would prompt Travis CI to use a PHP build environment. I needed to use a lesser known language designation that supported my use case.&lt;/p&gt;

&lt;p&gt;Travis CI provides two build environments that are not language-specific: the &lt;a href="https://docs.travis-ci.com/user/languages/minimal-and-generic/#minimal"&gt;minimal environment&lt;/a&gt; and the &lt;a href="https://docs.travis-ci.com/user/languages/minimal-and-generic/#generic"&gt;generic environment&lt;/a&gt;. The former is, as the name implies, pretty bare. Aside from gcc, curl, and a few other tools, minimal is not outfitted with much besides Docker and Python. The generic environment, on the other hand, includes everything included in the minimal environment as well as some additional runtimes such as Go, Ruby, and PHP.&lt;/p&gt;

&lt;p&gt;If you’re not familiar with it, the dist parameter of a Travis CI configuration file is used to denote the specific distribution of the build environment that you’d like to use. With Ubuntu images, you’ve got three options — trusty (the default, used when no distribution is specified), precise, and xenial —and they all contain specific configuration settings. In the case of the minimal and generic build environments, however, only precise and xenial are available. I’m using the xenial environment, as it’s the one that contains a suitable version of PHP.&lt;/p&gt;
&lt;h3&gt;
  
  
  A Little More About PHP Versions
&lt;/h3&gt;

&lt;p&gt;By default, the xenial environment uses the lowest of the &lt;a href="https://docs.travis-ci.com/user/reference/xenial/#php-support"&gt;three versions of PHP&lt;/a&gt; that are installed: 5.6. To change that, I used phpenv to specify the version that meets our needs. phpenv is installed on all Travis CI images that run PHP:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before\_install:
- phpenv global 7.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  CLI Tool Installation vs. Python Versions
&lt;/h3&gt;

&lt;p&gt;In Mike’s series, the installation of the AWS CLI and AWS SAM CLI tools seemed pretty straight forward, and I had no problem installing them on my laptop. I, however, found myself running into this error using Travis CI:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/security.html#insecureplatformwarning.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I tried using the python parameter, then the TRAVIS_PYTHON_VERSION environment variable to specify a newer Python version, but neither brought me joy. So, after some focussed Googling, I was led to a solution that involved adding the --user flag to the installation commands. So I ended up with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;install:
- pip install --user awscli
- pip install --user aws-sam-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Composer Scripts FTW
&lt;/h3&gt;

&lt;p&gt;I really, really, really, really, really like &lt;a href="https://getcomposer.org/doc/articles/scripts.md"&gt;Composer scripts&lt;/a&gt;. In the past, I used &lt;a href="https://www.phing.info/"&gt;Phing&lt;/a&gt; as a task runner without realizing that I could leverage the scripts feature of Composer to do almost exactly the same thing. I’ve since seen the proverbial light and haven’t turned back:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script:
- composer install --optimize-autoloader
- composer test
- composer package
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A look at an excerpt of the project’s composer.json file will shed some light on exactly what is going on:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The test script includes calls to &lt;a href="https://github.com/squizlabs/PHP_CodeSniffer/wiki/Fixing-Errors-Automatically"&gt;Squizlabs’ code beautifier and code sniffer&lt;/a&gt; as well as a call to run the tests and generate coverage reports in both text format and Clover format (the Clover format is especially useful if you plan to integrate Travis CI with a tool/service that can create code coverage visualizations or store a project’s code coverage history). The package script calls sam package to build the &lt;a href="https://bref.sh/docs/deploy.html#stacks"&gt;stack&lt;/a&gt; configuration file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying… Finally!
&lt;/h3&gt;

&lt;p&gt;I’m using Travis CI’s script deployment option to call my deployment script and associate the appropriate GitHub repository. The details behind the composer deploy call below are uncovered in the aforementioned composer.json excerpt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy:
  provider: script
  script: composer deploy
  skip\_cleanup: true
  on:
    repo: guillermoandrae/bref-hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  That’s All, Folks
&lt;/h3&gt;

&lt;p&gt;We’re done. PHP on AWS Lambda with a proper CI/CD pipeline. No fuss. Life is good. If you have any questions or comments, don’t be shy about sharing them.&lt;/p&gt;

</description>
      <category>travisci</category>
      <category>bref</category>
      <category>php</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
