<?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: David G. Simmons</title>
    <description>The latest articles on DEV Community by David G. Simmons (@davidgs).</description>
    <link>https://dev.to/davidgs</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%2F173313%2Fdb1cee6c-0435-443f-b4b6-22420efce9d7.jpg</url>
      <title>DEV Community: David G. Simmons</title>
      <link>https://dev.to/davidgs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidgs"/>
    <language>en</language>
    <item>
      <title>How to avoid bricking your device during update rollouts</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Fri, 20 Sep 2024 15:29:21 +0000</pubDate>
      <link>https://dev.to/davidgs/how-to-avoid-bricking-your-device-during-update-rollouts-2hm1</link>
      <guid>https://dev.to/davidgs/how-to-avoid-bricking-your-device-during-update-rollouts-2hm1</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhrlvintns9v1rjljed5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhrlvintns9v1rjljed5.jpg" alt="wet street with a half-deflated smiley-face balloon hovering just above the gound" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
Having an update brick )render inoperable) your device is a real risk, and even the largest of companies have been known to have it happen. This has just happened to Apple (&lt;a href="https://arstechnica.com/gadgets/2024/09/apple-pauses-ipados-18-rollout-for-m4-ipad-pro-after-bricking-complaints/" rel="noopener noreferrer"&gt;Apple pauses iPadOS 18 rollout for M4 iPad Pro after bricking complaints&lt;/a&gt; and I think we're all well aware of the &lt;a href="https://en.wikipedia.org/wiki/2024_CrowdStrike_incident" rel="noopener noreferrer"&gt;Crowdstrike incident&lt;/a&gt; by now.&lt;/p&gt;

&lt;p&gt;It's such an issue that, given the recent disasters with update rollouts (which I wrote about briefly &lt;a href="https://dzone.com/articles/how-you-can-avoid-a-crowdstrike-fiasco" rel="noopener noreferrer"&gt;here&lt;/a&gt;) it seemed like a good topic to dive into. &lt;/p&gt;

&lt;p&gt;I've always believed that if you can't come at a problem with a solution, you're probably not helping, so I looked at ways to actually solve the problem of updates that "go bad" and how to implement better strategies for deploying them.&lt;/p&gt;

&lt;p&gt;Most of what I said is, of course, not new, or earth-shattering but it is worth taking a hard look at if you are deploying large numbers of devices that will at any time need to be managed and updated in the field. Let's face it, at some point something &lt;em&gt;will&lt;/em&gt; go wrong. It always does. So planning for how to recover from it &lt;em&gt;before&lt;/em&gt; it happens is prudent, and shows your customers that you are looking after them and their interests. It's really putting your customers first by ensuring that they can see you as a trusted partner over the long haul.&lt;/p&gt;

&lt;p&gt;I cover some key topics like A/B partitioning, roll-backs after failed updates, and others, but I'm not going to go into all the details here. I encourage you to watch the video &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/watch?si=nNUaPIZcgr0Vlh-o&amp;amp;v=XELyHZp_exM&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
and let me know your thoughts. 

&lt;p&gt;I'd love to start a wider discussion about this topic of resilience in updates as I firmly believe that, along with security, it is a critical area where IoT needs to focus in order to begin to fulfill the promise of the technology.&lt;/p&gt;

&lt;p&gt;This talk relies heavily on products from &lt;a href="https://zymbit.com/?utm_source=dev-to&amp;amp;utm_medium=blog&amp;amp;utm_term=dev-to" rel="noopener noreferrer"&gt;Zymbit&lt;/a&gt; including &lt;a href="https://zymbit.com/zymkey/?utm_source=dev-to&amp;amp;utm_medium=blog&amp;amp;utm_term=dev-to" rel="noopener noreferrer"&gt;Zymkey&lt;/a&gt; and &lt;a href="https://zymbit.com/bootware/?utm_source=dev-to&amp;amp;utm_medium=blog&amp;amp;utm_term=dev-to" rel="noopener noreferrer"&gt;Bootware&lt;/a&gt; but the general concepts are applicable across a variety of vendors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you're involved in IoT in North Carolina, Northern Virginia, or Colorado, and you don't know about &lt;a href="https://riot.org" rel="noopener noreferrer"&gt;R!OT&lt;/a&gt; then you are missing out on one of the best resources for IoT professionals. They hold regular events, workshops, Lunch and Learns, etc. for their members. This talk was originally presented as a Lunch and Learn for R!OT. I encourage you to join, and to attend their events if this is an area that interests you.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>security</category>
      <category>iot</category>
    </item>
    <item>
      <title>How you can avoid another Crowdstrike fiasco</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Tue, 13 Aug 2024 15:55:08 +0000</pubDate>
      <link>https://dev.to/davidgs/how-you-can-avoid-another-crowdstrike-fiasco-4g5c</link>
      <guid>https://dev.to/davidgs/how-you-can-avoid-another-crowdstrike-fiasco-4g5c</guid>
      <description>&lt;p&gt;By now we've all heard about -- or been affected by -- the Crowdstike fiasco. If you haven't, here's a quick recap:&lt;/p&gt;

&lt;p&gt;An update to the Crowdstrike Falcon platform, pushed on a Friday afternoon, caused computers to crash and be unbootable. The update was pushed to all customers at once, and the only way to recover was to boot into "Safe Mode" and uninstall the update. That often required direct physical access to the affected computer, making recovery times even longer.&lt;/p&gt;

&lt;p&gt;I'm not going to go into a detailed post-mortem of all that went wrong, because I honestly don't know the gory details (was it dereferencing a null pointer? Was it an attempt to read a protected memory location?). There have been any number of posts and &lt;a href="https://techcrunch.com/2024/07/19/what-we-know-about-crowdstrikes-update-fail-thats-causing-global-outages-and-travel-chaos/" rel="noopener noreferrer"&gt;articles&lt;/a&gt; written about the incident, and I'm sure that Crowdstrike is doing a deep dive into what went wrong and how to prevent it from happening again. (Plus there's that $10 UberEats gift card!)&lt;/p&gt;

&lt;p&gt;This is absolutely &lt;em&gt;not&lt;/em&gt; a finger-pointing exercise. I'm not here to say "I told you so" or "you should have done it this way." There's plenty of blame to go around in any large-scale incident like this. but examining how we could have avoided such an incident, and how to make sure it doesn't repeat, is a valuable exercise.&lt;/p&gt;

&lt;p&gt;Here are the issues I have with how this was rolled out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The update was pushed to all customers at once. This is a recipe for disaster. Updates should be pushed in a phased manner, starting with a small subset of customers, and then increasing the number of customers as the update is proven to be stable. There are, of course, situations where this is just not possible. Zero-day exploits need to be patched immediately, and there is no time for a phased rollout.&lt;/li&gt;
&lt;li&gt;The updates were run as "mandatory" and "background" updates. This means that the updates were pushed to the computers without the user's knowledge or consent. In general, System Admins should have warning before such updates are applied, and should have the ability to forgo the updates if they choose.&lt;/li&gt;
&lt;li&gt;The update was pushed on a Friday afternoon. This is a classic "bad idea" in the world of IT. Updates should be pushed during the week, when there are more people available to deal with any issues that arise. (Let's not get into this argument, shall we? There are lots of opinions on this, and I'm sure that you have yours.) Again, there are always situations where an emergency update is required, and there is no time for a phased rollout.&lt;/li&gt;
&lt;li&gt;There was no fall-back strategy in place for if the update went wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main thing missing from the entire episode was any idea of resiliency. How do you recover from a failed update? How do you ensure that your customers are not left high and dry when an update goes wrong? How do you ensure that your customers are not left with a bricked computer? These are all questions of resiliency that should be explicitly addressed in any update strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the embedded world handles updates
&lt;/h2&gt;

&lt;p&gt;In the embedded world, we have been designing systems to handle firmware updates -- and update failures -- for decades. Back in my &lt;a href="https://sunspotdev.org" rel="noopener noreferrer"&gt;Sun SPOT&lt;/a&gt; days we had a firmware update architecture that would update the firmware on the device, and if the update failed, the device would automatically roll back to the previous version. This was a system that was designed to be updated over the air (OTA) and to be updated in the field. We had to design for the worst-case scenario that was both robust and recoverable.&lt;/p&gt;

&lt;p&gt;How did we do this? We implemented what is known as A/B partitioning. The device has 2 partitions (well, more really, but we'll cover that in a minute), A and B. When an update is pushed, it is pushed to the partition that is not currently running the active system. The device then reboots into the new partition, and if the update is successful, the device continues to run normally. If the update fails, the device reboots into the previous partition, and the device is back up and running. The failed partition can then be updated again, and the process repeated until the update is successful.&lt;/p&gt;

&lt;p&gt;It is a fail-safe way to push updates to devices that are in the field, and it is a system that has been in use for decades in the embedded world. If you have an Android-based phone, chances are that your phone is using this system to update the OS. If you have a Mac, you may notice that you have a small disk partition that can be used for 'recovery' in case of a system failure. It's not a full A/B partitioning system, but it is a similar idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why didn't Crowdstrike use this system?
&lt;/h2&gt;

&lt;p&gt;Well, the first problem is that these systems were all running Windows. I'll defer the whole "don't run production systems on Windows" argument for another time, but suffice it to say that Windows is not known for its robustness in the face of updates. Windows updates have been known to cause all sorts of problems, from the benign to the catastrophic. Anyone that runs Windows daily can attest to these problems. Drivers conflict, updates fail, and the system is left in a state that is not recoverable. The dreaded Blue Screen of Death (BSOD).&lt;/p&gt;

&lt;p&gt;Windows is also not built to use such an A/B partitioning system. Windows is designed to be updated in place, and  by the user, and the user is expected to be able to recover from a failed update. Much of this has been taken over by remote administration, but the basic idea is still the same: Windows is designed to be updated in place. Recent versions of Windows do have a system of "snapshots" of prior working versions that can be rolled-back to, but this is not the same as a full A/B partitioning system and the roll-back is not automatic.&lt;/p&gt;

&lt;p&gt;The Crowdstrike update pushed a change to a driver that is loaded early in the boot phase. When this driver (or any driver that is loaded into kernel space) fails for any reason, Windows will refuse to boot and go to the Blue Screen of Death (BSOD). This is how Windows protects itself from malicious drivers and driver conflicts. Effective, sure, but not the most resilient way to go when you're dealing with large production systems that &lt;em&gt;must&lt;/em&gt; be up and running all the time.&lt;/p&gt;

&lt;p&gt;Production servers simply must be available all the time. There is no room for downtime, and there is no room for failure. This is why we have resiliency built into our systems. This is why we have A/B partitioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does A/B partitioning actually work?
&lt;/h2&gt;

&lt;p&gt;A/B partitioning schemes are designed to provide high resiliency in systems that must be up and running all the time, or where a failed update can be extremely difficult or costly to recover. Think about a small sensor embedded in a hydro-electric dam that needs to get a firmware update over the air. Now think about what happens if that update fails for some reason. Having that sensor off-line due to a failed update can have catastrophic results. Sending a technician out to that remote place, to crawl into some small, dark, possibly unsafe place to fix it just isn't a viable solution. Having a system that allows for the graceful recovery of the device is the only practical solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvgjw85bj75gdqc1sokue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvgjw85bj75gdqc1sokue.png" alt="A/B Partitioning" width="800" height="983"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a simple diagram of how A/B partitioning works. The system boots into partition A, and when an update is pushed, it is written to partition B. The system then reboots from partition B, and if the update is successful, the system continues to run the updated firmware. If the update fails, the system reboots from partition A, and the system is back up and running. The failed partition can then be updated again, and the process repeated. Once successful, the system will be running out of partition B, and partition A will be the new 'spare'.&lt;/p&gt;

&lt;p&gt;During the next update cycle, the system will update the partition that is not currently running (in this example partition A). This way, the system is always running on a known-good partition, and the system can always recover from a failed update. This makes the system extremely resilient in the face of failed updates.&lt;/p&gt;

&lt;p&gt;Resiliency. It's a good thing.&lt;/p&gt;

&lt;p&gt;Is there a cost for this? Of course. There is a cost in terms of storage space, obviously. You need to have enough space to hold 2 copies of the OS, and that can be expensive. But you don't have to have double the storage space of the entire system, as only the critical OS components need be stored in the A/B partitions. All of the user and application data can be stored in a separate partition that is always available to the running OS no matter which partition it is running from.&lt;/p&gt;

&lt;p&gt;Is storage expensive? It can be, yes, but let's compare the relatively minor cost of this extra bit of storage to, say, the $500 million dollars that Delta Airlines has &lt;a href="https://www.cnbc.com/2024/07/31/delta-ceo-crowdstrike-microsoft-outage-cost-the-airline-500-million.html" rel="noopener noreferrer"&gt;reportedly&lt;/a&gt; lost over the latest incident. Seems like a small price to pay for the added resiliency that it could provide.&lt;/p&gt;

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

&lt;p&gt;The Crowdstrike meltdown was a failure of epic proportions, that much is clear. But there are a lot of factors that could have prevented it. Once the update escaped the validation and Q/A phase at Crowdstrike (which they acknowledge it shouldn't have), the lack of resiliency in the update process was the main issue, and one that could have been addressed with an A/B partitioning scheme. A/B partitioning isn't available on Windows machines, but it is absolutely available for other architectures. (I know I said I wouldn't get into the "don't run your production systems on Windows" argument, but I just can't help myself.)&lt;/p&gt;

&lt;p&gt;We here at Zymbit have have designed a system to provide this kind of resiliency for our customers. Our &lt;a href="https://www.zymbit.com/bootware/" rel="noopener noreferrer"&gt;Bootware&lt;/a&gt; system is designed to provide a secure and resilient way to update devices in the field. We use A/B partitioning to ensure that the device is always running from a known-good instance, and that the device can always recover from a failed update. Bootware also provides encrypted filesystems, secure boot, and secure updates, all designed to provide the highest level of security and resiliency for your devices.&lt;/p&gt;

&lt;p&gt;All drivers for Microsoft Windows must be cyptographically signed. Most software for Windows is also required to be signed. The same is true for macOS. All system and driver updates -- really anything that runs in any privileged mode -- should be cryptographically signed and verified before being allowed to be installed and run. Bootware ensures that all updates are cryptographically signed and verified before being allowed to be installed and run. This is a critical part of the security and resiliency of the system.&lt;/p&gt;

&lt;p&gt;How a truly secure and resilient update process &lt;em&gt;should&lt;/em&gt; be rolled out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updates should be pushed in a phased manner, starting with a small subset of customers, and then increasing the number of customers as the update is proven to be stable whenever possible. Non-phased rollouts should be reserved for true emergency situation like zero-day exploits.&lt;/li&gt;
&lt;li&gt;Updates should be pushed during the week, when there are more people available to deal with any issues that arise. Again, there are always situations where an emergency update is required, and there is no time for a phased rollout.&lt;/li&gt;
&lt;li&gt;Administrators should be informed &lt;em&gt;before&lt;/em&gt; an update is rolled out, and should have the ability to forgo the update if they choose.&lt;/li&gt;
&lt;li&gt;There should be a fall-back and recovery strategy in place for when an update goes wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how a truly secure and resilient update process should be rolled out. This is how we at Zymbit do it, and this is how we think it should be done. We build systems with this level of trust and resiliency built in because our customers expect it, and because we believe it is the right way to do things. If your updates are not done this way, it's time you start asking why not.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>raspberrypi</category>
      <category>security</category>
    </item>
    <item>
      <title>A KubeCon Wrap Up, of sorts</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Tue, 14 Nov 2023 21:22:00 +0000</pubDate>
      <link>https://dev.to/davidgs/a-kubecon-wrap-up-of-sorts-16c0</link>
      <guid>https://dev.to/davidgs/a-kubecon-wrap-up-of-sorts-16c0</guid>
      <description>&lt;p&gt;Well, KubeCon North America 2024 is over and done with, and I thought I'd write a short recap of my time there. &lt;/p&gt;

&lt;p&gt;While the final attendance numbers aren't in yet, it's likely that there were between 7,000 and 8,000 attendees in Chicago the week of November 6th. That's a &lt;em&gt;lot&lt;/em&gt; of Kubernetes enthusiasts! And the weather could not have been better (I mean, for Chicago, in November). &lt;/p&gt;

&lt;p&gt;At the Otterize booth we were extremely busy too! On day one, November 7th, I personally was in the booth for 11 hours (some of that was booth setup). That's a long day. &lt;/p&gt;

&lt;p&gt;We had some &lt;em&gt;very&lt;/em&gt; popular swag! The Otter was a huge hit, again, and we handed out about 600 of them over the course of the week. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ACDeSlWx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wybkis7dcd8yj0wf0z9q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ACDeSlWx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wybkis7dcd8yj0wf0z9q.jpeg" alt="The Otterize Otter plushie that we handed out" width="800" height="601"&gt;&lt;/a&gt;&lt;br&gt;
We also handed out several hundred T-Shirts and an uncountable number of Otterize stickers of various kinds. Ok, enough about the swag, how was the booth otherwise?&lt;/p&gt;

&lt;p&gt;Well, we scanned over 450 badges (last I checked) and had some really interesting conversations with a lot of super interesting people. We gave over 100 demos (at least, I didn't count them all) to some very enthusiastic attendees and overall got some great feedback. &lt;/p&gt;

&lt;p&gt;A rundown of some numbers: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;600 otters given out&lt;/li&gt;
&lt;li&gt;200+ T-shirts given out&lt;/li&gt;
&lt;li&gt;36 new members joined our &lt;a href="https://otrz.md/3QLMYFY"&gt;Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've done a lot of booth duty in my years in this business, and I can honestly say that this KubeCon NA Otterize booth was one of the most successful, fun, and productive booths I've ever done. &lt;/p&gt;

&lt;p&gt;It was, of course, due in large part to the amazing group of Otters that were there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2NK_5acr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxbeuf68ehgvnnj76wgt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2NK_5acr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxbeuf68ehgvnnj76wgt.png" alt="Otters (Otterize employees) at the KubeCon Booth from left to right: David G. Simmons, Tomer Greenwald - CEO, Ori Shavit, Neha Varshneya, and Omri Shani. Not Pictured: Ori Shoshan, CTO" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think we may have more fun than anyone though! &lt;/p&gt;

&lt;p&gt;If you didn't get a chance to stop by the booth and see us, please, join our &lt;a href="https://otrz.md/3QLMYFY"&gt;Community&lt;/a&gt;, ⭐️ us on &lt;a href="https://github.com/otterize"&gt;GitHub&lt;/a&gt;, and reach out to &lt;a href="https://otrz.md/47blrVm"&gt;set up a demo&lt;/a&gt;! &lt;/p&gt;

</description>
      <category>kubecon</category>
    </item>
    <item>
      <title>Deploying an EKS cluster with attached storage</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Mon, 06 Nov 2023 20:12:34 +0000</pubDate>
      <link>https://dev.to/davidgs/deploying-an-eks-cluster-with-attached-storage-43i5</link>
      <guid>https://dev.to/davidgs/deploying-an-eks-cluster-with-attached-storage-43i5</guid>
      <description>&lt;p&gt;I'll admit, up front, that I'm new to Kubernetes, and probably always will be. There's just too much to learn! That being said, it is my job (well, I am Head of #DevRel for &lt;a href="https://otterize.com/?utm_campaign=dev-to&amp;amp;utm_source=blog&amp;amp;utm_medium=referral"&gt;Otterize&lt;/a&gt;, and we're all about Kubernetes) so I have to know a certain amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I was trying to do
&lt;/h2&gt;

&lt;p&gt;I do a lot of demos. As part of that, I have to provision, a lot of Kubernetes clusters. A lot. Several times a day if I'm working on building or debugging a demo or tutorial. &lt;/p&gt;

&lt;p&gt;Being able to create, and recreate, clusters on any of the major cloud providers (mainly AWS and Google Cloud, for now) is essential to me being able to create tutorials that always work, and demos that work under pressure (like on stage!).&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem child
&lt;/h2&gt;

&lt;p&gt;Doing this on GKE was relatively straight forward. When I'd create a cluster that needed persistent storage, it was just provisioned and added, and everything worked as expected. &lt;/p&gt;

&lt;p&gt;Imagine my surprise when I created a cluster in EKS and deployed the demo applications and ... Kafaka just failed because there was no attached storage! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y8gZaLUi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obdan5jz13sicoyquqsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y8gZaLUi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obdan5jz13sicoyquqsr.png" alt="Hi, it's me. I'm the problem it's me" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It turns out that EKS doesn't auto-provision storage. So I started trying to figure out how to attach storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just because it's documented doesn't mean you can do it
&lt;/h2&gt;

&lt;p&gt;After reading a ton of documentation (which, quite frankly made my head hurt and didn't get me any closer to a solution) I posted in the r/kubernetes subreddit hoping for help. &lt;/p&gt;

&lt;p&gt;Pretty much all that did was point me back to the documentation, which wasn't helpful. Nothing I could find pointed to a solution that I could simply add to my EKS config.json to build the cluster. &lt;/p&gt;

&lt;p&gt;I wanted something to add so that my command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eksctl create cluster -f eks-config.yaml --profile ME&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;would create the cluster with everything I needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Working with one of the great engineers here at &lt;a href="https://otterize.com/?utm_campaign=dev-to&amp;amp;utm_source=blog&amp;amp;utm_medium=referral"&gt;Otterize&lt;/a&gt; we finally hit on a solution. &lt;/p&gt;

&lt;p&gt;It was so amazingly simple that I am still a little dumbfounded that the AWS docs make it so hard. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;addons&lt;/code&gt; section I had already added the VPC CNI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;addons:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;vpc-cni&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;v&lt;/span&gt;&lt;span class="mf"&gt;1.14&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;-eksbuild.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;attachPolicyARNs:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#optional&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;configurationValues:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|-&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;enableNetworkPolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a few others, like DNS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;coredns&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;-eksbuild.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kube-proxy&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;v&lt;/span&gt;&lt;span class="mf"&gt;1.27&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;-eksbuild.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's where the addition of the EBS storage came in, and was just stupid-easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;aws-ebs-csi-driver&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;v&lt;/span&gt;&lt;span class="mf"&gt;1.22&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;-eksbuild.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;attachPolicyARNs:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I could now create a cluster with a single command, from the command-line, that would run my demo app (with Kafaka) on EKS without any trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell me about it
&lt;/h2&gt;

&lt;p&gt;If you read this and say "hey dummy, that's not how any of this works!" please reach out and tell me why! I'm just trying to learn, so learning from my mistakes is a huge bonus. &lt;/p&gt;

&lt;p&gt;If you read this and are like "that's effing &lt;em&gt;genius&lt;/em&gt;!" please also reach out! I'd love to hear from you either way! &lt;/p&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;Please come and join us in the &lt;a href="https://otrz.md/slack"&gt;Otterize Community&lt;/a&gt; and learn more about what we do, and how we're trying to make securing your cluster dead-simple!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>eks</category>
      <category>aws</category>
    </item>
    <item>
      <title>Bite-size Otterize: moving fast and (never) breaking things.</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Wed, 16 Aug 2023 12:47:23 +0000</pubDate>
      <link>https://dev.to/davidgs/bite-size-otterize-moving-fast-and-never-breaking-things-3lic</link>
      <guid>https://dev.to/davidgs/bite-size-otterize-moving-fast-and-never-breaking-things-3lic</guid>
      <description>&lt;p&gt;If you read my &lt;a href="https://otterize.com/blog/joining-otterize-modern-networks" rel="noopener noreferrer"&gt;last blog post&lt;/a&gt;, you'll know that I'm not new at computering™. I'm old enough, and have enough experience, to know that the "move fast and break things" culture just means you end up with broken things. But at least you broke them quickly? No.&lt;/p&gt;

&lt;p&gt;What we want is to move fast and &lt;em&gt;fix&lt;/em&gt; things! I don't need the stress and anxiety of worrying about breaking a production app. I mean, everyone's broken prod once (or will someday) and no one wants to do it (again or for the first time).&lt;/p&gt;

&lt;p&gt;And if I'm going to move fast, the tools I use must make the job much, much easier: they need to give me superpowers. They need to help me tackle big tasks with little effort and little stress. Easy to use. Simple to configure. Safe to deploy. No unintended side-effects.&lt;/p&gt;

&lt;p&gt;This is especially true when I'm using a tool to do hard things where mistakes are costly. Have you tried deploying network policies to a large cluster? Or Istio auth policies? Or how about mTLS? None of those things are easy to do nor at all forgiving of mistakes. That's why I want a power tool to do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it precise
&lt;/h3&gt;

&lt;p&gt;I want to be able to target how I use the tool. Precision. Sure, I want the tool to be easy, and prevent me from making mistakes, but I also don't want to make massive changes all at once. I want to target my usage so I can walk before I run. I want rapid incremental progress that adds value at each step.&lt;/p&gt;

&lt;p&gt;And remember, I've been doing this a long time, so I want to be &lt;em&gt;sure&lt;/em&gt; that what I'm doing is working. Before it has a chance to blow up on me. Actually seeing what will happen &lt;strong&gt;before&lt;/strong&gt; it happens would be really nice. I'm old enough to remember "trust but verify."&lt;/p&gt;

&lt;p&gt;No matter what anyone tells you, sanity is not overrated. So sanity-checking a deployment before it has a chance to go sideways is a good thing. I want to trust my tool, but it has to earn my trust first. So let's do things to sanity-check its abilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  What am I talking about?
&lt;/h3&gt;

&lt;p&gt;As Head of DevRel here at Otterize, making things right &lt;em&gt;for developers&lt;/em&gt; is what I focus on. I make sure that Otterize makes things that are intuitive, easy to use, solve hard problems, and let developers make quick iterations that add value.&lt;/p&gt;

&lt;p&gt;When I got here I knew nothing about Otterize, Kubernetes, or securing services within a Kubernetes cluster. In reality, I was the perfect test-subject. And I was eager to ramp up.&lt;/p&gt;

&lt;p&gt;As luck would have it, during my onboarding, Otterize was working on some new capabilities to suit precisely those users who wanted more precision in configuring access controls, in driving to zero trust. Users who wanted to deploy Otterize in a more "bite size" way: rapidly, easily, predictably, and incrementally, getting more and more secure at every step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it bite-sized
&lt;/h3&gt;

&lt;p&gt;I was still new to Otterize and the IBAC (intent-based access control) approach when these capabilities became available. The first time I tried them, I was like "YES! THIS is how it's supposed to be!"&lt;/p&gt;

&lt;p&gt;And that's because I was able to just pick a single service, any service, that I wanted to protect. I could prepare to protect it by automatically ensuring all clients that should be able to reach it would be able to, in one step, and then actually protect it, in a second step. Without impacting anything else! The protection is done by configuring network policies, which are built into Kubernetes itself. But I didn't have to touch network policies themselves, much less be an expert with them.&lt;/p&gt;

&lt;p&gt;In fact, &lt;strong&gt;step 1&lt;/strong&gt;, knowing which clients intend to call the service is one of those superpowers I mentioned. The Otterize open source network mapper finds the clients calling services in your cluster. If you believe those calls are likely the way the code is supposed to behave, you can export those call patterns as client intents. And you can export just the clients of a single server:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;% otterize network-mapper export --server frontend.otterize-ecom-demo -o clients.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;All you need to do to &lt;em&gt;declare&lt;/em&gt; that these are the authorized clients is run &lt;code&gt;kubectl apply -f clients.yaml&lt;/code&gt;, and Otterize will ensure they'll have access to the &lt;code&gt;frontend.otterize-ecom-demo&lt;/code&gt; service when you protect it.&lt;/p&gt;

&lt;p&gt;The Otterize Cloud access graph UI will even show you, visually, that these authorized clients will be able to call that service once protected. Think of it as a dry run for what will happen when you protect.&lt;/p&gt;

&lt;p&gt;Then protecting it, &lt;strong&gt;step 2&lt;/strong&gt;, is just as easy. Create this short &lt;code&gt;protect-frontend.yaml&lt;/code&gt; file and &lt;code&gt;kubectl apply&lt;/code&gt; it too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s.otterize.com/v1alpha2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProtectedService&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otterize-ecom-demo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiq6i0yz8pna46skfdy2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiq6i0yz8pna46skfdy2.gif" alt="Protecting just the front-end service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And really that's all it takes! Quick. Easy. High precision. It just works. Otterize labeled pods, labeled namespaces, and created and applied network policies to a single service without affecting anything else and without me having to fuss over anything.&lt;/p&gt;

&lt;p&gt;So then I thought "Let me just work my way across my access graph here and protect another service, and then another ..."&lt;/p&gt;

&lt;p&gt;Watch as I move across the access graph and protect everything, one service at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwdw4wxvgk78r58vikp6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwdw4wxvgk78r58vikp6.gif" alt="Protecting each service one at a time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doing all of that took 8 more commands: 2 to create all the intents, and then 6 more to protect each service. Most important for someone as careful as me: I didn't have to do it all at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  It does what I want
&lt;/h3&gt;

&lt;p&gt;I now have a tool that easily and effortlessly does &lt;em&gt;exactly&lt;/em&gt; what I want — and I generally dislike the use of the word "easy" because, well, just because something's easy for &lt;strong&gt;me&lt;/strong&gt; doesn't mean it will be easy for &lt;strong&gt;you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this point I'll refer you back to the previous section when I told you I knew less than nothing about deploying network policies in a Kubernetes cluster. Guess what? I &lt;em&gt;still&lt;/em&gt; don't know how to do it. At least not the old-fashioned way. But &lt;em&gt;I just did it&lt;/em&gt;. I did it slowly, service-by-service, and I didn't break anything in the process. I ended up with all of my services protected. And it wasn't a 6-month project. It took 5 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  That's it!
&lt;/h3&gt;

&lt;p&gt;No, really, that's &lt;strong&gt;it!&lt;/strong&gt; It's a joy to work with. I can apply intents and protection and see it in my Otterize Cloud access graph in real time so I know what's happening the whole time. I can grow my level of confidence as I progress — and I can complete it without breaking anything. HUZZAH!!&lt;/p&gt;

&lt;p&gt;Ready to experience this game-changer for yourself?&lt;br&gt;
Take &lt;a href="https://docs.otterize.com/guides/protect-1-service-network-policies" rel="noopener noreferrer"&gt;our new Otterize tutorial&lt;/a&gt; out for a spin and &lt;a href="https://joinslack.otterize.com" rel="noopener noreferrer"&gt;join our Slack Community&lt;/a&gt; to let us know what you think!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>networking</category>
    </item>
    <item>
      <title>Joining Otterize and the modern network stack</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Wed, 19 Jul 2023 11:48:07 +0000</pubDate>
      <link>https://dev.to/davidgs/joining-otterize-and-the-modern-network-stack-1baj</link>
      <guid>https://dev.to/davidgs/joining-otterize-and-the-modern-network-stack-1baj</guid>
      <description>&lt;h3&gt;
  
  
  Hi! 👋🏼 I'm new here.
&lt;/h3&gt;

&lt;p&gt;I joined Otterize about 3 weeks ago as the Head of Developer Relations and I could not be more excited to be here.&lt;/p&gt;

&lt;p&gt;Some of you who &lt;a href="https://davidgs.com"&gt;know me&lt;/a&gt; might be asking the questions "Wait, I thought you were a data nerd and an IoT junkie? What are you doing going into Kubernetes?"&lt;/p&gt;

&lt;p&gt;Absolutely fair question! Let me explain a bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, Some history
&lt;/h3&gt;

&lt;p&gt;Way back at the dawn of time, when I first began my career in tech I worked for a US Government research lab. I wrote network intrusion detection software. It was called The &lt;a href="https://ieeexplore.ieee.org/document/390643"&gt;NERD&lt;/a&gt; (Network Event Recording Device) and it was deployed site-wide in what was (and probably still is) one of the most complicated, and highly secure, computing environments in the world.&lt;/p&gt;

&lt;p&gt;We wanted to monitor all traffic, and report anomalies in real-time. Most of the reporting traffic was over UDP (&lt;code&gt;syslogd&lt;/code&gt;) but &lt;strong&gt;no&lt;/strong&gt; traffic of any kind was allowed to traverse the network unencrypted. We used Kerberos 2048kb encryption for all TCP traffic, but there was no such beast for UDP. Until I wrote it. I also wrote a bunch of extensions to &lt;code&gt;syslogd&lt;/code&gt; to further secure it with ingress and egress filters.&lt;/p&gt;

&lt;p&gt;Our group also maintained detailed network maps of all servers and services and what they were connected to, which servers talked to which servers, etc. These network maps were always hopelessly out of date and we had no real way to automatically generate them, so we just sort of hoped we were right most of the time. Invariably we were not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fun story:&lt;/strong&gt; We were absolutely convinced, based on our network topology maps, that one of the most sensitive networks was air-gapped and therefore could not be penetrated from the outside. Given the nature of the data we had on this network, it was absolutely critical that it remain secure.&lt;br&gt;
&lt;/p&gt;


&lt;p&gt;To test this theory, we asked &lt;a href="https://www.cybersecurityeducationguides.org/tsutomu-shimomura-vs-kevin-mitnick/"&gt;Tsutomu Shimomura&lt;/a&gt; to try to gain access to it. We were &lt;strong&gt;certain&lt;/strong&gt; that he would be unable to do so. Imagine our level of panic when, 5 minutes into the test, he had taken over a terminal we were using on this "air-gapped" network.&lt;br&gt;
&lt;/p&gt;


&lt;p&gt;It was time to update the network maps! And to rethink our network access policies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was all back in the mid 1990s when everything was just migrating from mainframes to client-server architectures. Everything was either a client or a server on the network, and each one had its own firewall, security policies, etc. It was a nightmare to manage. We relied on individual groups to report to us in the NOC (Network Operations Center) when they added a computer, or a service, to the network. They rarely did, so we were always playing catch-up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cloud has entered the chat
&lt;/h3&gt;

&lt;p&gt;As client-server computing, and maintaining your own data center or server farm, became too costly, we saw the migration to the cloud. In the beginning, "The Cloud" really was just "someone else's computers." We trusted someone else to run the data center and the servers, and they sold us time on those servers to run our workloads.&lt;/p&gt;

&lt;p&gt;In the early days of cloud computing, this was revolutionary. It was a huge cost savings for companies, and it allowed them to focus on their core business rather than on maintaining their own data centers. They could off-load the expensive part to cloud providers, but they essentially had to manage all of the services — and security for them — themselves. It was &lt;em&gt;better&lt;/em&gt;, but still not ideal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cloud has evolved
&lt;/h3&gt;

&lt;p&gt;It turns out that just moving to "other people's computers" wasn't really enough to realize the true power of cloud computing. When you truly want to scale to the massive sizes that a lot of modern applications require, it certainly wasn't enough.&lt;/p&gt;

&lt;p&gt;It was time for another revolution in computing. This time, it was the revolution of containers and container orchestration. This is where Kubernetes comes in.&lt;/p&gt;

&lt;p&gt;Containers had been around for a long time, and they exploded in popularity with the release of Docker. Docker made it easy to create, manage, and deploy applications in containers, but it was still a lot of work to manage them all. You had to manage the container images, the container registries, the container runtimes, the container networks, etc. It was still a lot of work.&lt;/p&gt;

&lt;p&gt;Kubernetes was the answer to this problem. (Well, mostly.) It was a way to manage all of the containers, all of the services, all of the networks, all of the storage, all of the security, and all of the everything else. It was a way to manage all of the things all at once. It was also the way to introduce scaling to the container architectures, allowing you to scale both up and down as demand required.&lt;/p&gt;

&lt;p&gt;We have now moved from client-server to cloud architectures, and have virtualized the data centers and services using Kubernetes. Progress!&lt;/p&gt;

&lt;p&gt;But here's the thing: The basics of it all have not really changed. The physical machines have been virtualized. The networking between them has been virtualized. The storage has been virtualized. The services have been virtualized. The security has been virtualized. But it's all still there, and it all still has to be managed. Somehow.&lt;/p&gt;

&lt;p&gt;And guess what? All the problems with all of those things are still there as well. Manging the network access between servers and services. Managing the permissions and properties for databases, streaming data services (like Kafka) and everything. Yes, it's &lt;strong&gt;easier&lt;/strong&gt; now, and there are a lot better tools for doing it, but the problems are still there.&lt;/p&gt;

&lt;h3&gt;
  
  
  So why am I here?
&lt;/h3&gt;

&lt;p&gt;Yes, we're finally getting to that. First and foremost, I'm here at Otterize because, after talking to the founders, I had such enormous respect and admiration for them that I &lt;em&gt;really&lt;/em&gt; wanted to join them. Second, I had been wanting to work with &lt;a href="https://www.linkedin.com/in/sarid/"&gt;Uri Sarid&lt;/a&gt; for a &lt;em&gt;very&lt;/em&gt; long time. Finally, I was thoroughly convinced that what they were building was both technologically brilliant and absolutely necessary.&lt;/p&gt;

&lt;p&gt;One of the things that absolutely sealed the deal for me was the origin story of the company name Otterize. The founders were brainstorming names and Uri suggested "Otterize" as a joke. But when they looked up the meaning of the word "otterize" they found that it actually means "to make something more delightful, more fun, or more charming." And that really resonated with me.&lt;/p&gt;

&lt;p&gt;Remember, I started with some history about my experience in the networking and data services area. So while I don't understand Kubernetes well (yet) the underlying principles are much the same as my previous experience, and almost all of the problems I identified in that history are addressed by Otterize and Intent Based Access Control (IBAC). In fact, it goes further and solves several additional problems that I hadn't covered before. It's a brilliant solution to a set of very real problems.&lt;/p&gt;

&lt;p&gt;So here I am, stepping out of my tech comfort zone yet again. But I'm stepping &lt;em&gt;sort of&lt;/em&gt; back into my own history as well. I started my career in networking and security, so in a way Otterize is bringing me full circle. And I'm excited to learn from and work with Uri and the other brilliant folks here. I'm confident that together we can build something truly transformational that makes managing data services and security in the cloud era much easier and more secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  What now?
&lt;/h3&gt;

&lt;p&gt;For me, it's on to learning more about Kubernetes (since it's not been an area of expertise for me). I'll also be building more demos and tutorials (and we already have some great ones, so go check those out!). And I'll be back on the road, coming to a DevOps, Platform Engineering, or Kubernetes conference near you soon!&lt;/p&gt;

&lt;p&gt;If you're looking for a speaker, I'd love to hear from you via &lt;a href="//mailto:davidgs@otterize.com"&gt;email&lt;/a&gt;, &lt;a href="https://twitter.com/davidgsIoT"&gt;twitter&lt;/a&gt;, &lt;a href="https://linkedin.com/in/davidgsimmons"&gt;LinkedIn&lt;/a&gt;, or &lt;a href="https://tty0.social/@davidgs"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ibac</category>
      <category>kubernetes</category>
      <category>network</category>
    </item>
    <item>
      <title>Handy Tools I Use</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Thu, 08 Apr 2021 21:18:53 +0000</pubDate>
      <link>https://dev.to/davidgs/handy-tools-i-use-o42</link>
      <guid>https://dev.to/davidgs/handy-tools-i-use-o42</guid>
      <description>&lt;p&gt;This started as a simple post to an internal Slack group. Then I got asked to post it to another channel in that Slack. Then I decided to post it to the DevRel Collective Slack. At which point I was asked to make it a blog post so it didn’t get lost in the scroll-back (DevRel Collective uses a free Slack account, so we can only scroll back 10,000 messages. With 2,000+ members, that happens faster than you think.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Letters to Santa - Automating Joy to the World, At Scale</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Fri, 18 Dec 2020 13:30:13 +0000</pubDate>
      <link>https://dev.to/davidgs/letters-to-santa-automating-joy-to-the-world-at-scale-n6c</link>
      <guid>https://dev.to/davidgs/letters-to-santa-automating-joy-to-the-world-at-scale-n6c</guid>
      <description>&lt;p&gt;It’s that time of year again. The time when the world’s largest order fulfillment operation experiences its heaviest load. No, not Amazon - we’re talking about Santa Claus, Inc. - the largest logistics company in the world, with a 24-hour global delivery window at peak load.&lt;/p&gt;

&lt;p&gt;This year is different, however. Earlier this year, Saint Nick clicked on an ad on his Facebook feed, one promising a digital nomad lifestyle through automating his business. Sick of the Arctic weather and the stress of traveling, the thought of sitting on a beach in Thailand - while still bringing joy to children around the world - was enticing.&lt;/p&gt;

&lt;p&gt;Santa paid for the course and applied the principles of process automation, task decomposition and distribution, and integration with third-party services to his business.&lt;/p&gt;

&lt;p&gt;Now he's kicking back on a beach on Koh Samui, while the automation brings joy to the world - at scale.&lt;/p&gt;

&lt;p&gt;So this Christmas, children’s letters to Santa are routed to independent associates (their parents), who fulfill the orders using Amazon. Santa’s successful business transformation became a case study, which we’re going to share with you here.&lt;/p&gt;

&lt;p&gt;Here’s how it’s done.&lt;/p&gt;

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

&lt;p&gt;Given that Santa's a modern guy, and in case he needed to supplement his retirement income with some contract front-end development work, Santa decided to do a crash course in learning to program in React.js. It seemed like the thing all the cool kids were doing, so Santa wanted to give it a shot.&lt;/p&gt;

&lt;p&gt;While it was harder than he thought, thanks to a lot of hard-core googling, and a lot of copy-paste (remember kids, good developers copy, great developers paste!) he was able to come up with a site that at least looks passable, and handles the simple function of accepting a letter to Santa and submitting it to the process engine.&lt;/p&gt;

&lt;p&gt;For the process engine Santa of course chose &lt;a href="https://camunda.com"&gt;Camunda&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Once the form was designed, all that was left to do was submit the form using some JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setSubmitting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkValidity&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// form is invalid! so we do nothing&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://write-a-letter-to-santa.org:9091/santa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Santa has been notified! You can reload the page to send another letter.&lt;/span&gt;&lt;span class="dl"&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;Using a simple alert to let the user know that the form was submitted was the path of least resistance, and Santa was getting lazy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;Handling a letter by just forwarding it to the parents as-is seemed a little too lazy, even for Santa, so he quickly designed a business process using &lt;a href="https://cawemo.com"&gt;Cawemo&lt;/a&gt; to handle the routing of the letters.&lt;/p&gt;

&lt;p&gt;Here's what that process looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zjuIIlDS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/cgd0c34rgq15w3mspstk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zjuIIlDS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/cgd0c34rgq15w3mspstk.png" alt="Letter to Santa Business Process" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the flow:&lt;/p&gt;

&lt;p&gt;1) A letter comes in, which starts the process.&lt;br&gt;
2) The letter is analyzed using some Natural Language Processing (NLP) algorithms to extract some parts of the letter to help figure out what the writer is asking for:&lt;br&gt;
   1) Identify any items the writer is asking for.&lt;br&gt;
   2) Do some Sentiment Analysis to try to figure out how important each item is to the writer.&lt;br&gt;
3) If there are no items identified, then the letter is routed to a manual process where one of the Elves can do some more investigation, and update the list.&lt;br&gt;
4) Once this is done, go find some possible Amazon links for the things identified.&lt;br&gt;
5) Send a letter to the parents with a copy of the original letter, the items they asked for (linked to Amazon of course) and some helpful hints as to what the writer wanted most.&lt;br&gt;
6) Store the product information in a local database for analysis later.&lt;/p&gt;

&lt;p&gt;Now, before anyone tries to have Santa fined for non-compliance with GDPR, he's not storing any names, email addresses, or any other personal data. Santa already knows everything about you! He just stores the items asked for. So he can do some demand-gen analysis later, of course.&lt;/p&gt;

&lt;p&gt;Santa wrote a pretty basic web-server in &lt;code&gt;Go&lt;/code&gt; to handle the incoming letters, and submit them to the Camunda BPM processing engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/santa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;santa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":9091"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// set listen port&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ListenAndServe: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;And then a handler function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;santa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;enableCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET Method Not Supported"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GET Method not supported"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;SantaData&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Letter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;EndpointUrl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8000/engine-rest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ApiUser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ApiPassword&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="n"&gt;processKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"santa"&lt;/span&gt;
        &lt;span class="n"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParentEmailAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="s"&gt;"letter"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryProcessDefinitionBy&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;processKey&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReqStartInstance&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error starting process: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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;He did have to enable CORS to allow the cross-origin posting of data. That's rather a key point in all of this, since the server here runs on a different port than the server that handles posting the letters.&lt;/p&gt;

&lt;p&gt;After that, a bit of magic with the &lt;a href="https://github.com/citilinkru/camunda-client-go"&gt;Camunda Go Client&lt;/a&gt; and the letter is submitted to the Camunda BPM Process Engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Natural Language Processing?
&lt;/h2&gt;

&lt;p&gt;Yes, it's a form of Artificial Intelligence (AI) that allows you to break up written text and identify parts of it based on certain criteria. Done right, it can be very accurate.&lt;/p&gt;

&lt;p&gt;So let's take a sample letter:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dear Santa,&lt;/p&gt;

&lt;p&gt;My name is Leon and I'm 36 years old (yeah, I still believe in Santa 😇)&lt;/p&gt;

&lt;p&gt;This year I've been the goodest kid ever, so I kinda deserve a big present...&lt;/p&gt;

&lt;p&gt;I was thinking about a nice LEGO box like the skyline kit or the New York City one. If that's not an option, I'd settle for some good chocolate too!&lt;/p&gt;

&lt;p&gt;Thank you,&lt;br&gt;
Leon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you and I can easily pick out the &lt;code&gt;items&lt;/code&gt; in that letter that would be gifts, but it turns out that doing that is harder than it seems.&lt;/p&gt;

&lt;p&gt;When we run that through our NLP processor we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This year I've been the goodest kid ever, so I kinda deserve a big present...
Sentiment: 0.300000, positive   Item: name   Type: OTHER
Sentence: I was thinking about a nice LEGO box like the skyline kit or the New York City one.
Sentiment: 0.200000, positive   Item: LEGO box   Type: OTHER
Item: skyline kit    Type: OTHER
Sentence: If that's not an option, I'd settle for some good chocolate too!
Sentiment: 0.700000, positive   Item: option     Type: OTHER
Item: chocolate  Type: OTHER
Sentence: Thank you,
Leon
Sentiment: 0.800000, positive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmmm ... Not great.&lt;/p&gt;

&lt;p&gt;If Leon had written Santa a more specific letter, we could have gotten some better results for him:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dear Santa,&lt;/p&gt;

&lt;p&gt;My name is Leon and I'm 36 years old (yeah, I still believe in Santa 😇)&lt;/p&gt;

&lt;p&gt;This year I've been the goodest kid ever, so I kinda deserve a big present...&lt;/p&gt;

&lt;p&gt;I was thinking about a nice Lego skyline kit or the Lego New York City Skyline Kit.&lt;/p&gt;

&lt;p&gt;If that's not an option, I'd settle for some good Belgian Chocolate too!&lt;/p&gt;

&lt;p&gt;Thank you,&lt;br&gt;
Leon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we process that letter, we get better results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Letter is 4 sentences long.
Sentence: Dear Santa, My name is Leon and I'm 36 years old (yeah, I still believe in Santa :innocent:) This year I've been the goodest kid ever, so I kinda deserve a big present...
Sentiment: 0.500000, positive   Item: name   Type: OTHER
Item: Santa  Type: OTHER
Sentence: I was thinking about a nice Lego skyline kit or the Lego New York City Skyline Kit.
Sentiment: 0.000000, positive   Item: skyline kit    Type: OTHER
Item: Lego   Type: ORGANIZATION
Item: Skyline Kit    Type: CONSUMER_GOOD
Sentence: If that's not an option, I'd settle for some good Belgian Chocolate too!
Sentiment: 0.400000, positive   Item: option     Type: OTHER
Item: Belgian Chocolate  Type: CONSUMER_GOOD
Sentence: Thank you, Leon
Sentiment: 0.800000, positive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice that now we have identified some &lt;code&gt;CONSUMER_GOODS&lt;/code&gt; in the letter, which are &lt;em&gt;much&lt;/em&gt; easier to find.&lt;/p&gt;

&lt;p&gt;So let's see how Santa went about finding links.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about if there are no CONSUMER_GOODS?
&lt;/h2&gt;

&lt;p&gt;That's where the magic of manual processes and forms comes in, of course. We have an exclusive gateway that checks to see if any &lt;code&gt;CONSUMER_GOODS&lt;/code&gt; have been identified. If not, then it would be harder for the Amazon-search process to find anything meaningful.&lt;/p&gt;

&lt;p&gt;This part of the process is where the Elves come into play. They didn't all lose their jobs once the whole operation was automated! But they &lt;em&gt;were&lt;/em&gt; able to join the "Work From Home" movement, so now they do their jobs from wherever they want! (Look for elves in your neighborhood!)&lt;/p&gt;

&lt;p&gt;Let's say Leon had written a letter that just said "I want world peace. And I'd love harmony". While those are lofty ideals, they aren't really things that can be ordered from Amazon (at least not yet).&lt;/p&gt;

&lt;p&gt;Here's the form the Elves get when a letter gets routed to them for intervention:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qIvDfcqG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vs6yjyuwbft7kz792scj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qIvDfcqG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vs6yjyuwbft7kz792scj.png" alt="When the form arrives" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then after the Elves have given it some thought, checked the Naughty/Nice list, they can update the items:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zvieanKM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/m77ef36c98swceae00km.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zvieanKM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/m77ef36c98swceae00km.png" alt="Updated items form" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form is then routed back into the process.&lt;/p&gt;

&lt;p&gt;There is a bit of work to do in building the form though. First thing is to build the form according to the &lt;a href="https://docs.camunda.org/manual/7.5/reference/embedded-forms/javascript/lifecycle/"&gt;docs&lt;/a&gt;. Since Santa put everything into a JSON object when the letter was parsed, he had a bit more work to do though.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;santa-form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="nx"&gt;Gifts&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;make&lt;/span&gt; &lt;span class="nx"&gt;them&lt;/span&gt; &lt;span class="nx"&gt;easier&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/form-script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;variableManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;camForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;camForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-loaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// fetch the variable 'gifts'&lt;/span&gt;
      &lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gifts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variableValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gifts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;camForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variables-fetched&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// value has been fetched from the backend&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variableValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gifts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;frm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;santa-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;// will be the number of gifts.&lt;/span&gt;
        &lt;span class="c1"&gt;// it's an array of Json so we have to parse it.&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;gift&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
          &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;textfield&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;textfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="c1"&gt;// set the ID so we know where the gift goes back in the JSON array&lt;/span&gt;
          &lt;span class="nx"&gt;textfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gift-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;
          &lt;span class="nx"&gt;textfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;gift&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nx"&gt;textfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htmlFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="o"&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="nx"&gt;camForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// get the form&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;frm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;santa-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// parse the original JSON&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// get all the inputs&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
          &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])].&lt;/span&gt;&lt;span class="nx"&gt;gift&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;// re-stringify the updated JSON&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;backendValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gifts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;backendValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// prevent submit if value of form field was not changed&lt;/span&gt;
        &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitPrevented&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// set value in variable manager so that it can be sent to backend&lt;/span&gt;
        &lt;span class="nx"&gt;variableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variableValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gifts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;final&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Santa had to create all the form elements on-the-fly, and then read them back into the instance variable at the end.&lt;/p&gt;

&lt;p&gt;Now, here's the tricky bit: If you're uploading a form along with your diagram, you can't use the easy interface provided by the Modeler. You have to use a manual process. Santa, being an old-school command-line guy, used &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; “&lt;span class="se"&gt;\n&lt;/span&gt;” — cookie cookie.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; “Accept: application/json” &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"deployment-name=santa"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"enable-duplicate-filtering=false"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"deploy-changed-only=false"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"santa.bpmn=@/Users/santa/Downloads/santa.bpmn"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"ManualLetter.html=@/Users/santa/github.com/letter-to-santa/src/ManualLetter.html"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   http://santa-server.com:8080/engine-rest/deployment/create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That uploads the BPMN file and the Form to the Camunda BPM Server, and then when the manual process is called, the form shows up!&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding Links
&lt;/h2&gt;

&lt;p&gt;Being Santa, and having an entire &lt;em&gt;year&lt;/em&gt; to plan for this, you would have thought Santa could have been better prepared, but, well, the retirement decision was sort of last-minute, and the beach in Thailand was sooo nice, he sort of forgot a few details.&lt;/p&gt;

&lt;p&gt;The main detail he forgot was to create an Amazon Seller Account, which would have given him access to the product search API. With that, he could have done a much better job of searching for products, looking at the results, etc.&lt;/p&gt;

&lt;p&gt;This was not the case, alas. But thankfully one of Santa's smarter elves stepped up at the last minute and told him to just use an Amazon search URL. Next year, Santa will be more prepared for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending the Email
&lt;/h2&gt;

&lt;p&gt;Since Santa didn't really want to do, well, much of anything, even the email portion was automated.&lt;/p&gt;

&lt;p&gt;He took all the information gathered in the previous steps, and pulled it all together into a nice email to the Parents:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Seasons Greetings!&lt;/p&gt;

&lt;p&gt;Guess what? Leon has written me a letter asking for a few things. As I've now retired to a beach in Thailand, I thought maybe you'd like to know what Lean asked for. Here's the letter:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Dear Santa,&lt;/p&gt;

&lt;p&gt;My name is Leon and I'm 36 years old (yeah, I still believe in Santa 😇)&lt;/p&gt;

&lt;p&gt;This year I've been the goodest kid ever, so I kinda deserve a big present...&lt;/p&gt;

&lt;p&gt;I was thinking about a nice Lego skyline kit or the Lego New York City Skyline Kit.&lt;/p&gt;

&lt;p&gt;If that's not an option, I'd settle for some good Belgian Chocolate too!&lt;/p&gt;

&lt;p&gt;Thank you,&lt;br&gt;
Leon"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've taken the liberty of figuring out which things they want most, and provided you with a list so that you can just purchase these items directly. Don't worry, the Elves are not out of work! They're working from home to monitor all the processes. And no, they are not available for purchase.&lt;/p&gt;

&lt;p&gt;So, that list:&lt;/p&gt;

&lt;p&gt;Lego ⁉️&lt;br&gt;
Skyline Kit ⁉️&lt;br&gt;
Belgian Chocolate ❗️&lt;/p&gt;

&lt;p&gt;In case you're wondering, since I'm retired, I'm also lazy. So I've used some Artificial Intelligence (which really isn't all that intelligent) to sort of 'rate' what they asked for. I &lt;em&gt;could&lt;/em&gt; have ordered the list, but as I just told you, I'm retired, and lazy. Here's the rating system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚠️: meh.&lt;/li&gt;
&lt;li&gt;⁉️: Ok, I guess.&lt;/li&gt;
&lt;li&gt;❗: Now we're talkin!&lt;/li&gt;
&lt;li&gt;‼️: Oh please! Oh Please! Oh please!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the best from me and Mrs. Claus&lt;/p&gt;

&lt;p&gt;--&lt;br&gt;
PS: Please don't write back to this email address. I'm retired!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://write-a-letter-to-santa.org"&gt;Write your own letter!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Santa was now done. And he didn't have to lift a finger!&lt;/p&gt;
&lt;h2&gt;
  
  
  How did he do it all?
&lt;/h2&gt;

&lt;p&gt;It did take writing some code, but Santa was able to use the Camunda Golang client library to handle everything.&lt;/p&gt;

&lt;p&gt;As we saw, once the letter was submitted, the web server created a new task in Camunda and submitted it, along with all the process variables it needed to keep track of (to start with, just the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email address&lt;/code&gt; and the &lt;code&gt;letter&lt;/code&gt; itself). We've already seen how that was done.&lt;/p&gt;

&lt;p&gt;But once that was submitted as a task, how was that task handled?&lt;/p&gt;
&lt;h2&gt;
  
  
  Handling a task
&lt;/h2&gt;

&lt;p&gt;This is the technical bit. In that same Go process that handles the incoming letters (though it could have been in a completely separate process), we listen for new tasks on the &lt;code&gt;santa&lt;/code&gt; queue. Specifically, we first listen for &lt;code&gt;nlp-extraction&lt;/code&gt; tasks.&lt;/p&gt;

&lt;p&gt;First, we have to create a client for the Camunda BPM engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;EndpointUrl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"http://hostname:8080/engine-rest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c"&gt;// ApiUser:     "demo",&lt;/span&gt;
        &lt;span class="c"&gt;// ApiPassword: "demo",&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&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 we have the client, we can begin to create some processes that watch the various task queues. So for the NLP queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;WorkerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                  &lt;span class="s"&gt;"nlpProcessor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LockDuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTasks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                  &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxParallelTaskPerHandler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LongPollingTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// NLP Handler&lt;/span&gt;
    &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryFetchAndLockTopic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TopicName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"nlp-extraction"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Running task %s. WorkerId: %s. TopicName: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TopicName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sentRes&lt;/span&gt; &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Variable&lt;/span&gt;
            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
            &lt;span class="n"&gt;varb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;varb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"letter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sentRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// &amp;lt;-- **this is the important bit&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camundaclientgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"gifts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sentRes&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryComplete&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error set complete task %s: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task %s completed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&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;This process creation process is also provided by the &lt;a href="https://github.com/citilinkru/camunda-client-go/processor"&gt;Go Client&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The process is created, using the &lt;code&gt;client&lt;/code&gt; created previously, and telling the process what tasks to listen for, how long to lock the task (so no one else tries to claim and process it) and then what to &lt;strong&gt;do&lt;/strong&gt; once the task is claimed. A Camunda Client &lt;code&gt;Variable&lt;/code&gt; object is created, and then the &lt;code&gt;analyze()&lt;/code&gt; function is called.&lt;/p&gt;

&lt;p&gt;The analysis function returns the &lt;code&gt;Variable&lt;/code&gt; which has been filled out with all the parts identified. Those are all stored in a JSON object (represented by a &lt;code&gt;struct&lt;/code&gt; in Go)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Gift&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Gifts&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"gift"`&lt;/span&gt;
    &lt;span class="n"&gt;Types&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"type"`&lt;/span&gt;
    &lt;span class="n"&gt;Sentiments&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`json:"sentiment"`&lt;/span&gt;
    &lt;span class="n"&gt;Amazon&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"amazon"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the &lt;code&gt;analyze&lt;/code&gt; function completes, the &lt;code&gt;Gifts&lt;/code&gt;, &lt;code&gt;Types&lt;/code&gt; and &lt;code&gt;Sentiments&lt;/code&gt; are all filled out, but the &lt;code&gt;Amazon&lt;/code&gt; portion is empty because we haven't done that yet.&lt;/p&gt;

&lt;p&gt;Since we've completed the analysis of the letter, we take all the results, package them up into some new variables, and put everything back into the Camunda BPM engine.&lt;/p&gt;

&lt;p&gt;Of course, the next step is to create a similar process to watch for tasks on the &lt;code&gt;amazon-search&lt;/code&gt; queue. The process is really identical to the previous one, except that it listens for different task identifiers, and calls a different method to execute on the instance variables.&lt;/p&gt;

&lt;p&gt;Once the &lt;code&gt;amazon-search&lt;/code&gt; task is completed (and the &lt;code&gt;Amazon&lt;/code&gt; portion of the data structure is filled in for each &lt;code&gt;Gift&lt;/code&gt; idea), the whole thing is returned to Camunda BPM and the task is marked as completed.&lt;/p&gt;

&lt;p&gt;Which moves it on to the &lt;code&gt;email&lt;/code&gt; portion.&lt;/p&gt;

&lt;p&gt;Again, a &lt;code&gt;processor&lt;/code&gt; is defined to listen for &lt;code&gt;email&lt;/code&gt; tasks, claim them, and then compose and send the email to the recipient. Once this is done, the task is marked as completed, and returned.&lt;/p&gt;

&lt;p&gt;Finally, we have a task that stores all the &lt;code&gt;Gifts&lt;/code&gt; in a database so that Santa can see what sorts of gifts people asked for this year. He may be retired, but still needs to keep a finger on the pulse of what kids want!&lt;/p&gt;

&lt;h2&gt;
  
  
  Work Flow Completion
&lt;/h2&gt;

&lt;p&gt;This entire workflow is extremely efficient. It generally completes in a few seconds at most. It's so fast, in fact, that Santa can't even see any processes sitting around in Cockpit! Unless there's a problem. Which there won't be, because Santa doesn't want to be disturbed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Areas for improvement
&lt;/h2&gt;

&lt;p&gt;Of course the NLP part could be improved substantially. Santa simply used the free-tier of Google's Natural Language Processing engine, with zero adjustments, and took the results without any further analysis. (Need I remind you of Santa's laziness at this point?).&lt;/p&gt;

&lt;p&gt;Further, the Amazon search portion could be &lt;em&gt;much&lt;/em&gt; better with an actual Amazon Reseller account. Maybe next year.&lt;/p&gt;

&lt;p&gt;If you can think of other areas for improvement -- and there must be a lot! -- please feel free to reach out to &lt;a href="//mailto:david.simmons@camunda.com"&gt;David G. Simmons&lt;/a&gt;, Principal Developer Advocate at Camunda who was responsible for helping Santa get this entire process set up.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What in the job hopping hell?</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Mon, 12 Oct 2020 16:29:43 +0000</pubDate>
      <link>https://dev.to/davidgs/what-in-the-job-hopping-hell-n78</link>
      <guid>https://dev.to/davidgs/what-in-the-job-hopping-hell-n78</guid>
      <description>&lt;h2&gt;
  
  
  You what?
&lt;/h2&gt;

&lt;p&gt;I changed jobs. Again. Yes. I did. But there’s a good reason for it. It wasn’t that long ago that I &lt;a href="https://davidgs.com/2020/its-time-series-all-the-way-down/"&gt;posted&lt;/a&gt; about leaving InfluxData and joining &lt;a href="http://questdb.io"&gt;QuestDB&lt;/a&gt; as their Head of Developer Relations. In fact, it was only 6 months ago. And yet, here I am, once again, posting about a job change. This is, you may notice, not characteristic of me. I was at Sun for 15 years, they Riverbed for 2.5 years, then InfluxData for 2.5 years. So what’s going on?&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s going on?
&lt;/h2&gt;

&lt;p&gt;You ever get an offer you just can’t refuse? Well, I  did.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://davidgs.com/wp-content/uploads/2020/10/im-gonna-make-5yooa8.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vWtKQRlN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/10/im-gonna-make-5yooa8-250x300.jpg" alt="child in a tuxedo captioned &amp;quot;I'm going to make him an offer he can't refuse&amp;quot;" width="250" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you work in &lt;a href="http://devrelcollective.fun"&gt;Developer Relations&lt;/a&gt;, then you probably know (or know of) &lt;a href="https://twitter.com/mary_grace"&gt;Mary Thengvall&lt;/a&gt;. She quite literally wrote &lt;a href="https://www.amazon.com/dp/1484237471/ref=cm_sw_em_r_mt_dp_2eiHFbV0TNHX9"&gt;the book&lt;/a&gt; on Developer Relations. If you haven’t read it, you should. She’s also been a friend of mine for a few years (and sort of my hero in DevRel).&lt;/p&gt;

&lt;p&gt;A couple of years ago she recommended me for a job as Director of DevRel at a small German company called &lt;a href="https://camunda.com/"&gt;Camunda&lt;/a&gt;. I interviewed, and was pretty excited about it, but it sort of fizzled. And then Mary came to me to let me know that, while she had been working with them to find the Director, they had decided what they &lt;em&gt;really&lt;/em&gt; wanted was Mary herself! Duh! So she took the job and I couldn’t have been happier for her. Heck, if I had the choice between hiring me or hiring Mary, I’d have hired her too!&lt;/p&gt;

&lt;p&gt;As soon as she told me that, I said “hire me!” Of course. again, duh. But she didn’t have any openings, was just settling in to the new role, and so on.&lt;/p&gt;

&lt;p&gt;Fast forward a year (ish). Mary was hiring. For senior roles. Anyone who works in Devrel knows that senior roles don’t come around all that often, and senior roles working for Mary are a once-in-a-lifetime opportunity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://davidgs.com/wp-content/uploads/2020/10/9d069efc9e47c091c0d18d041ed863f0.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--za1zuEy8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/10/9d069efc9e47c091c0d18d041ed863f0-210x300.png" alt="David Byrne Once In a Lifetime poses" width="210" height="300"&gt;&lt;/a&gt;You’ll either get this, or you won’t.&lt;/p&gt;

&lt;p&gt;I asked, she offered, and I had to take it!&lt;/p&gt;

&lt;h2&gt;
  
  
  So what now?
&lt;/h2&gt;

&lt;p&gt;Now I’m a Principle Developer Advocate at Camunda! But wait, that’s not IoT! That’s not Time Series Databases! WTF is going on here even?&lt;/p&gt;

&lt;p&gt;You’re right. It’s none of those things. But what it is, for me, is an opportunity to learn more about Developer Relations (again, from the woman who literally wrote the book on it) in one place than I ever could any other way. It’s a way to expand my knowledge of the industry, and to learn a completely new skill (Business Process Automation — spoiler, if you dig back in my past far enough, you’ll find I did it 20 years ago!).&lt;/p&gt;

&lt;p&gt;So here we are. And I couldn’t be more excited or more pleased to be starting this new adventure!&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>evangelism</category>
      <category>jobs</category>
    </item>
    <item>
      <title>What Happens When you Put Your SQL Database on the Internet</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Thu, 25 Jun 2020 12:35:32 +0000</pubDate>
      <link>https://dev.to/davidgs/what-happens-when-you-put-your-sql-database-on-the-internet-4d9c</link>
      <guid>https://dev.to/davidgs/what-happens-when-you-put-your-sql-database-on-the-internet-4d9c</guid>
      <description>&lt;p&gt;And then we posted it to Hacker News.&lt;/p&gt;

&lt;p&gt;If you listen to, well, pretty much anyone rational, they will tell you in no uncertain terms that the last thing you ever want to do is put your SQL Database on the internet. And even if you’re crazy enough to do that, you certainly would never go post the address to it on a place like Hacker News. Not unless you were a masochist anyway.&lt;/p&gt;

&lt;p&gt;We did it though, and we’re not even a little bit sorry about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://questdb.io/"&gt;QuestDB&lt;/a&gt; has built what we think is the fastest Open Source SQL Database in existence. We really do. And we’re pretty proud of it, in fact. So much so that we wanted to give anyone that wanted the opportunity a chance to take it for a spin. With real data. Doing real queries. Almost anyone can pull together a demo that performs great under just the right conditions, with all the parameters tightly controlled.&lt;/p&gt;

&lt;p&gt;But what happens if you unleash the hordes on it? What happens if you let anyone run queries against it? Well, we can tell you, now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is
&lt;/h2&gt;

&lt;p&gt;First off, it’s a SQL-based Time Series database, built from the ground up for performance. It’s built to store and query very large amounts of data very quickly.&lt;/p&gt;

&lt;p&gt;We deployed it on an AWS &lt;code&gt;c5.metal&lt;/code&gt; server in the London, UK datacenter (sorry all you North Americans, there’s some built-in latency due to the laws of physics). It was configured with 196GB of RAM, but we were only using 40GB at peak usage. The &lt;code&gt;c5.metal&lt;/code&gt; instance provides 2 24-core CPUs (48 cores), but we only used one of them (24 cores) on 23 threads. We really weren’t using anywhere &lt;em&gt;close&lt;/em&gt; to the full potential of this AWS instance. That didn’t appear to matter at all.&lt;/p&gt;

&lt;p&gt;The data is stored on an AWS EBS volume that provides SSD access to the data. It’s not all in memory.&lt;/p&gt;

&lt;p&gt;The data is the entire &lt;a href="https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;NYC Taxi Database&lt;/a&gt; plus associated weather data. It amounts to 1.6 billion records, weighing in at about 350GB of data. That’s a lot. And it’s too much to store in-memory. It’s too much to cache.&lt;/p&gt;

&lt;p&gt;We provided some clickable queries to get people started (none of the results were cached or pre-calculated) but we essentially didn’t restrict the kinds of queries that users could run. We wanted to see, and let users see, how well this performed on whatever queries they threw at it.&lt;/p&gt;

&lt;p&gt;It was not disappointing!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hacker News Post
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A few weeks ago, we wrote about how we implemented SIMD instructions to aggregate a billion rows in milliseconds [1] thanks in great part to Agner Fog’s VCL library [2]. Although the initial scope was limited to table-wide aggregates into a unique scalar value, this was a first step towards very promising results on more complex aggregations. With the latest release of QuestDB, we are extending this level of performance to key-based aggregations.&lt;/p&gt;

&lt;p&gt;To do this, we implemented Google’s fast hash table aka “Swisstable” [3] which can be found in the Abseil library [4]. In all modesty, we also found room to slightly accelerate it for our use case. Our version of Swisstable is dubbed “rosti”, after the traditional Swiss dish [5]. There were also a number of improvements thanks to techniques suggested by the community such as prefetch (which interestingly turned out to have no effect in the map code itself) [6]. Besides C++, we used our very own queue system written in Java to parallelise the execution [7].&lt;/p&gt;

&lt;p&gt;The results are remarkable: millisecond latency on keyed aggregations that span over billions of rows.&lt;/p&gt;

&lt;p&gt;We thought it could be a good occasion to show our progress by making this latest release available to try online with a pre-loaded dataset. It runs on an AWS instance using 23 threads. The data is stored on disk and includes a 1.6billion row NYC taxi dataset, 10 years of weather data with around 30-minute resolution and weekly gas prices over the last decade. The instance is located in London, so folks outside of Europe may experience different network latencies. The server-side time is reported as “Execute”.&lt;/p&gt;

&lt;p&gt;We provide sample queries to get started, but you are encouraged to modify them. However, please be aware that not every type of query is fast yet. Some are still running under an old single-threaded model. If you find one of these, you’ll know: it will take minutes instead of milliseconds. But bear with us, this is just a matter of time before we make these instantaneous as well. Next in our crosshairs is time-bucket aggregations using the SAMPLE BY clause.&lt;/p&gt;

&lt;p&gt;If you are interested in checking out how we did this, our code is available open-source [8]. We look forward to receiving your feedback on our work so far. Even better, we would love to hear more ideas to further improve performance. Even after decades in high performance computing, we are still learning something new every day.&lt;/p&gt;

&lt;p&gt;[1] &lt;a href="https://questdb.io/blog/2020/04/02/using-simd-to-aggregate-b"&gt;https://questdb.io/blog/2020/04/02/using-simd-to-aggregate-b&lt;/a&gt;…[2] &lt;a href="https://www.agner.org/optimize/vectorclass.pdf"&gt;https://www.agner.org/optimize/vectorclass.pdf&lt;/a&gt;[3] &lt;a href="https://www.youtube.com/watch?v=ncHmEUmJZf4"&gt;https://www.youtube.com/watch?v=ncHmEUmJZf4&lt;/a&gt;[4] &lt;a href="https://github.com/abseil/abseil-cpp"&gt;https://github.com/abseil/abseil-cpp&lt;/a&gt;[5] &lt;a href="https://github.com/questdb/questdb/blob/master/core/src/main"&gt;https://github.com/questdb/questdb/blob/master/core/src/main&lt;/a&gt;…[6] &lt;a href="https://github.com/questdb/questdb/blob/master/core/src/main"&gt;https://github.com/questdb/questdb/blob/master/core/src/main&lt;/a&gt;…[7] &lt;a href="https://questdb.io/blog/2020/03/15/interthread"&gt;https://questdb.io/blog/2020/03/15/interthread&lt;/a&gt;[8] &lt;a href="https://github.com/questdb/questdb"&gt;https://github.com/questdb/questdb&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then we posted the link to the live database.&lt;/p&gt;

&lt;p&gt;And sat back.&lt;/p&gt;

&lt;p&gt;And watched the incoming traffic.&lt;/p&gt;

&lt;p&gt;And tried not to panic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;First off, we made the front page of Hacker News. Then we stayed there. For &lt;em&gt;hours&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We saw a lot of traffic. I mean a &lt;em&gt;lot&lt;/em&gt; of traffic. Somewhere over 20,000 unique IP addresses.&lt;/p&gt;

&lt;p&gt;Excluding simple &lt;code&gt;show&lt;/code&gt; queries, we saw 17,128 SQL queries with 2,485 syntax errors in the queries. We sent back almost 40GB of data in response to the queries. The response times were phenomenal. Sub-second responses to nearly all of the queries.&lt;/p&gt;

&lt;p&gt;Someone in the HN comments suggested that column stores are bad at joins, which led to someone coming in and trying to join the table to itself. Ordinarily, this would be a stunningly bad decision.&lt;/p&gt;

&lt;p&gt;The result was … not what they were expecting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IkwKhf5q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dzone.com/storage/temp/13664047-join.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IkwKhf5q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dzone.com/storage/temp/13664047-join.png" alt="" width="360" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeah, that’s 2,671,914,914,060,072,000 rows. In 69ms (including network transfer time). That’s a lot of results in a very short amount of time. Definitely not what they were expecting.&lt;/p&gt;

&lt;p&gt;We saw only a couple of bad actors try something cute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2020-06-23T20:59:02.958813Z I i.q.c.h.p.JsonQueryProcessorState [8536] exec [q='drop table trips'] 
2020-06-24T02:56:55.782072Z I i.q.c.h.p.JsonQueryProcessorState [6318] exec [q='drop *']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But those didn’t work. We may be crazy, but we’re not naive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;It turns out that when you do something like this, you learn a lot. You learn about your strengths and your weaknesses. And you learn about what users want to do with your product as well as what they will do to try to break it.&lt;/p&gt;

&lt;p&gt;Joining the table to itself was one such lesson. But we also saw a lot of folks use a &lt;code&gt;where&lt;/code&gt; clause, which caused fairly slow results. We were not entirely surprised by this result, as we are aware that we have not done the optimizations on that path to yield the fast results we want. But it was a great insight into how often it is used, and how people use it.&lt;/p&gt;

&lt;p&gt;We saw a number of people use the &lt;code&gt;group by&lt;/code&gt; clause as well, and then be surprised that it didn’t work. We probably should have warned people about that. In short, &lt;code&gt;group by&lt;/code&gt; is automatically inferred, so it’s not needed. But since it’s not implemented at all, it causes an error. So we’re looking at ways to handle that.&lt;/p&gt;

&lt;p&gt;We are taking all these lessons to heart, and making plans to address everything we saw in upcoming releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;It seems that the vast majority of the people that tried the demo were very impressed with it. The speed is truly breathtaking.&lt;/p&gt;

&lt;p&gt;Here are just a few of the comments we got in the thread:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I abused LEFT JOIN to create a query that produces 224,964,999,650,251 rows. Only 3.68ms execution time, now that’s impressive!&lt;/p&gt;

&lt;p&gt;Very cool. Major props for putting this out there and accepting arbitrary queries.&lt;/p&gt;

&lt;p&gt;Very impressive, i think building your own (performant) database from scratch is one of the most impressive software engineering feats.&lt;/p&gt;

&lt;p&gt;Very cool and impressive!! Is full PostreSQL wire compatibility on the roadmap? I like postgres compatibility &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m-Ffyb4W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s.w.org/images/core/emoji/12.0.0-1/72x72/1f642.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m-Ffyb4W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s.w.org/images/core/emoji/12.0.0-1/72x72/1f642.png" alt="🙂" width="72" height="72"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Yes, full PostgreSQL Wire Protocol is on the roadmap!)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mind blowing, did not know about questDB. The back button seems broken on chrome mobile&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, the demo did break the Back button on your browser. There’s an actual reason for that, but it is true, for now. We’re definitely addressing that one right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to try it yourself? You’ve read this far, you really should! got to &lt;a href="http://try.questdb.io:9000/"&gt;http://try.questdb.io:9000/&lt;/a&gt; to give it a whirl.&lt;/p&gt;

&lt;p&gt;We’d love to have you join us on our &lt;a href="https://serieux-saucisson-79115.herokuapp.com/"&gt;Community Slack Channel&lt;/a&gt;, give us a &lt;a href="https://github.com/questdb/questdb"&gt;Star on GitHub&lt;/a&gt;, and &lt;a href="https://questdb.io/getstarted"&gt;Download&lt;/a&gt; and try it yourself!&lt;/p&gt;

</description>
      <category>database</category>
      <category>questdb</category>
      <category>sql</category>
    </item>
    <item>
      <title>QuestDB With Python</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Mon, 15 Jun 2020 12:08:02 +0000</pubDate>
      <link>https://dev.to/davidgs/questdb-with-python-291j</link>
      <guid>https://dev.to/davidgs/questdb-with-python-291j</guid>
      <description>&lt;h1&gt;
  
  
  QuestDB Tutorial for Python
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To get started, you'll need a few things installed and set up. This should be quick. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;QuestDB: To install Questdb you can see &lt;a href="https://questdb.io/getstarted"&gt;Installation&lt;/a&gt; for complete instructions in case you want to use Docker, or &lt;code&gt;brew&lt;/code&gt; on MacOS, but the easiest way is to download the binaries and run it directly. Instructions for that are &lt;a href="https://questdb.io/docs/guideBinaries"&gt;Here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Jupyter Notebooks: That's what this is. To run it, you should:

&lt;ol&gt;
&lt;li&gt;make &lt;strong&gt;sure&lt;/strong&gt; you are running Python 3.x and &lt;em&gt;not&lt;/em&gt; Python 2.7. If you're in doubt, &lt;code&gt;python --version&lt;/code&gt; will tell you.&lt;/li&gt;
&lt;li&gt;install Jupyter Notebooks with &lt;code&gt;pip3 install --upgrade ipython jupyter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;make sure that the libraries we use in this tutorial are also installed with &lt;code&gt;pip3 install requests urlib matplotlib pandas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;clone this repository (&lt;code&gt;git clone https://github.com/davidgs/QuestNotebook&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;in the repository directory run &lt;code&gt;jupyter notebook&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That will get you right back to a page like this that is interactive, allowing you to run the code and interact with the database yourself.&lt;/p&gt;

&lt;p&gt;If you get errors like &lt;code&gt;ModuleNotFoundError: No module named 'requests'&lt;/code&gt; for any of the libraries you installed above, double-check to make sure that you are actually using Python 3.x &lt;code&gt;jupytper --path&lt;/code&gt; will let you know if Jupyter is using 2.7 or 3.x&lt;/p&gt;

&lt;h2&gt;
  
  
  Create A Database
&lt;/h2&gt;

&lt;p&gt;We will need someplace to store our data, so let's create a test database where we can put some random data.&lt;/p&gt;

&lt;p&gt;We will create a simple table with 5 columns, one of which is a &lt;code&gt;timestamp&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;The Create operation in QuestDB appends records to bottom of a table. If the table has a designated &lt;code&gt;timestamp&lt;/code&gt;, new record timestamps must be superior or equal to the latest timestamp. Attempts to add a timestamp in middle of a table will result in a timestamp out of order error.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cust_id&lt;/code&gt; is the customer identifier. It uniquely identifies customer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;balance_ccy&lt;/code&gt; balance currency. We use &lt;code&gt;SYMBOL&lt;/code&gt; here to avoid storing text against each record to save space and increase database performance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;balance&lt;/code&gt; is the current balance for customer and currency tuple.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inactive&lt;/code&gt; is used to flag deleted records.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timestamp&lt;/code&gt; timestamp in microseconds of the record. Note that if you receive the timestamp data as a string, it could also be inserted using &lt;code&gt;to_timestamp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This should return a &lt;code&gt;200&lt;/code&gt; status the first time you run it. If you run it more than once, subsequent runs will return &lt;code&gt;400&lt;/code&gt; because the database already exists.&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;par&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create table balances&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(cust_id int,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; balance_ccy char,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;balance double,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inactive boolean,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp timestamp)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;\
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp(timestamp)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/exec?query=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate some Data
&lt;/h2&gt;

&lt;p&gt;Since we have a new setup, we should add some data to QuestDB so that we can have something to query. We will add some random data, for now. &lt;/p&gt;

&lt;p&gt;You can re-run this section as many times as you want to add 100 entries at a time, or simply change the &lt;code&gt;range(100)&lt;/code&gt; to add as many datapoints as you wish.&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;€&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;£&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;¥&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;235.15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getrandbits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insert into balances values(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cust&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;\
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,systimestamp())&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/exec?query=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rows inserted: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rows Failed: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Query Data from QuestDB
&lt;/h2&gt;

&lt;p&gt;Now that we have data available, let's try querying some of it to see what we get back!&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/exp?query=select * from  balances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rawData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Read the content into Pandas Dataframe
&lt;/h2&gt;

&lt;p&gt;So you'll notice that the returned data is just a massive &lt;code&gt;csv&lt;/code&gt; string. If you'd rather have &lt;code&gt;json&lt;/code&gt; data, then you would change the endpoint to &lt;code&gt;http://localhost:9000/exec ...&lt;/code&gt; But since we're going to use Pandas to frame our data, we'll stick with &lt;code&gt;csv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We are also telling pandas to parse the &lt;code&gt;timestamp&lt;/code&gt; field as a date. This is important since we're dealing with Time Series data.&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;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;pData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;parse_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Narrow the search
&lt;/h2&gt;

&lt;p&gt;That's just getting us &lt;em&gt;all&lt;/em&gt; the data, but let's narrow the search using some SQL clauses. Let's look for a specific &lt;code&gt;cust_id&lt;/code&gt; and only balances of that customer that are in $s. We are also only interested in times the customer was &lt;code&gt;active&lt;/code&gt; Since this is SQL, you can make this query as simple, or as complex, as you'd like.&lt;/p&gt;

&lt;p&gt;Since all of the data was generated randomly, this exact query may return no results, so you may have to adjust the &lt;code&gt;cust_id&lt;/code&gt; below until you get results back. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;**Note:&lt;/em&gt;* The query string &lt;em&gt;must&lt;/em&gt; be URL-encoded before it is sent.&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;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;select cust_id,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; balance,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; balance_ccy,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; inactive,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; from balances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; where cust_id = 26&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; and balance_ccy = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;\
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; and not inactive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/exp?query=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;queryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="n"&gt;rawData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;parse_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Plot the data
&lt;/h2&gt;

&lt;p&gt;We will use &lt;code&gt;matplotlib&lt;/code&gt; to plot the data&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="n"&gt;matplotlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;subplots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, with any luck, you'll get a great little plot like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4R4JcofB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/tim9sfiwptz3p4mhp3ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4R4JcofB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/tim9sfiwptz3p4mhp3ps.png" alt="QuestDB Data Plot" width="506" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean up
&lt;/h2&gt;

&lt;p&gt;Now we will clean everything up for the next time.&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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/exec?query=drop table balances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database Table dropped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database Table not Dropped: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're done! If you liked this tutorial, please give us a ⭐ on &lt;a href="https://github.com/questdb/questdb"&gt;Github&lt;/a&gt; and be sure to &lt;a href="https://twitter.com/intent/follow?screen_name=questdb"&gt;follow us&lt;/a&gt; on twitter!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A QuestDB Dashboard with Node-Red</title>
      <dc:creator>David G. Simmons</dc:creator>
      <pubDate>Tue, 09 Jun 2020 12:31:58 +0000</pubDate>
      <link>https://dev.to/davidgs/a-questdb-dashboard-with-node-red-524h</link>
      <guid>https://dev.to/davidgs/a-questdb-dashboard-with-node-red-524h</guid>
      <description>&lt;p&gt;This is really a follow-on to my &lt;a href="https://davidgs.com/2020/iot-on-questdb/"&gt;post&lt;/a&gt; from last week where I connected an Arduino with a temperature and humidity sensor to QuestDB.&lt;/p&gt;

&lt;p&gt;It’s one thing to send data to your database, but being able to visualize that data is the next logical step. So let’s dive right in to doing that.&lt;/p&gt;

&lt;p&gt;QuestDB is rather new, and hence we haven’t completed our Grafana Data Source Plugin yet, so I wanted to make a quick dashboard to show the incoming temperature/humidity data (and you’ll see just how awful the sensor really is). To do this, I chose Node-Red because, well, it seems the obvious choice! &lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Nodes:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--itBOmCF3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.38.57-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--itBOmCF3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.38.57-AM.png" alt="Screen Shot 2020 06 09 at 7 38 57 AM" title="Screen Shot 2020-06-09 at 7.38.57 AM.png" width="714" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it uses only a few nodes, so I’ll walk through them one by one. &lt;/p&gt;

&lt;p&gt;The initial node is an injector node that fires at a regular, configurable interval. Mine fires every 10 seconds just to keep from being too noisy. It triggers the &lt;code&gt;SetQuery&lt;/code&gt; node which builds the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var q = {}
q["query"] = "select temp_c, humidity, timestamp from iot where timestamp &amp;gt; (systimestamp() - 5000000)" 
msg.payload = q
return msg;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I set the payload to a query, in this case, I’m getting the temperature and the humidity for the past 5 seconds (remember, we are dealing with microsecond timestamps, so 5 seconds is 5M microseconds). I then send that query, as the payload, to an http request node that I’ve called Query QuestDB. I’ve set the host to be my local machine, the URL to the query API endpoint, and I append the incoming msg.payload to the URL. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DMd3P1lg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.51.26-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DMd3P1lg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.51.26-AM.png" alt="Screen Shot 2020 06 09 at 7 51 26 AM" title="Screen Shot 2020-06-09 at 7.51.26 AM.png" width="505" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The query returns a a JSON string, so I’ll need to run it through a JSON Node to turn it into a JSON Object. I then send the result of that JSON-parsing to 2 additional nodes, one for temperature and one for Humidity. After the JSON Parsing, I get an object back that has several things in it I want to go through. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kr2MtiUV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.57.42-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kr2MtiUV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.57.42-AM.png" alt="Screen Shot 2020 06 09 at 7 57 42 AM" title="Screen Shot 2020-06-09 at 7.57.42 AM.png" width="301" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing to note is that the payload contains a &lt;code&gt;query&lt;/code&gt; field that shows the query I executed. Cool! Next, I get a &lt;code&gt;columns&lt;/code&gt; field that is an array with an entry for each column if data I am getting back. Since I queried for &lt;code&gt;temp_c&lt;/code&gt;, &lt;code&gt;humidity&lt;/code&gt; and &lt;code&gt;timestamp&lt;/code&gt; I would expect this array to have 3 elements in it, and indeed it does. It also tells me, in each element, the name, and the type of value it has returned which is helpful information. &lt;/p&gt;

&lt;p&gt;Finally, there is a &lt;code&gt;dataset&lt;/code&gt; field which contains a array of arrays with my data that I requested. Since I requested 5 seconds worth of data, and, if you remember from the &lt;a href="https://davidgs.com/2020/iot-on-questdb/"&gt;previous post&lt;/a&gt;, I was sending data once per second, I get back an array with 5 arrays in it, one for each second. By expanding these arrays, I see that I have gotten 2 doubles and a timestamp in each one corresponding to what the &lt;code&gt;columns&lt;/code&gt; field told me I would get. Nice! So all that’s left is to send that data to some dashboard elements. Well, almost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var f = {}
f.topic = "Temperature ºC"
f.payload = msg.payload.dataset[msg.payload.dataset.length-1][0]
f.timestamp = msg.payload.dataset[msg.payload.dataset.length-1][2]
return f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;code&gt;Set Temp&lt;/code&gt; node, I pull the last element out of the dataset, and grab the temperature value and the timestamp value. I then send those on, as the payload, to the actual Dashboard elements. I do the exact same thing for the &lt;code&gt;Set Humidity&lt;/code&gt; Node. By dragging in the dashboard nodes, Node-Red automatically sets up a web dashboard with these elements, and I can go to it and see my new dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wedubQOS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.39.13-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wedubQOS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://davidgs.com/wp-content/uploads/2020/06/Screen-Shot-2020-06-09-at-7.39.13-AM.png" alt="Screen Shot 2020 06 09 at 7 39 13 AM" title="Screen Shot 2020-06-09 at 7.39.13 AM.png" width="670" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that you can actually visualize the data, you can see how awful the data really is! There is no way it’s 2.3º C in my office right now! I guess my next task is to get a  &lt;strong&gt;real&lt;/strong&gt; temperature and humidity sensor set up to send more accurate data! Lucky for me, I have a few of those lying around, so that will have to be my next project I guess. &lt;/p&gt;

&lt;h2&gt;
  
  
  We’re Done Here
&lt;/h2&gt;

&lt;p&gt;As always, please visit our &lt;a href="https://github.com/questdb/questdb"&gt;GitHub&lt;/a&gt; and give us a star if you think this was useful! You can &lt;a href="https://twitter.com/intent/follow?screen_name=davidgsIoT"&gt;follow me&lt;/a&gt; on twitter, but also follow &lt;a href="https://twitter.com/intent/follow?screen_name=questdb"&gt;QuestDB&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>database</category>
      <category>gadgetry</category>
      <category>iot</category>
      <category>dashboard</category>
    </item>
  </channel>
</rss>
