<?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: Kyle Stratis</title>
    <description>The latest articles on DEV Community by Kyle Stratis (@kyle_stratis).</description>
    <link>https://dev.to/kyle_stratis</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%2F20422%2F3e3f8672-1aba-4951-bbe5-49b39482d46b.jpg</url>
      <title>DEV Community: Kyle Stratis</title>
      <link>https://dev.to/kyle_stratis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kyle_stratis"/>
    <language>en</language>
    <item>
      <title>Trigger an AWS Step Function with an API Gateway REST API using CDK</title>
      <dc:creator>Kyle Stratis</dc:creator>
      <pubDate>Sat, 25 Sep 2021 20:38:22 +0000</pubDate>
      <link>https://dev.to/kyle_stratis/trigger-an-aws-step-function-with-an-api-gateway-rest-api-using-cdk-1nb3</link>
      <guid>https://dev.to/kyle_stratis/trigger-an-aws-step-function-with-an-api-gateway-rest-api-using-cdk-1nb3</guid>
      <description>&lt;p&gt;AWS documentation can be rough. Have you ever looked for an example of something you're trying to set up but only finding bits and pieces of what you need across several different sites? That was my experience recently when trying to set up a REST API with API Gateway that would trigger a Step Function.&lt;/p&gt;

&lt;p&gt;There are some good tutorials and examples for doing this, just in the AWS console. What about infrastructure-as-code geeks? There isn't quite so much. And so, this tutorial. In it, you will learn how to use CDK to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an API Gateway REST API that takes a single parameter&lt;/li&gt;
&lt;li&gt;an IAM role that allows the API to connect to your Step Function&lt;/li&gt;
&lt;li&gt;an API Gateway integration to connect your API to your Step Function, passing along a parameter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial is for current CDK users looking for examples of connecting AWS services like Step Functions to APIs set up in CDK. While it uses Python CDK, translating to Typescript or other languages should be trivial.&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;WARNING:&lt;/strong&gt; 🚨 Deploying to AWS may incur charges. To ensure this doesn't happen, tear down any deployed resources with &lt;code&gt;cdk destroy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the Step Function
&lt;/h2&gt;

&lt;p&gt;Define a step function as you usually would. For this article, let's assume you created a step function called &lt;code&gt;item_step_function&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the API
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;aws_cdk.aws_apigateway&lt;/code&gt;'s &lt;code&gt;RestApi&lt;/code&gt; constructor to create the base API object. You will use this for all further API setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_apigateway&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_stepfunctions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;item_step_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;([...])&lt;/span&gt;
&lt;span class="n"&gt;item_api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"item-api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚨 &lt;strong&gt;WARNING&lt;/strong&gt; 🚨 This example code does not do any additional authorization beyond what is done by AWS by default. You may wish to add additional security measures for a production workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up Role
&lt;/h3&gt;

&lt;p&gt;The earlier you set up the IAM permissions, the better. The proper IAM permissions will allow your API to trigger your step function. You will use the &lt;code&gt;Role&lt;/code&gt; construct in the &lt;code&gt;aws_iam&lt;/code&gt; package for this.&lt;/p&gt;

&lt;p&gt;First, you will instantiate the &lt;code&gt;Role,&lt;/code&gt; give it a name, and pick the service that will assume it. Since you want API Gateway to have access to Step Functions, you will use &lt;code&gt;"apigateway.amazonaws.com"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;item_api_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"item-api-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"item-api-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;assumed_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apigateway.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is set up, you will need to add a policy to the Role, which defines the permissions that the Role grants to the service that assumes it. AWS IAM provides several managed policies that cover most use cases, so it is unlikely that you will need to craft your own. For this guide, you will use the AWSStepFunctionsFullAccess managed policy, but in most cases, you will want to use a more restrictive managed or custom-built policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;item_api_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_managed_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_aws_managed_policy_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AWSStepFunctionsFullAccess"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these two calls, you've created a role with a policy that will allow your API to interact with your Step Function. You will still need to link with this Role with the API itself, but that will come later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up resources
&lt;/h2&gt;

&lt;p&gt;Resources are any path-based pieces of your request URI. While you can nest resources using the &lt;code&gt;.add_resource()&lt;/code&gt; method, you will add a single resource level for this tutorial. To use a resource as a parameter, surround your parameter name with curly braces. Note that you have to add it to the &lt;code&gt;root&lt;/code&gt; of the API object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;step_function_trigger_resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{item_id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your request URI will look something like &lt;code&gt;[https://aws-generated-tld.com/1337](https://aws-generated-tld.com/1337)&lt;/code&gt; where &lt;code&gt;1337&lt;/code&gt; is the &lt;code&gt;item_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;Note&lt;/strong&gt; 🚨 You can also set up a query string parameter if you wish. For this tutorial, we will stick with path-based parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect Your API to Your Step Function
&lt;/h2&gt;

&lt;p&gt;CDK provides an &lt;code&gt;AWSIntegration&lt;/code&gt; construct that is supposed to make it easier to integrate with other AWS services. It does not. At least, not by itself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AWSIntegration&lt;/code&gt; construct is difficult to use because implementations for different services aren't well-documented. You may not even know the internal service name for Step Functions or any other service you wish to integrate and have difficulty finding it. (if you do, the AWS CLI is here to help: &lt;code&gt;aws list-services&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Request Templates
&lt;/h3&gt;

&lt;p&gt;Before setting the integration itself, you need to set up a request template. A request template allows you to build the request you are making to your Step Function, including transmitting your API parameters to the Step Function.&lt;/p&gt;

&lt;p&gt;The template is a dictionary and should have a single key of &lt;code&gt;"application/json"&lt;/code&gt;. Its value is a JSONified dictionary with your step function's ARN and &lt;code&gt;input&lt;/code&gt;, which is part of the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html"&gt;Step Function StartExecution request syntax&lt;/a&gt;. You will use some methods built into the request from Amazon, specifically &lt;code&gt;$input.params()&lt;/code&gt;, which &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#input-variable-reference"&gt;allows you to grab some or all of your request's parameters&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I strongly recommend you escape any Javascript by wrapping your &lt;code&gt;$input.params()&lt;/code&gt; call with &lt;code&gt;$util.escapeJavaScript()&lt;/code&gt;:&lt;br&gt;
&lt;code&gt;"$util.escapeJavaScript($input.params('item_id'))"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your template should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;request_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"stateMachineArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item_state_machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_machine_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;item_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;$util.escapeJavaScript($input.params('item_id'))&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step Function Integration
&lt;/h3&gt;

&lt;p&gt;Next, you will define the integration itself. The integration requires you to set a few parameters, but it's not always clear from the CDK documents which are the correct ones to set. This ambiguity is thanks to how general the &lt;code&gt;AWSIntegration&lt;/code&gt; construct is: it allows you to use any service, but you have to know what parameters their requests need.&lt;/p&gt;

&lt;p&gt;For all integrations, you need to provide the service name. For Step Functions, it's &lt;code&gt;"states"&lt;/code&gt;. Then you need to provide the action you want to do. To start a Step Function execution, you'll use &lt;code&gt;"StartExecution"&lt;/code&gt;. This is determined again by the service's API, and you can read more about &lt;code&gt;"StartExecution"&lt;/code&gt; &lt;a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html"&gt;in the AWS Step Function documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Then, you'll provide options. In CDK, these are &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.IntegrationOptions.html"&gt;IntegrationOptions&lt;/a&gt;. Here, you can define many options, but for this tutorial, the important ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;credentials_role&lt;/code&gt;: This will take the &lt;code&gt;item_api_role&lt;/code&gt; you set up earlier, attaching the Role (and its attached policy) to the API itself via the integration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;integration_responses&lt;/code&gt;: This is a list of possible responses to API requests. At the very least, you'll want to return an &lt;code&gt;IntegrationResponse&lt;/code&gt; object with a 200 status code, but you can define all sorts of situations that would trigger different status codes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request_templates&lt;/code&gt;: This is where you'll attach the request template you made in the pre&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Investigate these options and determine which options are right for your use case. To keep things simple, this example will pass credentials through the integration to the integrated service and only return a 200 response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;item_sfn_integration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwsIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"states"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"StartExecution"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;credentials_role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item_api_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;integration_responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegrationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;request_templates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you created an integration between the Step Functions service and the API itself. You can think of the integration as the portal that your parameters pass through when traveling from the API to your integrated service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect Integration to REST verbs
&lt;/h2&gt;

&lt;p&gt;Do you remember the &lt;code&gt;resource&lt;/code&gt; you set up earlier, defining the &lt;code&gt;item_id&lt;/code&gt; parameter for the API? This object comes with an &lt;code&gt;add_method()&lt;/code&gt; function, which you can use to connect a REST verb (such as &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, etc.) to your integration. Doing this will allow a request using the correct verb to reach your integration.&lt;/p&gt;

&lt;p&gt;Since you're sending data via the API, you'll use &lt;code&gt;POST&lt;/code&gt; and wire it to &lt;code&gt;item_sfn_integration&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;step_function_trigger_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;item_sfn_integration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;method_responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you connected your integration to your &lt;code&gt;step_function_trigger_resource&lt;/code&gt; via the &lt;code&gt;POST&lt;/code&gt; verb, and you set it to respond with a &lt;code&gt;200&lt;/code&gt; response status. Like the integration, you can set multiple method response statuses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and Wrapping Up
&lt;/h2&gt;

&lt;p&gt;To test this, deploy with &lt;code&gt;cdk deploy &amp;lt;location&amp;gt;&lt;/code&gt;, open the &lt;a href="https://console.aws.amazon.com/apigateway"&gt;API Gateway console&lt;/a&gt;, and navigate to your API and the REST verb you set up. Just click &lt;strong&gt;Test&lt;/strong&gt;, add your parameter, and check the output. You can also navigate to the Step Functions console and check on your execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that you've learned how to wire an API up to a Step Function, you can do several things to dive deeper. Here are some suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change your path parameter to a query string. How does this change how you set up the API and the service integration?&lt;/li&gt;
&lt;li&gt;Make a more complex API. Multiple resources and multiple levels of resources. Can you mimic the structure of a public API, like &lt;a href="https://www.reddit.com/dev/api/"&gt;Reddit's&lt;/a&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_apigateway&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_stepfunctions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;item_step_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;([...])&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the API
&lt;/span&gt;&lt;span class="n"&gt;item_api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"item-api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set up IAM role and policy
&lt;/span&gt;&lt;span class="n"&gt;item_api_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"item-api-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"item-api-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;assumed_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apigateway.amazonaws.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;item_api_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_managed_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_aws_managed_policy_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AWSStepFunctionsFullAccess"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set up API resources
&lt;/span&gt;&lt;span class="n"&gt;step_function_trigger_resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{item_id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set up request template and integration
&lt;/span&gt;&lt;span class="n"&gt;request_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"stateMachineArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item_state_machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_machine_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;item_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;$util.escapeJavaScript($input.params('item_id'))&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;item_sfn_integration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwsIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"states"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"StartExecution"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;credentials_role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item_api_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;integration_responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegrationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;request_templates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Connect integrations to REST verbs
&lt;/span&gt;&lt;span class="n"&gt;step_function_trigger_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;item_sfn_integration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;method_responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>infrastructureascode</category>
      <category>cdk</category>
      <category>python</category>
    </item>
    <item>
      <title>How a Side Project Helped Me Double My Salary</title>
      <dc:creator>Kyle Stratis</dc:creator>
      <pubDate>Sun, 04 Feb 2018 05:44:57 +0000</pubDate>
      <link>https://dev.to/kyle_stratis/how-a-side-project-helped-me-double-my-salary-8g1</link>
      <guid>https://dev.to/kyle_stratis/how-a-side-project-helped-me-double-my-salary-8g1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kylestratis.com"&gt;my personal blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The dust has settled. The boxes are (mostly) unpacked. The cats have claimed their perches. At the beginning of January, Tallahassee got its first real snow in decades, and my wife and I prepared to take our adventure to our mutual dream city, Boston. While the 1,300 mile trip could consume a post or two on its own, today I'd like to talk about what brought me from Tallahassee to Boston, and how I got there.  &lt;/p&gt;

&lt;p&gt;Like everything in life, there was an element of luck involved. I was lucky to have a great recruiter, working for me, I was lucky to have interviewed with people who saw the value in my projects in particular, as well as side projects in general, and saw a fit for me in a fast-moving team working on highly experimental data tooling. However, the harder your work, the luckier you seem to get - and there are elements from this experience that I think can aid people in any job search. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Beginning of the Job Search
&lt;/h2&gt;

&lt;p&gt;What motivated me to look in the first place? I enjoyed the team I worked with (the building we were in? Not so much), and while we never saw Tallahassee as our forever home, my wife and I both made great lifelong friends there and had a great routine. Well, as we were preparing for our first anniversary trip, a recruiter for Amazon reached out to me on LinkedIn. I wasn't planning on taking it, but after the results of my pitching &lt;a href="https://danqex.com"&gt;Danqex&lt;/a&gt; (formerly NASDANQ - and fodder for another post) and with the encouragement of my career sherpa (who also works for Amazon), I decided to go for it. That got me itching to see what was out there given my interests and experience - and there was a lot. I figured I'd look for remote opportunities, save up some money, then move in the early summer or fall to Boston. The best laid plans, yadda yadda yadda. &lt;/p&gt;

&lt;h2&gt;
  
  
  Updating My Resume
&lt;/h2&gt;

&lt;p&gt;To prepare for this, I had to update my resume. I added some projects and experience I got while working at Homes.com, but I think the most important thing was adding my work as Danqex cofounder, CEO, and data lead. This was something I worried about - I didn't want to give the impression that I'd up and leave right away for Danqex, but at the same time it was (and continues to be) a source of super-concentrated experience in a number of areas - team management, project management, development, technology selection, even dealing with investors and knowing a bit about how funding works. So I added it to my resume - one thing I've learned in my career is to be competely upfront in the job search, because the hunt is a lot like dating: a good fit is far more important than the quickest almost-fit. Danqex also worked as a great conversation starter - who doesn't like to talk memes? &lt;/p&gt;

&lt;h2&gt;
  
  
  Mismatches and Encouragement
&lt;/h2&gt;

&lt;p&gt;The Amazon interview came and went. My wife and I had just gotten back from our trip, and I hadn't much time to review data structures and algos. I wasn't aware that I'd have a coding test (something that is always a source of anxiety) at the first phone interview, so that happened and I powered through that anxiety because I had no choice. Getting that out of the way was great, and I actually enjoyed the problem given - unfortunately Amazon decided to pass on me. It was disappointing, but now I didn't have to move to Seattle. &lt;/p&gt;

&lt;p&gt;I had quite a few other interviews, which was actually very encouraging because my previous job searches did not often get past the resume submission. These were often great, geeking out with someone else about technologies we loved and work that we'd do. Unfortunately a lot of these were not good matches - often because of a lack of experience with their technologies. The types of companies that don't encourage on-the-job learning (or can't) aren't the ones I necessarily want to work for - picking up a language to do work is pretty quick, even though mastering it takes many hours of work. Picking up supporting technologies (think Kafka, etc.) is much quicker.&lt;/p&gt;

&lt;p&gt;One job I applied to I didn't realize was for in Boston and was through an external recruitment firm. I was nervous when this became clear, because I've only heard bad things about these, but I am incredibly grateful for the experience and it worked out perfectly. The business model here is interesting: companies that don't necessarily have the resources to do their own recruitment on a large scale will pay another firm to do it, in this industry this is mostly startups or companies undergoing very rapid growth. The firm that posted the ad I applied to was &lt;a href="https://www.winterwyman.com"&gt;WinterWyman&lt;/a&gt;, and they have teams dedicated to different fields. The recruiter, Jamie, contacted me, and said he didn't think what I applied to would be the ideal fit, but he'd talk to them anyway - in the meantime, though, he wanted to know what my priorities were as far as my career and what things are important for me in a company. I told him I was ready to make an impact on society in some way - one of the jobs I applied for was for a company doing research on various mental issues as detected in social media postings. I wanted to be able to take point on my projects and have ownership, have the opportunity to advance, work with interesting technologies, process lots of data, and a few others, but my most important priority was impactful work. He returned to me a list of companies that I did some research on, and picked a few. One didn't want me because I didn't have a CS degree (their loss - and I'm happy not to work in a culture with those attitudes), a few others dropped off, but one in particular was right up my alley and was really interested in me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Match
&lt;/h2&gt;

&lt;p&gt;This company was &lt;a href="https://patientslikeme.com"&gt;PatientsLikeMe&lt;/a&gt; which has a track record of not only improving lives of patients through connecting them in support networks, but was also doing some groundbreaking research with the data that users of the platform provide. Impactful? You bet. They wanted a data engineer for a new data engineering team that supports the research team and builds tools to trial before bringing them to full production status. Ownership? Plenty. I had two phone interviews, one with my future boss, and one with a teammate. Both were a lot of fun, talking about Danqex/Nasdanq, my experience, my educational background, and more. Jamie helped me prepare for both, and called me quickly to let me know that the team was really excited about me and would be setting me up for a trip to Cambridge for an in-person interview. &lt;/p&gt;

&lt;p&gt;I flew in to Logan (almost missed my flight because my car greeted us in the morning with a flat), spent a few days with my dad, and then got settled in my hotel in Cambridge. My recruiter had been giving me a lot of details on what to expect for the interview, which helped put me at ease, and after a good night of sleep I got dressed and headed over to the PLM offices. While there I went through a few rounds of interviewing, two with my future boss, one with the other team members, one with HR, and one with one of the scientists on the biocomputing team, who we'd be supporting. The topics ranged from the function of outer hair cells (the subject of my research in grad school) to the design of database tables given some features to RaspberryPis to how to trade memes for profit. Instead of an interview, it was more like meeting with a bunch of interesting and smart people that geek out over the same things I do, and getting to chat about our passions. It was fun. &lt;/p&gt;

&lt;p&gt;After the interview I met with some family and before leaving, I got to meet Jamie in person for breakfast. He informed me there was another person being interviewed, but that I'd hear something within the next couple of weeks. A little over one long week later, Jamie called me to let me know an offer was coming. The offer came, and it was exactly what I was looking for and the match was officially made. &lt;/p&gt;

&lt;p&gt;One mild issue - the job was not remote. We'd be moving to Boston on the heels of record-breaking cold with plenty of winter left for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;Wrapped up in all that are a few lessons that can be gleaned from this, I think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work on your side projects, take them farther than makes sense, and be a cheerleader for them and the work you did on them. &lt;/li&gt;
&lt;li&gt;Be prepared to answer truthfully any hard questions about those projects. One of the questions asked was how my priorities would shift with this job in relation to Danqex. Of course, the job would come first, Danqex was born as a side project and that's how we're equipped to work on it. You'll likely get those questions and more about your projects, and you should know them inside and out. Pitching to investors, while not feasible for many, was a great preparation for this.&lt;/li&gt;
&lt;li&gt;Get outside of your comfort zone with technologies you work with. This is especially useful if you, like I was, work with a less in-demand language at your current job. &lt;/li&gt;
&lt;li&gt;Find the &lt;strong&gt;best&lt;/strong&gt; match, not the the first yes. I've been working at PLM now for just under 3 weeks, and it has been the best match for me. A fun, stimulating culture (we have journal club between 1 and 3 times a week, and also a well-stocked beer fridge!), brilliant people to work with (2 of my teammates have PhDs from MIT, the other studied at WPI), a team that's all about rapid prototyping, proving a tool we make, and then letting it mature to another team to maintain, and a shared drive to truly push the science of chronic illness and improve the lives of our patients in tangible ways. Work that truly matters is one of the greatest motivators of all. &lt;/li&gt;
&lt;li&gt;To help with the above lesson - don't wait (if you can help it) until you absolutely need a job to start looking for your next step. This keeps you from making any spur of the moment emotional decisions, and keeps the ball in your court as you wade through rejections and negotiations. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>salary</category>
      <category>career</category>
      <category>hustle</category>
    </item>
    <item>
      <title>A MongoDB Optimization</title>
      <dc:creator>Kyle Stratis</dc:creator>
      <pubDate>Sun, 29 Oct 2017 21:43:51 +0000</pubDate>
      <link>https://dev.to/kyle_stratis/a-mongodb-optimization-e64</link>
      <guid>https://dev.to/kyle_stratis/a-mongodb-optimization-e64</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.kylestratis.com/post/mongodb-aggregation-pipelines-to-reduce-time-of-data-operations"&gt;my personal blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently at Homes.com, one of my coworkers was charged with speeding up a batch process that we were required to run at a scheduled interval. No big deal, but he was stuck: the process required a number of steps at every typical 'stage', for identifying the data we needed to pull, for pulling the data, for transforming the data, and for writing the transformed data back to Mongo. When he was talking about the process, I realized this would be a perfect use case for Mongo's aggregation framework. I offered to help, based on my experience with the aggregation framework I got while working on &lt;a href="https://nasdanq.com"&gt;NASDANQ&lt;/a&gt;, and immediately got to work on designing an aggregation pipeline to handle this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Solution
&lt;/h2&gt;

&lt;p&gt;This batch process exists to update mean and median home value data for a given area. A rough overview is laid out below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query a large collection (&amp;gt; 1 TB) for two fields that help identify an area in which a property resides.&lt;/li&gt;
&lt;li&gt;Join into a single ID&lt;/li&gt;
&lt;li&gt;Use this to pull location data from another collection on a separate database (&amp;gt; 25 GB).&lt;/li&gt;
&lt;li&gt;For each property in this area, we pull from another collection the price.&lt;/li&gt;
&lt;li&gt;These are loaded into an array in our script, and then we iterate to find the mean and median. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At our estimates, if we could run this process to completion with no interruption, it would take ~104 days to finish. This is unacceptable for obvious reasons. &lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt the First
&lt;/h2&gt;

&lt;p&gt;We ran into an architecture issue early on. MongoDB's aggregation pipeline doesn't support working across multiple databases, and the collections we needed to work on were split between a few databases. Luckily, we were able to move the collections onto a single database so we could start testing the pipeline. We started with a 9 stage monster - the multiple collections we had to match on required multiple match stages and a stage to perform what was essentially a join. Because the data is stored in memory at each stage, and each stage is &lt;a href="https://docs.mongodb.com/manual/core/aggregation-pipeline-limits/"&gt;limited to 100MB of memory&lt;/a&gt; we first attempted to switch on the &lt;code&gt;allowDiskUse&lt;/code&gt; option to allow the pipeline to at least run. And run it did. Our DBA team notified us that we were spiking memory usage to unacceptable levels while the pipeline was running.&lt;/p&gt;

&lt;p&gt;We reduced the pipeline to 7 stages - we take location data as inputs to the pipeline, match on this to the 25GB collection, use &lt;code&gt;$lookup&lt;/code&gt; to join in the 1TB collection holding the ID fields on one of the ID fields, project the fields we actually want, unwind, redact, sort by value, group by location (2 fields), take an average, and sort again. The pipeline looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$match-&amp;gt;$lookup-&amp;gt;$project-&amp;gt;$unwind-&amp;gt;$redact
                                      |
                                      V
       VALUES&amp;lt;-$sort&amp;lt;-$avg&amp;lt;-$group&amp;lt;-$sort

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

&lt;/div&gt;



&lt;p&gt;This failed at &lt;code&gt;$lookup&lt;/code&gt;. Why? For many locations, we were joining in nearly 1 million documents per month of data, and we wanted multiple years of that data. Across all locations, this fails to solve our performance issues. It also fails to run. &lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt the Second
&lt;/h2&gt;

&lt;p&gt;Our first thought was to add the unique identifier (which was a combination of two fields in different collections) to the 1TB collection, but that was not workable due to the size of the collection. Instead, what we can do is project a concatenated version of the two fields we are using as a UID, use &lt;code&gt;$lookup&lt;/code&gt; to join in the 25GB collection on that - because it's much faster to make this change to the smaller collection. Simultaneously, we were testing performance differences in sorting in our ETL code vs. within the pipeline itself. Since the time taken in the code to run these sorts was trivial, we could remove these stages from the pipeline. Now we are looking at all IDs for a single month taking 10 days, but we need to run multiple years - this comes out to worse performance than the original solution. &lt;/p&gt;

&lt;p&gt;However, an interesting finding was when we ran a single identifier at a time - 1 for one month took about a minute. But one location identifier over 3 years only took 10 minutes. I mention this because it demonstrates how nicely aggregation pipeline performance scales as your dataset grows. And remember how I said we projected all IDs for one month taking 10 days? In light of the results showing that time taken in the pipeline does not scale linearly with data size, we ran the pipeline and found that a single month for all identifiers took about 14 hours. This is a big improvement, but not enough. So we look for more optimizations. &lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt the Third
&lt;/h2&gt;

&lt;p&gt;This was a smaller change, and on its own created a big change in time taken to process our data. We re-architected our data so that we had a temporary collection of a single month of data. We generally process one month at a time, despite the overall length of time we want. We were able to cut the time in half from the previous attempt - a single month for all identifiers now took only 7 hours and we were now not querying the full 1TB collection when we only wanted a small piece of it anyways. Creating the temporary collections is trivial and done by our counterparts on the DBA team as a part of their data loading procedures. &lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt the Fourth
&lt;/h2&gt;

&lt;p&gt;After seeing these results, management was fully convinced of the need of redesigning how we stored this data. Let this be a lesson: hard data is &lt;em&gt;very&lt;/em&gt; convincing. So now each month's data will be in its own collection, when we load data the location data will also be added in to these monthly collections avoiding costly joins via &lt;code&gt;$lookup&lt;/code&gt;. Surprisingly, while testing, adding this information did not impact our overall data preloading times. This location data was also indexed for quicker querying. All of this allowed us to go from a 7-stage aggregation pipeline to 3. Now we start with the split collections, project the fields we are interested (location, value, etc.), group on location, average them and also add all individual values to an array (for sorting and finding the median in code), and output to a temp collection. If we want to process a period of time longer than a month, we rinse and repeat. &lt;/p&gt;

&lt;p&gt;For all identifiers in each month, our processing time went from 7 hours to 8 &lt;em&gt;minutes&lt;/em&gt;. Then the queries made on the generated collections to get the computed averages plus arrays of individual values to calculate the median in code added a minute per output collection if we did it serially. Being primarily ETL pipeline builders, we do nothing serially. We tested with 7 workers, and the added processing time goes from a minute to 30 seconds. In production, we have a worker pool that numbers in the hundreds, so this additional time was satisfactory. If we assume a conservative 7.5 minutes to process a month of data, then projecting to 3 years we estimated we should see runtime of around 4.5 hours. We decided we were happy with this, especially when we considered the original process was projected to take 104 days.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$project-&amp;gt;$avg-&amp;gt;$out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I learned a lot of lessons from this little project, and wanted to distill them here as a sort of tl;dr to ensure that they can be passed on to the reader. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Management is convinced by data. Run your proposed changes, show them graphs or numbers of performance improvements. You're all working to the same goal. &lt;/li&gt;
&lt;li&gt;ABB. Always Be Building. In my case, my work on &lt;a href="https://nasdanq.com"&gt;NASDANQ&lt;/a&gt; gave me the knowledge I needed to hear a teammate's struggles, identify a use case, and implement a plan to alleviate those issues.&lt;/li&gt;
&lt;li&gt;Standups suck, but they're useful. Hearing my teammate's struggles with this code during a standup meeting allowed me to assist him and come up with a workable solution. &lt;/li&gt;
&lt;li&gt;More generally, communication is important. Not only was understanding my teammate's needs important, but this project required constant contact between our DBA team, me and my teammate (who was running many of our tests), and management of our team, the DBA team, and the larger team we report to. &lt;/li&gt;
&lt;li&gt;And, finally, MongoDB's aggregation pipelines are incredibly powerful. They're worth learning and getting familiar with if you work with sizable datasets at all. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mongodb</category>
      <category>optimization</category>
      <category>nosql</category>
    </item>
    <item>
      <title>Hi, I'm Kyle Stratis</title>
      <dc:creator>Kyle Stratis</dc:creator>
      <pubDate>Fri, 16 Jun 2017 14:22:15 +0000</pubDate>
      <link>https://dev.to/kyle_stratis/hi-im-kyle-stratis</link>
      <guid>https://dev.to/kyle_stratis/hi-im-kyle-stratis</guid>
      <description>&lt;p&gt;I have been coding professionally for 3 years.&lt;/p&gt;

&lt;p&gt;You can find me on Twitter as &lt;a href="https://twitter.com/KyleStratis" rel="noopener noreferrer"&gt;@KyleStratis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I live in Boston, MA.&lt;/p&gt;

&lt;p&gt;I work for &lt;del&gt;NASDANQ&lt;/del&gt; Danqex and PatientsLikeMe&lt;/p&gt;

&lt;p&gt;I mostly program in these languages: Python, Perl, and Go.&lt;/p&gt;

&lt;p&gt;I am currently learning more about Python and data science.&lt;/p&gt;

&lt;p&gt;Nice to meet you.&lt;/p&gt;

</description>
      <category>introduction</category>
    </item>
  </channel>
</rss>
