<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Matthew Harper on Medium]]></title>
        <description><![CDATA[Stories by Matthew Harper on Medium]]></description>
        <link>https://medium.com/@matt-harper?source=rss-a9fb260ed68f------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*enlkJlMOPZtWEv7vdZm43g.jpeg</url>
            <title>Stories by Matthew Harper on Medium</title>
            <link>https://medium.com/@matt-harper?source=rss-a9fb260ed68f------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 18:48:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@matt-harper/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Build an AI Agent in 5 Minutes with AWS Strands]]></title>
            <link>https://matt-harper.medium.com/build-an-ai-agent-in-5-minutes-with-aws-strands-c302e03bf738?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/c302e03bf738</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Tue, 26 Aug 2025 13:50:55 GMT</pubDate>
            <atom:updated>2025-08-26T13:50:55.062Z</atom:updated>
            <content:encoded><![CDATA[<h4>A step by step tutorial from beginner to functional agent</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5Kd3VPH3J4sJOn0tYnPX1A.png" /></figure><p>Before we dive into the details of building agents, let’s review what we consider an “agent”. Referencing Anthropic’s <a href="https://www.anthropic.com/engineering/building-effective-agents"><em>Building Effective Agents</em></a> blog, we can think of an agent as a system that uses an LLM to take actions toward a goal, often by reasoning over context, making decisions, and interacting with external tools or data sources.<br>Anthropic highlights a distinction between:</p><p><strong>Workflows</strong> — Prescriptive sequences where the LLM follows a predefined, often linear path. They are structured, predictable, and tightly scoped.</p><ul><li>A backfill workflow may process a very specific type of support ticket with a fixed series of steps (identify impacted customers, issue refunds, send notification emails.)</li><li>A daily briefing workflow may review all of your email, check your meeting schedule, determine the highest priority items on your agenda, and generate a summary for you.</li></ul><p><strong>Agents</strong> — More flexible, adaptive systems that can decide what steps to take next based on evolving information, user input, or intermediate results. Agents can be highly autonomous or lightly guided, but the defining trait is their ability to choose actions dynamically rather than strictly follow a static script. Examples:</p><ul><li>A support agent may process any ticket in a queue, and decide whether to execute some mitigation, escalate the ticket to a human, or gather more data before responding.</li><li>A coding agent (Cline/Cursor/Q CLI) may take a prompt from a user, and decide whether it can immediately write code, or read files from the existing project, or do research on the web, or write a design before implementing anything.</li></ul><h3>Why Developers Build Agents</h3><p>From a developer’s perspective, there are a few compelling reasons to build an agent instead of (or alongside) more traditional workflows:</p><ol><li><strong>Dynamic Problem Solving</strong><br> Agents excel in environments where the “right” sequence of steps can’t be fully determined in advance — for example, debugging an unfamiliar error, triaging customer issues, or planning a multi-step research task.</li><li><strong>Integration with Tools and APIs</strong><br> Agents can call APIs, run code, query databases, and trigger actions in other systems.</li><li><strong>Adaptability and Context Awareness</strong><br> By keeping track of state and re-evaluating as they go, agents can pivot mid-task if new information changes priorities.</li></ol><h3>Why Use AWS Strands to Build Agents?</h3><p>While you can build agents with various frameworks, <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">AWS Strands</a> offers distinct advantages for developers:</p><ul><li><strong>Simplicity</strong>: Build functional agents with minimal code. No complex framework setup or boilerplate required.</li><li><strong>AWS-Native</strong>: If you’re already running in AWS, you can easily leverage native services like Bedrock, Lambda, and CloudWatch.</li><li><strong>Extensibility</strong>: Easily integrate custom tools and services. The tool decorator pattern makes adding new capabilities as simple as writing a function.</li><li><strong>Scalability</strong>: Deploy the same agent code from local development to production-grade infrastructure including AWS Lambda and Fargate.</li><li><strong>Observability</strong>: Built-in support for tracing, logging, and monitoring agent performance via OpenTelemetry (and X-Ray if you’re running in AWS.)</li></ul><h3>Getting Started: Step-by-Step Guide</h3><h3>Prerequisites</h3><p>Before diving in, ensure you have the following:</p><ul><li><strong>Python 3.10 or higher</strong>: Strands requires Python 3.10+.</li><li><strong>AWS CLI + AWS Account with Bedrock Access</strong></li><li>Make sure your account has enabled access to the desired models via Amazon Bedrock. Strands will use Claude Sonnet 4 in us-west-2 by default.</li><li>See <a href="https://strandsagents.com/latest/documentation/docs/">https://strandsagents.com/latest/documentation/docs/</a> for details.</li></ul><h3>Environment Setup</h3><p>You’re welcome to do this in an IDE, but I’ll outline the steps explicitly via command line to keep it as simple as possible.</p><p><strong>Create a Project Directory</strong>:</p><pre>mkdir strands_agent_demo<br>cd strands_agent_demo</pre><p><strong>Set Up a Python Virtual Environment</strong>:</p><pre>python3 -m venv venvsource venv/bin/activate</pre><p><strong>Install Strands Agents SDK</strong>:</p><pre>pip install strands-agents strands-agents-tools</pre><h3>Create Your First Agent</h3><h4>Create a Python File:</h4><pre>touch agent.py</pre><h4>Add the Following Code:</h4><pre>from strands import Agent<br># Create an agent with default settings<br>agent = Agent()<br><br>while True:<br>    # Get user input<br>    user_input = input(&quot;\nYour question: &quot;)<br>    if user_input.lower() in [&quot;exit&quot;, &quot;quit&quot;, &quot;bye&quot;]:<br>        print(&quot;Goodbye!&quot;)<br>        break<br>    # Send the input to the agent, which will automatically print the response<br>    print(&quot;\nThinking...\n&quot;)<br>    agent(user_input)</pre><h4>Run the Agent:</h4><pre>python agent.py</pre><p>Upon execution, the agent will ask for your input and process your query using the default model.</p><p>Try a question that’s difficult for LLMs to answer, like long division: “What is 123,456,789 divided by 123?” Notice you’ll get an answer that’s close, but not quite correct.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/851/1*Jt8OkM2SZxVN0bBVr9g7hg.png" /></figure><h3>Add a tool that the agent can use as needed</h3><pre>from strands import Agent, tool<br><br>@tool<br>def calculator(expression: str) -&gt; str:<br>    &quot;&quot;&quot;Evaluates simple math expressions.<br>    This docstring is used by the LLM to understand the tool&#39;s purpose.<br>    &quot;&quot;&quot;<br>    result = eval(expression)<br>    return f&quot;The result is: {result}&quot;<br># Create an agent and add the calculator tool<br>agent = Agent(tools=[calculator])<br>while True:<br>    # Get user input<br>    user_input = input(&quot;\nYour question: &quot;)<br>    if user_input.lower() in [&quot;exit&quot;, &quot;quit&quot;, &quot;bye&quot;]:<br>        print(&quot;Goodbye!&quot;)<br>        break<br>    # Send the input to the agent, which will automatically print the response<br>    print(&quot;\nThinking...\n&quot;)<br>    agent(user_input)</pre><p>Run the agent again, and ask it what tools it has access to:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/985/1*omptA1hm4yF97PjcDMIu9g.png" /></figure><p>Now try the math question again. Watch as the agent determines that it should use the tool to solve the problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/967/1*WUZYpPbIN2Pv1Q3atNOgmw.png" /></figure><h3>Try some of Strands’ built-in tools</h3><p>Reference <a href="https://github.com/strands-agents/tools">full list</a> of provided tools. Examples:</p><ul><li>Want your agent to be able to execute python code? Import the tool and add it to your agent:</li></ul><pre>from strands_tools import python_repl<br>agent = Agent(tools=[calculator, python_repl])</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*fwQBOYrtm0t-86gxT9A4lw.png" /></figure><ul><li>How about general command line capabilities? Use the shell tool:</li></ul><pre>from strands_tools import shell<br>agent = Agent(tools=[calculator, shell])</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/774/1*kNUK6K0MR8Znn_97QD9xpg.png" /></figure><h3>See a Pattern?</h3><p>You’re <strong>not defining explicit logic</strong> to enable new capabilities, you’re only providing the agent with additional tools, and letting it reason on how to use them to meet the goal you provide in your prompt.</p><h3>Next Steps</h3><p>With your first agent up and running, consider exploring some more advanced patterns:</p><ul><li>Want to expose an agent as a tool? Just wrap it in a function with a tool decorator, and add that tool to your primary agent. This is a common method of <em>context engineering</em> — keeping your primary agent’s context window focused on what’s important to it, and delegating sub-tasks to sub-agents.</li></ul><pre>CODE_AGENT_PROMPT = &quot;&quot;&quot;<br>You are an agent that writes Python code using best practices in Python.<br>Only output the Python code and nothing else.<br>&quot;&quot;&quot;<br><br>@tool<br>def code_generator(task: str) -&gt; str:<br> “””<br> Generates Python code from a task description<br> Args:<br> task: Given task by user<br> Returns:<br> Generated Python code<br> “””<br> try:<br>   agent = Agent(system_prompt=CODE_AGENT_PROMPT)<br>   result = str(agent(f”Complete the task: {task}”)).strip()<br>   return result or “Could not generate code. Please provide more context.”<br> except Exception as e:<br>   return f”Error generating code for task ‘{task}’: {e}”</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*2Q0F-zUFY-s7RI5gJbK00A.png" /></figure><ul><li>Want to use an MCP server? It’s very straightforward to initialize the MCP client, fetch the tools, and add them to the agent. In this example, we add all the tools to the agent, but you can also pick and choose only those that you need (don’t forget, each MCP tool consumes tokens in your context.)</li></ul><pre>from mcp import stdio_client, StdioServerParameters<br>from strands.tools.mcp import MCPClient<br><br># Connect to an MCP server using stdio transport<br>your_mcp_client = MCPClient(lambda: stdio_client(<br> StdioServerParameters(command=&quot;uv&quot;, args=[<br> &quot; - directory&quot;,<br> &quot;/directory_your_mcp_server_lives_in&quot;,<br> &quot;run&quot;,<br> &quot;name_of_server&quot;])))<br> <br># Create an agent with MCP tools<br>with your_mcp_client:<br> # Get the tools from the MCP server<br> tools = your_mcp_client.list_tools_sync()<br> <br> # Create an agent with these tools<br> agent = Agent(tools=tools)</pre><h3>Additional Resources:</h3><ul><li><a href="https://strandsagents.com/0.1.x/examples/">Strands Agents Examples</a></li><li><a href="https://strandsagents.com/0.1.x/user-guide/quickstart/">Quickstart Guide</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c302e03bf738" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting Started with MCP (Model Context Protocol)]]></title>
            <link>https://matt-harper.medium.com/getting-started-with-mcp-model-context-protocol-ded288ef3ae7?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/ded288ef3ae7</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[mcp-server]]></category>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Thu, 10 Apr 2025 15:44:51 GMT</pubDate>
            <atom:updated>2025-04-10T15:44:51.904Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mM5jZn31RWaGcYAphs_Ayw.png" /></figure><h3>What is MCP, and why should you care?</h3><p>If you’re a software engineer trying to integrate generative AI into your workflow, you’ve probably experienced the frustration of constantly switching between tools like Claude, Word or Docs, local files, your IDE, and Google — endlessly copying and pasting prompts and outputs along the way. With new AI tools popping up daily, it’s tough to know which ones genuinely simplify your workflow.</p><p>Enter <strong>Model Context Protocol (MCP)</strong>.</p><p>MCP simplifies how your applications interact with large language models (LLMs) by providing a standardized way to manage prompts, context, and responses. Think of MCP like REST for language models: it offers a single, consistent way to communicate with any LLM, abstracting away complexity so you can spend less time context-switching and more time coding.</p><p>Here’s an example to illustrate the value. Let’s say you have workflow where you:</p><ul><li>Use Claude to help break down a large project into incremental stories</li><li>Take each story and add it to a JIRA project</li><li>Search a wiki for relevant context for each task and append it to the JIRA story.</li></ul><p>Between each step there is manual effort. Without MCP, you could try to string this all together programmatically, but you’ll need to manage multiple API integrations and write a lot of code.</p><p>With MCP, you can expose an MCP server that wraps integration with these tools, and then use plain text prompts like the workflow above and the LLM will leverage the tool integrations naturally during the conversation. Let’s get hands-on and quickly build an MCP to get a better understanding of how this works.</p><h3>Build an MCP Server in 5 minutes</h3><p><strong>Prerequisites</strong> (running on macOS):</p><p><strong>Install </strong><a href="https://docs.astral.sh/uv/getting-started/installation/"><strong>uv</strong></a> (Python package and project manager)</p><ul><li><em>brew install uv</em> (preferred, requires <a href="https://brew.sh/">homebrew</a>)</li><li>If you don’t use homebrew, you can run the following command to install uv, but I had trouble getting the Claude Desktop App to launch the MCP server with this method: <em>curl -LsSf </em><a href="https://astral.sh/uv/install.sh"><em>https://astral.sh/uv/install.sh</em></a><em> | sh</em></li></ul><p><strong>Install Python</strong> (minimum version 3.10, I’m using 3.12)</p><ul><li><em>uv install python</em></li></ul><p>Open your favorite terminal app and follow the commands below to create a new python project and add the <a href="https://github.com/modelcontextprotocol/python-sdk">official python MCP SDK</a> as a dependency.</p><pre># navigate to your preferred working directory<br>cd ~/workplace<br><br># create the project<br>uv init mcp_demo<br>cd mcp_demo<br><br># add the MCP package as a project dependency<br>uv add &quot;mcp[cli]&quot;</pre><p>We’ll build a simple server to enable an LLM to access a local text file on your machine and append ‘todo’ items.</p><p>Open main.py, delete the contents, and replace with the below code. Feel free to edit the hard-coded filepath to your liking:</p><pre>from mcp.server.fastmcp import FastMCP<br><br># Create an MCP server<br>mcp = FastMCP(&quot;Demo&quot;)<br><br><br># Add a tool to append TODO items to a local file<br>@mcp.tool()<br>def append_todo(todo_item: str) -&gt; str:<br>    &quot;&quot;&quot;Append a TODO item to a local markdown file.&quot;&quot;&quot;<br>    filepath = &quot;/Volumes/workplace/mcp_demo/todos.md&quot;<br>    try:<br>        with open(filepath, &quot;a&quot;) as file:<br>            file.write(f&quot;- [ ] {todo_item}\n&quot;)<br>        return f&quot;Successfully appended TODO: &#39;{todo_item}&#39;&quot;<br>    except Exception as e:<br>        return f&quot;Failed to append TODO due to: {e}&quot;</pre><p>From the same directory in your terminal (~/workplace/mcp_demo in my case), run <em>mcp dev run main.py </em>to launch the official <a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector</a> app (this is a great tool that simplifies testing MCP servers.) Navigate to <a href="http://localhost:6247/">http://localhost:6247</a> in your browser, and you should see this screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HPA4cljfFLCmEjgNMvHxLQ.png" /></figure><p>Click <strong>Connect, </strong>then <strong>Tools</strong>, then <strong>List Tools. </strong>You should see the append_todo tool that we created; click on it, and you’ll get the option to enter text and run the tool. Give it a try by entering some text and cliking ‘Run Tool’</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vVwQJfL3pcejWwdFEMIx7Q.png" /></figure><p>You should see a successful response:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bkTp8aYt8_4FHfrak1oUbA.png" /></figure><p>And as you invoke the tool to add notes, you should see them appear in your local file system:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eo4MfRUGSuDIWi20RuqK0g.png" /></figure><p>Next, we’ll use this server in a more natural integration with Claude.</p><h3>Integrate your server with the Claude Desktop App</h3><p>If you don’t have it, install the Anthropic Claude <a href="https://support.anthropic.com/en/articles/10065433-installing-claude-for-desktop">desktop app</a> and use the menu bar to navigate to Settings -&gt; Developer:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9c6eNsNusq2nT2QQZI3LNg.png" /></figure><p>Click the Edit Config button to view the empty MCP server config file:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XkZP6JMbJ5dCigY-z22DRA.png" /></figure><p>Back in your terminal, still operating within your project directory, run <em>mcp install main.py </em>— this<em> </em>will update the Claude MCP configuration to include your server.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kMtkLXck5Cck2qNr0nOvgw.png" /></figure><p>View the updated configuration; this tells Claude where your server is, and how to launch it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PUcIWCicanZnvtSx4zZtrA.png" /></figure><p>Restart the Claude Desktop App, and you should now see a hammer icon; hover over it to see your newly available MCP tool. Click on it for more details.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HsvAZDcGECkHKHbc_evpwQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1016/1*GrN6RwZtO0JmJhJ-ATJvRQ.png" /></figure><p>Now let’s see it in action:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f7cU8Sa45Y_W4_oeXpbdew.png" /></figure><p>Note how you don’t have to explicitly reference MCP or tool use; Claude gets the description of the tools available from the MCP server, and naturally uses them as part of the conversation.</p><h3>Next Steps</h3><p>Think about what sites, APIs, or tools you’d like your LLM to be able to interact with on your behalf. Experiment with building tools for your personal use.</p><p>If there’s a use case you are interested in integrating your LLM with, like interacting with your <a href="https://github.blog/changelog/2025-04-04-github-mcp-server-public-preview/">GitHub</a> repos, check the internet. There may already be an official MCP server provided by that company, or a popular open source server. Make certain to vet that the MCP servers you use are secure, especially before feeding any confidential or personal information to them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ded288ef3ae7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 Ways to Protect S3 Data from Deletes and Overwrites]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://matt-harper.medium.com/3-ways-to-protect-s3-data-from-deletes-and-overwrites-ad97756e3cd?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/800/1*cUj1imK5S9u0mPtzwAqDBA.png" width="800"></a></p><p class="medium-feed-snippet">A concise review of methods to ensure objects are not accidentally deleted or overwritten in an S3 bucket.</p><p class="medium-feed-link"><a href="https://matt-harper.medium.com/3-ways-to-protect-s3-data-from-deletes-and-overwrites-ad97756e3cd?source=rss-a9fb260ed68f------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://matt-harper.medium.com/3-ways-to-protect-s3-data-from-deletes-and-overwrites-ad97756e3cd?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/ad97756e3cd</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[s3]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Mon, 25 Jan 2021 11:53:47 GMT</pubDate>
            <atom:updated>2021-01-25T11:53:47.192Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Getting Started with AWS Big Data — Athena, Glue, and S3]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-aws-big-data-athena-glue-and-s3-ef52f9b68d60?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/601/1*iKXB7X54a23RLt-SD-zGmA.png" width="601"></a></p><p class="medium-feed-snippet">A step-by-step tutorial to quickly build and test a data analytics service in AWS using S3, Glue, and Athena.</p><p class="medium-feed-link"><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-aws-big-data-athena-glue-and-s3-ef52f9b68d60?source=rss-a9fb260ed68f------2">Continue reading on Trimble Maps Engineering Blog »</a></p></div>]]></description>
            <link>https://medium.com/trimble-maps-engineering-blog/getting-started-with-aws-big-data-athena-glue-and-s3-ef52f9b68d60?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/ef52f9b68d60</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[data-analytics]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Tue, 14 Apr 2020 12:24:10 GMT</pubDate>
            <atom:updated>2020-04-15T13:22:40.046Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Protect your Infrastructure with Real-time Notifications of AWS Console User Changes]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-science/protect-your-infrastructure-with-real-time-notifications-of-aws-console-user-changes-3144fd18c680?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/611/1*0OxbHBLx_M6ujVqIV9yNyA.png" width="611"></a></p><p class="medium-feed-snippet">Step by step tutorial combining CloudTrail, Lambda, and SNS to get alerts only when a user manually makes a change in the AWS Console.</p><p class="medium-feed-link"><a href="https://medium.com/data-science/protect-your-infrastructure-with-real-time-notifications-of-aws-console-user-changes-3144fd18c680?source=rss-a9fb260ed68f------2">Continue reading on TDS Archive »</a></p></div>]]></description>
            <link>https://medium.com/data-science/protect-your-infrastructure-with-real-time-notifications-of-aws-console-user-changes-3144fd18c680?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/3144fd18c680</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[cloudtrail]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Thu, 20 Feb 2020 14:47:28 GMT</pubDate>
            <atom:updated>2021-11-16T17:57:57.299Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Getting Started with DynamoDB Streams and AWS Lambda in .NET Core]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-science/how-to-use-dynamodb-streams-with-aws-lambda-and-net-core-62db43ae98c1?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/749/1*E9-aRdJ6NwaiNp8o3tR-Mg.png" width="749"></a></p><p class="medium-feed-snippet">A step by step tutorial on consuming a DynamoDB Stream using AWS Lambda in C#</p><p class="medium-feed-link"><a href="https://medium.com/data-science/how-to-use-dynamodb-streams-with-aws-lambda-and-net-core-62db43ae98c1?source=rss-a9fb260ed68f------2">Continue reading on TDS Archive »</a></p></div>]]></description>
            <link>https://medium.com/data-science/how-to-use-dynamodb-streams-with-aws-lambda-and-net-core-62db43ae98c1?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/62db43ae98c1</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[lambda]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Sun, 02 Feb 2020 16:38:40 GMT</pubDate>
            <atom:updated>2020-02-09T02:22:43.469Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Getting Started with DynamoDB and .NET Core — How to Build a Leaderboard]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-dynamodb-and-net-core-how-to-build-a-leaderboard-4335f2bd56a8?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/780/1*RGAPxe3MOY-UTRxkPjBVuA.png" width="780"></a></p><p class="medium-feed-snippet">Learn how to use DynamoDB (and it&#x2019;s keys and indexes) efficiently in this step by step guide with source code in C# and included Terraform.</p><p class="medium-feed-link"><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-dynamodb-and-net-core-how-to-build-a-leaderboard-4335f2bd56a8?source=rss-a9fb260ed68f------2">Continue reading on Trimble Maps Engineering Blog »</a></p></div>]]></description>
            <link>https://medium.com/trimble-maps-engineering-blog/getting-started-with-dynamodb-and-net-core-how-to-build-a-leaderboard-4335f2bd56a8?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/4335f2bd56a8</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[dynamodb]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Tue, 21 Jan 2020 13:06:04 GMT</pubDate>
            <atom:updated>2020-05-12T20:07:13.693Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to Build an Alexa Skill with .NET Core and AWS Lambda]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/trimble-maps-engineering-blog/how-to-build-an-alexa-skill-with-net-core-and-aws-lambda-fe41903dad9f?source=rss-a9fb260ed68f------2"><img src="https://cdn-images-1.medium.com/max/1894/1*gLljqQQFMYIeJ-9grgkq3w.png" width="1894"></a></p><p class="medium-feed-snippet">Use this step-by-step guide to easily build your first Alexa Skill with a serverless C# backend running in AWS Lambda.</p><p class="medium-feed-link"><a href="https://medium.com/trimble-maps-engineering-blog/how-to-build-an-alexa-skill-with-net-core-and-aws-lambda-fe41903dad9f?source=rss-a9fb260ed68f------2">Continue reading on Trimble Maps Engineering Blog »</a></p></div>]]></description>
            <link>https://medium.com/trimble-maps-engineering-blog/how-to-build-an-alexa-skill-with-net-core-and-aws-lambda-fe41903dad9f?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/fe41903dad9f</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[alexa]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Wed, 08 Jan 2020 14:52:18 GMT</pubDate>
            <atom:updated>2020-02-09T12:00:10.521Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Getting Started with .NET Core, Docker, and RabbitMQ — Part 3]]></title>
            <link>https://medium.com/trimble-maps-engineering-blog/getting-started-with-net-core-docker-and-rabbitmq-part-3-66305dc50ccf?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/66305dc50ccf</guid>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[rabbitmq]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Fri, 25 Oct 2019 12:49:38 GMT</pubDate>
            <atom:updated>2019-10-28T14:11:52.770Z</atom:updated>
            <content:encoded><![CDATA[<h3>Getting Started with .NET Core, Docker, and RabbitMQ — Part 3</h3><p>Picking up from <a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-net-core-docker-and-rabbitmq-part-2-e41a0961292a?source=friends_link&amp;sk=7d1626a361673056b87f3e1020640a7d">Part2</a>, we’re going add the final component of our application — a message queue. The specifics of this example will seem bit contrived, but the goal is to decouple our publisher and worker, use Docker to launch a RabbitMQ container, and use Docker Compose to orchestrate the system. And of course, go line by line through the changes required to accomplish these tasks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/820/0*ImGhrZDIxPR7kNBJ.png" /><figcaption><a href="https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html">https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html</a></figcaption></figure><p><strong>Why use a message queue?</strong></p><p>Microservices are all the rage in today’s distributed systems. But an unfortunate side effect of breaking down monolithic applications into multiple single-purpose services is high coupling between the new services. What appears to a client as a single RESTful API call may actually become many calls between interdependent services behind the scenes. If one critical service goes down, the negative effects (latency and more failures) will quickly be felt throughout a highly coupled system.</p><p>This is where message queues come into play. Instead of direct HTTP connections between services, we introduce a middleman. Services can publish messages to a queue, and consumers will pull messages from that queue and process them. This can allow asynchronous communication for long-running tasks (enqueue and forget), or the caller can enqueue the message and wait for a consumer to return a response (<a href="https://www.rabbitmq.com/tutorials/tutorial-six-python.html">Remote Procedure Call pattern</a>). Either way, we’ve decoupled the two services, in the sense that if one goes down, the message still persists in the queue, and as long as we’ve scaled horizontally (multiple copies of the services) on both sides of the queue, we don’t have a single point of failure (besides the queue itself, but there are ways to ensure the queue is highly available).</p><p>Let’s write some code!</p><p>We finished Part2 of this series with two applications running in Docker containers and orchestrated with Docker-Compose. Our ‘worker’ app calls our ‘publisher_api’ app via RESTful web service, and prints the response. Our next goal is to modify the program so that when we post a message from the worker to the publisher, instead of receiving an immediate response, we’ll simulate a situation where the server needs more time to process the request. The worker will not wait synchronously for the response to each request; it will fire off messages, and then poll our message queue awaiting responses.</p><p>Hopefully you’ve completed part 2 of the tutorial so we can pick up right where we left off. If you need the code for a starting point, look for the v2.0 tag in the <a href="https://github.com/matthew-harper/dotnet-docker-rabbitmq-tutorial">Github </a>repo.</p><p>To get started, we’re going to add a rabbitMQ container to our project by modifying docker-compose.yml. Here’s the updated file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7b2091abb550fd3046f556fc6a617a29/href">https://medium.com/media/7b2091abb550fd3046f556fc6a617a29/href</a></iframe><p>What we’ve done above is tell docker-compose to add a new service to our application, named rabbitmq. It pulls an existing image (rabbitmq:3-management) from the public Docker repository.; this image includes the message queue and the management console that you can access in a web browser. We’ve also exposed the ports RabbitMQ operates on and told our other services to always restart on shutdown or failure. Docker-compose doesn’t manage the startup order of services terribly well, so we’ll just keep restarting our worker/publisher until the queue is in place.</p><p>Next, we want to modify our code to use the message queue. Let’s import the NuGet package for the RabbitMQ client. Navigate to the publisher_api directory in a command prompt, and run the following command:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c6924bd74633f7af67e2ee6e6d498ae0/href">https://medium.com/media/c6924bd74633f7af67e2ee6e6d498ae0/href</a></iframe><p>Now we will create a class to post messages to RabbitMQ, Create a new folder inside the publisher_api directory called services, and a new file there called MessageService.cs. Here’s the file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8e5638084c76d82f66acc23ece1c620b/href">https://medium.com/media/8e5638084c76d82f66acc23ece1c620b/href</a></iframe><p>Let’s walk through the code. You’ll first note that we import the RabbitMQ.Client package, and that we define a namespace to hold our project’s services (publisher_api.Services). Next, we declare an interface (IMessageService)that our new class (MessageService) implements. Interfaces are valuable in .NET Core because they make it easier to create unit tests against your classes, and because they allow other classes in your application to depend on abstractions rather than concrete types (more about this when we discuss dependency injection later.)</p><p>Our actual MessageService class only implements a single public method — Enqueue. But there’s some setup that takes place in the constructor. We create a ConnectionFactory object to establish and return a connection to the RabbitMQ server. Then we use a channel to send and receive messages over that connection. The end result is that we invoke the QueueDeclare method on our channel — this will create a queue named “hello” if one does not already exist, and establish a connection to it.</p><p>In our Enqueue method, we create the string we want to return to our client (“server processed” plus the original message), and invoke BasicPublish on the channel. We’re not specifying an exchange, so we leave that parameter as an empty string, and our routingKey is the name of the queue we’re sending the message to (“hello”.)</p><p>Now, we want to modify the POST handler in ValuesController.cs to publish to our message queue. But how do we access MessageService from ValuesController? We don’t want to create a new instance of MessageService every time a POST request comes in — that’s not going to scale well. This is where we need Dependency Injection. In short, Dependency Injection is a pattern that allows you to decouple pieces of your application and helps us achieve the Dependency Inversion Principle (higher-level modules should not depend on lower-level modules, and modules should depend on abstractions rather than on concrete details.) My colleague Chris Tanghere has put together a <a href="https://medium.com/trimble-maps-engineering-blog/dependency-injection-whats-the-point-a8816fe4f68a?source=friends_link&amp;sk=6068058ca1763f617ae99d45b1fa18b6">deep dive on Dependency Injection</a> if you want more info.</p><p>To configure our application to access MessageService via Dependency Injection, we have to modify Startup.cs. Add a using directive to reference Publisher_api.Services to the top of the file. Then look for the ConfigureServices method, and add the following line to the end of the method, after AddMvc is invoked:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b028b8274f83a57e77ef3d694728f10/href">https://medium.com/media/9b028b8274f83a57e77ef3d694728f10/href</a></iframe><p>What we’re doing is basically telling our program that if anyone requests an instance of type IMessageService, they will be given a reference to a singleton MessageService object. This means our MessageService class will only be instantiated once, no matter how many times it is referenced.</p><p>Now let’s modify ValuesController.cs to take advantage of Dependency Injection and enqueue our message via our MessageService. Again, add the using directive for publisher_api.Services to the top of the file. Then, modify the class and it’s constructor to inject an instance of IMessageService, and store a reference to it in a private member variable:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dd678749bb6027db9e26524d57d9a7bb/href">https://medium.com/media/dd678749bb6027db9e26524d57d9a7bb/href</a></iframe><p>All we have to do now is modify the POST method to invoke Enqueue on our MessageService member variable:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b170b6397f717f607422660a99248029/href">https://medium.com/media/b170b6397f717f607422660a99248029/href</a></iframe><p>Publisher_api is complete. If you want to test the application now, you can run “docker-compose up — build” from our solution directory, and you should see output similar to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*umwjB5_9Isk8bXx2_AZpAQ.png" /></figure><p>The key items there are:</p><ul><li>rabbitmq_1 container completes initialization successfully</li><li>worker posts a message to the publisher</li><li>publisher_api receives a message and publishes it to RabbitMQ</li></ul><p>Once we verify that works, let’s kill the docker-compose process (Ctrl-C) and modify the worker to poll our message queue for responses. We’ll need to import the NuGet package for the RabbitMQ client, just as we did for the publisher_api. Navigate to the worker directory in a command prompt, and run the following command:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c6924bd74633f7af67e2ee6e6d498ae0/href">https://medium.com/media/c6924bd74633f7af67e2ee6e6d498ae0/href</a></iframe><p>Now let’s get into the code. In worker/Program.cs, add a couple of using directives from the RabbitMQ package:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5912de964ce20d8bc09249f3e18af85d/href">https://medium.com/media/5912de964ce20d8bc09249f3e18af85d/href</a></iframe><p>Our PostMessage method will remain the same, we’ll just modify the Main method to post 5 messages, then listen to our MessageQueue and await the responses:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/853889f22c26ca51e1f55fb3b7fd1e6b/href">https://medium.com/media/853889f22c26ca51e1f55fb3b7fd1e6b/href</a></iframe><p>The Wait() call at the start of the function is a hack to give the RabbitMQ container more time to initialize before we start sending messages. Then we loop, sending each of our test strings to the publisher_api. After that, it’s time to poll the message queue and retrieve the responses.</p><p>First, we set up a connection to the queue, in the same fashion that we did for the publisher_api. The major difference is that we set up an EventingBasicConsumer object. This class exposes RabbitMQ’s message handling functions as events. We add a handler to the ‘Received’ event where we retrieve the message body and print it to the console. Then we call the BasicConsume method on our channel object, supplying the name of the queue to poll and the EventingBasicConsumer object we just created.</p><p>Head back to the cmd prompt and docker-compose again (don’t forget the build flag to ensure your containers include the code changes you just made.) Your output should be similar to:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3mhRaIHfxYhNolk0g8l0Mg.png" /></figure><p>You should see the publisher_api receive five POSTs, and the worker consume the message queue, printing out five messages it retrieved. Another way you can verify the queue is functional is to visit the RabbitMQ Management Console by visiting localhost:15672 in your web browser. This site will only be accessible while your docker-compose is running because it is hosted in the RabbitMQ container.</p><p>Once you login to the site with name: guest and password: guest (defined when you created the queue in MessageService.cs), you should see some statistics. The clearest indication of activity is the message rates graph; as you can see below, there was a small spike in activity when our worker posted the five messages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/444/1*Dlje1UOs2m-OkYfNWXu0Pg.png" /></figure><p>That’s the end of this series — to recap, we built .NET core applications, containerized them, decoupled them using RabbitMQ, and used docker-compose to orchestrate all the pieces of this project. You can find all of the code in the <a href="https://github.com/matthew-harper/dotnet-docker-rabbitmq-tutorial">Github</a> repo. Please share any feedback, and also any ideas you’d like to see explored in future articles. Thanks!</p><p><em>Interested in joining our team at Trimble Maps? Click </em><a href="https://maps.trimble.com/careers/"><em>here</em></a><em>!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66305dc50ccf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-net-core-docker-and-rabbitmq-part-3-66305dc50ccf">Getting Started with .NET Core, Docker, and RabbitMQ — Part 3</a> was originally published in <a href="https://medium.com/trimble-maps-engineering-blog">Trimble Maps Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting Started with .NET Core, Docker, and RabbitMQ — Part 2]]></title>
            <link>https://medium.com/trimble-maps-engineering-blog/getting-started-with-net-core-docker-and-rabbitmq-part-2-e41a0961292a?source=rss-a9fb260ed68f------2</link>
            <guid isPermaLink="false">https://medium.com/p/e41a0961292a</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Matthew Harper]]></dc:creator>
            <pubDate>Fri, 09 Aug 2019 14:06:34 GMT</pubDate>
            <atom:updated>2019-08-09T15:26:00.777Z</atom:updated>
            <content:encoded><![CDATA[<h3>Getting Started with .NET Core, Docker, and RabbitMQ — Part 2</h3><p>Picking up from <a href="https://medium.com/@TrimbleMAPS.techblog/getting-started-with-net-core-docker-and-rabbitmq-part-1-10e246d4e7f">Part1</a>, we’re going to be containerizing the application we’ve built. Containerization is an approach where an application and its dependencies are packaged together and and run in an isolated environment. A VM (or physical machine) runs the container host, instead of running our applications directly. The container host, in turn, runs our containers. Each container is isolated from the others, and can even run a different operating system than the VM.</p><p>In this project, we’ll be using Docker, an open-source project for automating the deployment of applications as portable, self sufficient containers that can run almost anywhere. Before we start, let’s walk through some of the benefits of containerization.</p><p><strong>Consistency &amp; Portability</strong></p><p>How do you replicate the software you’ve built on your local machine somewhere else, perhaps on another physical machine or on a virtual machine in the cloud? Are all the run time dependencies (including any specific version requirements) in place? When dependencies change, how do you apply that change across all relevant machines and environments? A frequent symptom of this problem is when you hear “it works fine on my machine” or “why does it work in QA but not in Production?”</p><p>Once a container is created, you can run it basically anywhere and it will behave the same way. Because the application and all of its dependencies (including the OS) are encapsulated within the container, there will be no difference in execution between your local machine, another physical machine, or a virtual machine in the cloud. This approach also helps to simplify deployments.</p><p><strong>Isolation &amp; Efficiency</strong></p><p>Another common problem is VM utilization. To maximize efficiency and minimize cost, you may want to host multiple apps on a single VM. In that scenario, there is no logical boundary between the apps. Containers isolate each application, preventing dependency conflicts. Containers also allow resource limits to be set for each service, preventing a service on a shared VM from consuming all available RAM and starving other critical services. These silos provide more security because your applications aren’t running within a shared operating system.</p><p>A container also requires fewer resources than a virtual machine (containers don’t have any direct interface with hardware, for example). This enables them to launch faster, and allows your application scale quickly in response to heavy traffic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*s2fzClKxJykdsQyA" /><figcaption><a href="https://blog.docker.com/2018/08/containers-replacing-virtual-machines/">https://blog.docker.com/2018/08/containers-replacing-virtual-machines/</a></figcaption></figure><p>We’re going to modify our project to run the webAPI and console app within individual containers, and then use a tool called Docker-Compose to launch them together. Hopefully you’ve completed part 1 of the tutorial so we can pick up right where we left off. If you need the code for a starting point, look for the v1.0 tag in the <a href="https://github.com/matthew-harper/dotnet-docker-rabbitmq-tutorial">Github </a>repo. The only additional software you need installed is Docker Community Edition. I’m working on a Windows machine, so that’s <a href="https://docs.docker.com/docker-for-windows/install/">Docker Desktop for Windows</a>. Follow the installation guide and then we can get started.</p><p>The first step in introducing Docker to our project is to add a Dockerfile. A Dockerfile is a text file that contains all of the commands that will be executed to create a Docker image. An image is basically a package with all the dependencies and information needed to create a container. Images are composed of layers; you typically start with an operating system (maybe your container will run linux or windows server core), then install dependencies (such as the .NET Core SDK), and then install your application.</p><p>Let’s start with the publisher_api project. Add new file called Dockerfile (no file extension) to the publisher_api directory (publisher_api/Dockerfile). You can copy the content below, and then we’ll get into what each line is doing.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/62a5746b1e28b30a7ddd1894f956bea9/href">https://medium.com/media/62a5746b1e28b30a7ddd1894f956bea9/href</a></iframe><p>The first line declares the base image we’re building our new image from; in this case, it contains the .NET Core SDK and is optimized for local development and debugging. Line 2 declares our working directory within the container (future COPY and RUN commands will execute within this directory).</p><p>Lines 5 and 6 copy our project file into the WORKDIR, and then execute a dotnet restore to pull down required dependencies. Lines 9 and 10 copy the rest of the files in our project directory into the WORKDIR, and then actually build the application.</p><p>On line 13, it looks like we start over, building an image from a different base image. What we’re actually doing here is taking advantage of a Docker feature called multistage builds. The short of it is that we want to build and run our application in a container, but building the app requires a lot more overhead than running it. So what happens here is that when we declare another FROM statement in our Dockerfile, we’re starting the next stage of our build from a clean image. But we still have access to all of the artifacts from the previous stage. So the final image will only contain the results of the last stage of the build.</p><p>So getting back to line 13, we‘re creating a clean image from Microsoft&#39;s .NET core ‘runtime’ image (it’s leaner than the image we used for the first stage of the build). Line 15 declares our working directory in this image, and line 17 copies the binaries from our build image into this new image.</p><p>Line 19 declares the ENTRYPOINT for the container, which basically allows the container to run like an executable; when the container launches, it will launch the process declared here.</p><p>That’s all we need to containerize our publisher_api application. What we’ve done is actually more complex than the most basic example we could create, because we are using a multistage build. Let’s test it out!</p><p>Navigate to the publisher_api directory in a command prompt, and run the following command:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dc9773a8258273ff0d21fcf1f91b56c5/href">https://medium.com/media/dc9773a8258273ff0d21fcf1f91b56c5/href</a></iframe><p>You should see output similar to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/687/1*7YGQ_t_HYQZpnhWPe8pEow.png" /></figure><p>You can use <strong>docker image ls</strong> to view your new image:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/834/1*cqBR-h36O9RDRWWu13Jeeg.png" /></figure><p>And <strong>docker run my_publisher_api</strong> to start the container:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*CLYkrqcSK_R8V8zNKW9fDw.png" /></figure><p>Next, we’ll follow the same exact steps for the console app (adding a Dockerfile, building the image, and running the container.) The only difference in the new Dockerfile is the name of the DLL in the entrypoint:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b9b6d93fb93ecb4c99737615b9f83db5/href">https://medium.com/media/b9b6d93fb93ecb4c99737615b9f83db5/href</a></iframe><p>Navigate to the worker directory in a command prompt, and run the command to build the image:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a31670f12a46eeb5b73e515bfbcacf3f/href">https://medium.com/media/a31670f12a46eeb5b73e515bfbcacf3f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/665/1*PEU7vxUSraDRZAEwYVFTaA.png" /></figure><p>And go ahead and test it with <strong>docker run my_worker </strong>to launch the container:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/842/1*L8VYUJMTJ5gS1poTwXGOsw.png" /></figure><p>Our container launched as — but there’s an error. Our container ran as we expected, but since we’re not running the publisher_api, our POST request fails. Luckily, there is a way we easily run both of our containers at the same time; using a tool is called Docker-compose.</p><p>Docker-compose is basically orchestration for containers. It allows you to define and run multi-container applications. Create a docker-compose.yml file in our root directory (at same level as our worker and publisher_api folders.) The contents of our implementation are below; copy them into your file and then we will get into the details.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/df7a91189d91bd7676f31ebb027d20bf/href">https://medium.com/media/df7a91189d91bd7676f31ebb027d20bf/href</a></iframe><p>First we define the version of docker-compose that we’ll use (3 is the latest major version.) Then we declare our services; a service is basically a single container, encapsulating an isolated piece of our application. We have two services - one for our webAPI and one for our console app.</p><p>The first service, which we’ve named publisher_api (the same as the project, although that’s not required), is pretty straightforward. All we have to do is specify the build context, which is the directory where the Dockerfile for that project can be found.</p><p>The other service is exactly the same, except we express the dependency between our services using <em>depends-on. </em>We don’t want the worker to start up until the publisher_api is running.</p><p>Our docker is complete. However, if we run our app, we’ll still see an error. Try it by executing the following command from the root directory of our project. The <em>build </em>flag specifies that we want to rebuild our containers to pick up the latest changes to our code or configuration.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dec16ae0ed5cf3fb3bc205357ce79f36/href">https://medium.com/media/dec16ae0ed5cf3fb3bc205357ce79f36/href</a></iframe><p>You should see output similar to the below image. Our containers are built, the publisher_api launches and start listening for HTTP requests, the worker launches and tries to post a message but fails with an error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4WHgXrBNEYzhHON6F7HxXg.png" /></figure><p>Currently, the worker sends a POST to <a href="http://localhost:5001,">http://localhost:5001</a>/api/Values, and this worked because we were running both the webAPI and the worker on our local machine. But now that they are in separate containers, we need a way for the worker to discover and communicate with the publisher_api.</p><p>Since we’re using docker-compose to launch our application, by default a single network is created for our application. Each container for a service joins the default network and is both <em>reachable</em> by other containers on that network, and <em>discoverable</em> by them at a hostname identical to the container name.</p><blockquote><em>Understanding docker-compose networking, we need to modify the URL the worker is trying to reach. Replace localhost with the desired container name (publisher_api) and change the port to 80 (5001 was where Visual Studio Code hosted the debugger).</em></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/25bb2615c863acd30aced5b6f37ca372/href">https://medium.com/media/25bb2615c863acd30aced5b6f37ca372/href</a></iframe><p>Launch the app again using the same command as before (make sure you use the --build flag so our code changes are picked up.) This time, the worker should successfully reach the publisher_api, and the response (success:true) should be logged to the console.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/798/1*JdmaoOp-95qmW9_oaxonQA.png" /></figure><p>To recap, we took our existing .NET core application, containerized each of the two components, and then used docker-compose to launch our multi-container application. Stay tuned for Part 3 of this series, where we’ll use RabbitMQ to decouple our client and server and experiment a bit more with docker-compose.</p><p><em>Interested in joining our team at Trimble Maps? Click </em><a href="https://maps.trimble.com/careers/"><em>here</em></a><em>!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e41a0961292a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/trimble-maps-engineering-blog/getting-started-with-net-core-docker-and-rabbitmq-part-2-e41a0961292a">Getting Started with .NET Core, Docker, and RabbitMQ — Part 2</a> was originally published in <a href="https://medium.com/trimble-maps-engineering-blog">Trimble Maps Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>