<?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: Rob Reid</title>
    <description>The latest articles on DEV Community by Rob Reid (@robreid).</description>
    <link>https://dev.to/robreid</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%2F287343%2F8a8c49df-a95c-4fda-ada1-1ce4ffb381a0.JPG</url>
      <title>DEV Community: Rob Reid</title>
      <link>https://dev.to/robreid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robreid"/>
    <language>en</language>
    <item>
      <title>CockroachDB's Online Schema Changes make modifying foreign keys safe and terror-free!</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 27 Feb 2023 16:02:28 +0000</pubDate>
      <link>https://dev.to/robreid/cockroachdbs-online-schema-changes-make-modifying-foreign-keys-safe-and-terror-free-1akp</link>
      <guid>https://dev.to/robreid/cockroachdbs-online-schema-changes-make-modifying-foreign-keys-safe-and-terror-free-1akp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wcbjeexuwqfv067utrc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wcbjeexuwqfv067utrc.png" alt=" " width="800" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the background, CockroachDB will create a new version of your data, to ensure that existing queries can still be serviced.&lt;/p&gt;

&lt;p&gt;This means you can make scary-looking changes without fear of downtime or customer impact 💪&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
    </item>
    <item>
      <title>CockroachDB's Online Schema Changes make modifying primary keys safe and terror-free!</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Thu, 23 Feb 2023 14:46:04 +0000</pubDate>
      <link>https://dev.to/robreid/cockroachdbs-online-schema-changes-make-modifying-primary-keys-safe-and-terror-free-6db</link>
      <guid>https://dev.to/robreid/cockroachdbs-online-schema-changes-make-modifying-primary-keys-safe-and-terror-free-6db</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fnJB8N0S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwf77awu6jtv5f0xq30m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fnJB8N0S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwf77awu6jtv5f0xq30m.png" alt="Image description" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the background, CockroachDB will create a new version of your data, to ensure that existing queries can still be serviced.&lt;/p&gt;

&lt;p&gt;This means you can make scary-looking changes without fear of downtime or customer impact 💪&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>My favourite backend tools</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Thu, 24 Jun 2021 20:47:57 +0000</pubDate>
      <link>https://dev.to/robreid/my-favourite-backend-tools-9ka</link>
      <guid>https://dev.to/robreid/my-favourite-backend-tools-9ka</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97o27wyr9uncw73bbnwd.jpg" 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%2F97o27wyr9uncw73bbnwd.jpg" alt="Tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I spend a lot of my time as a software developer writing backend services and infrastructure and wanted to share some of the tools I use most regularly.&lt;/p&gt;

&lt;p&gt;I'm always in the market for new tools, so please feel free to add your favourite tools in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://golang.org" rel="noopener noreferrer"&gt;golang&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/avelino/awesome-go" rel="noopener noreferrer"&gt;awesome&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go is a fantastic language for web server development. It's standard library is very comprehensive, meaning you can do pretty much everything you'd want to do with &lt;em&gt;just&lt;/em&gt; the standard library (although there are plenty of packages that have already solved a lot of common challenges).&lt;/p&gt;

&lt;p&gt;Spinning up a web server is as simple as:&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;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&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;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hello&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;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;":8080"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;hello&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&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;"Hello, World!"&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;...although you should not use &lt;code&gt;http.ListenAndServe&lt;/code&gt; when developing applications destined for production, as it does not configure sensible timeout defaults.&lt;/p&gt;

&lt;p&gt;Go's tooling is as comprehensive as its standard library, meaning you can test, benchmark, profile, and generate coverage of your code just as easily as you wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://crystal-lang.org" rel="noopener noreferrer"&gt;crystal&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/veelenga/awesome-crystal" rel="noopener noreferrer"&gt;awesome&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Crystal is another great programming language for backend development. It's very concise, very declarative, and very fast.&lt;/p&gt;

&lt;p&gt;Crystal allows a developer to write very terse code to achieve what would typically be much more verbose in other programming languages. For example, here's a web service handler using Crystal’s most well-known web framework, &lt;a href="https://kemalcr.com" rel="noopener noreferrer"&gt;kemal&lt;/a&gt;. It accepts the id of a person from the URL path, queries a database for them using their is, and returns their first and last name as JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/people/:id"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt; &lt;span class="s2"&gt;"select first_name, last_name from person where id = $1 limit 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://replit.com" rel="noopener noreferrer"&gt;replit&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Replit started out as an online IDE but thanks to the team's ridiculous efficiency in getting out new releases, it's become a completely game-changing platform.&lt;/p&gt;

&lt;p&gt;With Replit, you can create websites, web services, games, and more, in any language you can think of (including esoteric ones). I use Replit to host long-running websites with custom domain names; all linked to GitHub. Everything's super easy and I think the world of online hosting is going to be disrupted by this awesome website.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.benthos.dev" rel="noopener noreferrer"&gt;benthos&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Benthos is a declarative stream processor that makes mundane tasks fun and allows you to achieve ridiculous levels of productivity. It's under active development with new components (inputs, processors, and outputs etc.) being added regularly.&lt;/p&gt;

&lt;p&gt;It can run anywhere and can be configured statically with YAML files or dynamically with &lt;a href="https://www.benthos.dev/docs/guides/streams_mode/about" rel="noopener noreferrer"&gt;Streams Mode&lt;/a&gt;. The following example consumes from 2 Kafka topics and writes messages from both into a Cassandra table:&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;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;localhost:9092&lt;/span&gt;
    &lt;span class="na"&gt;topics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;topic_one&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;topic_two&lt;/span&gt;
    &lt;span class="na"&gt;consumer_group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_benthos_app&lt;/span&gt;

&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cassandra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;localhost:9042&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INSERT INTO my_keyspace.my_table (id, event, created_at) VALUES (?, ?, ?)&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${! json("id") }&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${! json("event") }&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${! json("timestamp").format_timestamp() }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://kubernetes.io" rel="noopener noreferrer"&gt;kubernetes&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Kubernetes is a tool that's synonymous with the deployment of scalable and reliable application workloads. It's a huge tool that'll likely offer more than most people will need but it's very powerful, has a huge community, and allows you to quickly and reliably scale and serve your applications.&lt;/p&gt;

&lt;p&gt;I use Kubernetes to run all sorts of application workloads and regularly make use of its ability to integrate with cloud providers to transparently create things like load balancers.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.nomadproject.io" rel="noopener noreferrer"&gt;nomad&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nomad is an awesome alternative to Kubernetes. It's hugely scalable, allowing you to schedule many different types of workload including Docker and Podman containers, raw executables, and Java applications (to name a few).&lt;/p&gt;

&lt;p&gt;Unlike Kubernetes, applications can (and should) be scheduled onto machines that are not part of the orchestration cluster nodes. This allows Nomad to scale to millions of running applications, whereas Kubernetes best practices recommend a limit of &lt;a href="https://kubernetes.io/docs/setup/best-practices/cluster-large/" rel="noopener noreferrer"&gt;150,000 pods and 300,000 containers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I use Nomad to orchestrate IoT workloads but it can be used for many more use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/wg/wrk" rel="noopener noreferrer"&gt;wrk&lt;/a&gt; / &lt;a href="https://github.com/rakyll/hey" rel="noopener noreferrer"&gt;hey&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;wrk and hey are amazing HTTP benchmarking CLIs. They allow you to write very concise commands to benchmark web services, offering higher speeds and concurrency levels than &lt;a href="https://k6.io/blog/comparing-best-open-source-load-testing-tools" rel="noopener noreferrer"&gt;alternative tools&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;wrk is the fastest available (by a large margin) and can be scripted with Lua. Although it doesn't run on Windows without the use of WSL (Windows Subsystem for Linux). I tend to use wrk when I'm on my Mac and hey when I'm on Windows machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://k6.io" rel="noopener noreferrer"&gt;k6&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;k6 is a fantastic Open Source load testing tool that can be scripted with JavaScript.&lt;/p&gt;

&lt;p&gt;You can configure very complex load testing scenarios with large numbers of VUs (Virtual Users) to put your web services under realistic smoke, load, stress, and soak scenarios.&lt;/p&gt;

&lt;p&gt;I use k6 for anything more complicated than simply hammering a single endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;terraform&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform is a de-facto standard for deploying infrastructure with code. It has official support for many cloud providers and services and can be extended with custom providers depending on your needs.&lt;/p&gt;

&lt;p&gt;I use Terraform for managing infrastructure. It's not without its issues but once you're aware of them, it's a very productive tool that frequently leaves me in awe.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://prometheus.io" rel="noopener noreferrer"&gt;prometheus&lt;/a&gt; / &lt;a href="https://grafana.com" rel="noopener noreferrer"&gt;grafana&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Prometheus is a tool for collecting metric data from applications. You expose an endpoint from your application (e.g. /metrics), point Prometheus at it, and simply write metrics; all using the Prometheus client libraries that are available for most major programming languages.&lt;/p&gt;

&lt;p&gt;Grafana is a great tool for graphing metrics. It can read data from lots of different metric/time-based backends (including Prometheus and InfluxDB etc.) and can be configured to raise alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ngrok creates "secure introspection tunnels to localhost", which - put simply - allows you to expose your machine to the outside world, even if it's behind a firewall.&lt;/p&gt;

&lt;p&gt;The following command will expose any application running on port 8080 on your machine to the outside world. It will generate 2 unique URLs (one for HTTP and one for HTTPS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ngrok http 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use ngrok for testing local services against external services like applications that publish web hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.postman.com" rel="noopener noreferrer"&gt;postman&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Postman is a tool for testing web servers. It's feature-rich and allows you to test HTTP and GraphQL services with minimal effort.&lt;/p&gt;

&lt;p&gt;You can also use Postman as a documentation and code-generation tool (create a request, then generate code for lots of different tools and programming languages).&lt;/p&gt;

&lt;p&gt;I use Postman for testing both HTTP and GraphQL services, saving collections of request for each service for reproducible tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/derailed/k9s" rel="noopener noreferrer"&gt;k9s&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;k9s is an awesome CLI for interacting with Kubernetes.&lt;/p&gt;

&lt;p&gt;With k9s you can view pods, jump into their shells, view their logs, port forward, and delete them. You also have a lot of options for managing deployments, stateful sets, services and many other Kubernetes constructs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/fullstorydev/grpcurl" rel="noopener noreferrer"&gt;grpcurl&lt;/a&gt; / &lt;a href="https://github.com/fullstorydev/grpcui" rel="noopener noreferrer"&gt;grpcui&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;grpcurl and grpcui are great tools for working with gRPC services. To start, simply create a gRPC service with reflection enabled and open either of the tools. You'll now be able to call the endpoints just like you would with tools like cURL or Postman.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://tableplus.com" rel="noopener noreferrer"&gt;table plus&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;TablePlus is a great tool for working with a lot of database engines including Postgres, CockroachDB, MySQL, SQLServer, Cassandra, MongoDB, Redis, and more.&lt;/p&gt;

&lt;p&gt;Having the ability to work with so many data stores makes developing any kind of database-backed service very straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/codingconcepts/datagen" rel="noopener noreferrer"&gt;datagen&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A tool from me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;datagen is a tool for generating lots of random relational data quickly. You write a template file using a mixture of SQL and &lt;a href="https://golang.org/pkg/text/template" rel="noopener noreferrer"&gt;Go templates&lt;/a&gt; to create multi-row DML (Data Manipulation Language), or more simply, batches of INSERT statements.&lt;/p&gt;

&lt;p&gt;Outputs from one datagen insert (e.g, the output from a RETURNING or a follow-up SELECT statement) can be used as the input for the next, allowing you to build up related data across multiple tables.&lt;/p&gt;

&lt;p&gt;I use datagen for generating monstrous amounts of data for testing with while developing, as the best time to find issues with database performance and integration is during development!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/codingconcepts/setcfg" rel="noopener noreferrer"&gt;setcfg&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A tool from me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;setcfg allows you to substitute values in one YAML file with values from another YAML file like so:&lt;/p&gt;

&lt;p&gt;Input file:&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;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~region~&lt;/span&gt;

&lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~username~&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~password~&lt;/span&gt;

&lt;span class="na"&gt;brokers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~broker-1~&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~broker-2~&lt;/span&gt;

&lt;span class="na"&gt;subnet_cidrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~subnet-cidrs~&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config file:&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;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;

&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supersecret&lt;/span&gt;

&lt;span class="na"&gt;broker-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8001&lt;/span&gt;

&lt;span class="na"&gt;broker-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8002&lt;/span&gt;

&lt;span class="na"&gt;subnet-cidrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1.2.3.0/25&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1.2.3.128/25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running setcfg with these two files (&lt;code&gt;setcfg -i input.yaml -e config.yaml&lt;/code&gt;) will result in the following, which can be piped into a file or a &lt;code&gt;kubectl&lt;/code&gt; command, depending on the type of input file you’re using:&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;brokers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8001&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8002&lt;/span&gt;
&lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supersecret&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
&lt;span class="na"&gt;subnet_cidrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1.2.3.0/25&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1.2.3.128/25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use this for working with things like Kubernetes manifests and Benthos configuration files, although it'll happily work with any YAML file.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/codingconcepts/pa55" rel="noopener noreferrer"&gt;pa55&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A tool from me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;pa55 is a simple CLI for generating cryptographically secure passwords and copying them to the clipboard. It can generate passwords of any length and output them in ascii, base32, base64, and hexadecimal.&lt;/p&gt;

&lt;p&gt;I use pa55 for generating passwords for session keys, and SaaS solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/codingconcepts/see" rel="noopener noreferrer"&gt;see&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A tool from me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;see is a simple CLI that does what the Linux &lt;a href="https://en.wikipedia.org/wiki/Watch_(command)" rel="noopener noreferrer"&gt;watch command&lt;/a&gt; does but for any platform.&lt;/p&gt;

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

&lt;p&gt;A great tool can not only help you to become more efficient but can also help to make mundane tasks fun. If you find a tool that you love, make sure to support it with a star on GitHub and possibly a contribution!&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>backend</category>
      <category>kubernetes</category>
      <category>sql</category>
    </item>
    <item>
      <title>Crystal's Channels and the While Loop</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Sat, 04 Jan 2020 08:32:22 +0000</pubDate>
      <link>https://dev.to/robreid/crystal-s-channels-and-the-while-loop-4jc</link>
      <guid>https://dev.to/robreid/crystal-s-channels-and-the-while-loop-4jc</guid>
      <description>&lt;p&gt;Like most, I like to learn a new programming language by doing. At the moment, I'm enjoying the catharsis of completing &lt;a href="https://rosettacode.org/wiki/Category:Unimplemented_tasks_by_language"&gt;unimplemented Rosetta Code tasks&lt;/a&gt; in &lt;a href="https://crystal-lang.org"&gt;Crystal&lt;/a&gt; and &lt;a href="https://vlang.io"&gt;vlang&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During some exploration of the Crystal programming language, I took on the &lt;a href="https://rosettacode.org/wiki/Synchronous_concurrency#Crystal"&gt;Synchronous Concurrency&lt;/a&gt; task to pour some of my knowledge of the &lt;a href="https://en.wikipedia.org/wiki/Communicating_sequential_processes"&gt;CSP&lt;/a&gt; pattern from Go, into Crystal.&lt;/p&gt;

&lt;p&gt;The task asks you to communicate between multiple threads of execution within a process:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One of the concurrent units will read from a file named "input.txt" and send the contents of that file, one line at a time, to the other concurrent unit, which will print the line it receives to standard output. The printing unit must count the number of lines it prints. After the concurrent unit reading the file sends its last line to the printing unit, the reading unit will request the number of lines printed by the printing unit. The reading unit will then print the number of lines printed by the printing unit.&lt;br&gt;&lt;br&gt;&lt;br&gt;
This task requires two-way communication between the concurrent units. All concurrent units must cleanly terminate at the end of the program.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My original solution to the task was as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;c"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;spawn&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ClosedError&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal run example.cr
a
b
c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Crystal docs &lt;a href="https://crystal-lang.org/api/0.32.1/Channel.html"&gt;here&lt;/a&gt; and &lt;a href="https://crystal-lang.org/reference/guides/concurrency.html"&gt;here&lt;/a&gt; are fairly light on Channels, so there was an element of trial and error involved. I knew that a call to &lt;code&gt;receive&lt;/code&gt; against a closed channel would result in a raised &lt;code&gt;Channel::ClosedError&lt;/code&gt; so I made use of this in a try/catch (begin/rescue) block.&lt;/p&gt;

&lt;p&gt;I'm a big fan of &lt;a href="https://golang.org"&gt;Go&lt;/a&gt;, so much prefer to handle errors over catching exceptions. I wasn't satisfied with this solution and wanted to see if I could make Crystal's type system work to my advantage.&lt;/p&gt;

&lt;p&gt;Crystal's type system makes use of &lt;a href="https://crystal-lang.org/api/0.32.1/Union.html"&gt;Union&lt;/a&gt; types, allowing a variable to be one or more types at compile time. For example, because &lt;code&gt;a&lt;/code&gt; is initialised in both arms of the if statement in the following example, it can be either a &lt;code&gt;String&lt;/code&gt; or an &lt;code&gt;Int32&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="n"&gt;a&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;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; (Int32 | String)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's interesting about Union types in the context of Channels, is &lt;code&gt;Nil&lt;/code&gt;. There's another version of the &lt;code&gt;receive&lt;/code&gt; method, that instead of raising an exception when a channel is closed, it returns nil. It's called &lt;code&gt;receive?&lt;/code&gt; and its definition can be found &lt;a href="https://github.com/crystal-lang/crystal/blob/master/src/channel.cr#L271"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Initially, I simply swapped out the called to &lt;code&gt;receive&lt;/code&gt; with &lt;code&gt;receive?&lt;/code&gt; and re-ran my code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;c"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;spawn&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive?&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ClosedError&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was expecting the call to &lt;code&gt;receive?&lt;/code&gt; to block indefinitely after reading the last line from "input.txt" but was surprised to see the program output exactly what it had done in the original example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal run example.cr
a
b
c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confused, I added a log line into the &lt;code&gt;rescue&lt;/code&gt; arm to see if a &lt;code&gt;Channel::ClosedError&lt;/code&gt; exception had been thrown anyway (despite what the Crystal source told me) and re-ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ClosedError&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal run example.cr
a
b
c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No exception!?&lt;/p&gt;

&lt;p&gt;Then it dawned on my that the &lt;code&gt;while&lt;/code&gt; loop must be working to &lt;a href="https://crystal-lang.org/reference/syntax_and_semantics/truthy_and_falsey_values.html"&gt;truthy&lt;/a&gt; values and ignoring the line break!&lt;/p&gt;

&lt;p&gt;I rewrote my code to this new expectation, removing the begin/rescue block and the line break, inlining the declaration of &lt;code&gt;line&lt;/code&gt;, and relying on nil from the fourth call to &lt;code&gt;receive?&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;c"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;spawn&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive?&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal run example.cr
a
b
c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! I've learned something new about Crystal by fumbling around in the dark.&lt;/p&gt;

&lt;p&gt;The following demonstrates this succinctly by reading from the array elements until nil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>crystal</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>CockroachDB via Crystal ... In under 50 lines</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 30 Dec 2019 22:09:07 +0000</pubDate>
      <link>https://dev.to/robreid/cockroachdb-via-crystal-in-under-50-lines-3266</link>
      <guid>https://dev.to/robreid/cockroachdb-via-crystal-in-under-50-lines-3266</guid>
      <description>&lt;p&gt;I've been getting rather excited about the &lt;a href="https://crystal-lang.org"&gt;Crystal&lt;/a&gt; programming language recently and I've finally gotten round to creating a very simple CockroachDB-backed Crystal API!&lt;/p&gt;

&lt;p&gt;In this example, I'm going to create a very contrived API to manage todos. When the API is up and running, you'll be able to list the todos stored in the database, find a todo by its ID, and add new ones. These use cases lend themselves nicely to demonstrating 3 of the &lt;a href="https://crystal-lang.github.io/crystal-db/api/0.4.0/DB/QueryMethods.html"&gt;DB::QueryMethods&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to install Crystal, bring in the dependencies we'll need to create our API and walk you through the code that'll give us the following endpoints:&lt;/p&gt;

&lt;p&gt;POST /todos &lt;br&gt;&lt;br&gt;
GET /todos &lt;br&gt;&lt;br&gt;
GET /todos/:id&lt;/p&gt;
&lt;h5&gt;
  
  
  Setting things up
&lt;/h5&gt;

&lt;p&gt;First, install Crystal. There are a number of ways to do this listed on their &lt;a href="https://crystal-lang.org/install"&gt;install&lt;/a&gt; page. I use macos, so will keep things Mac-specific for this post but you'll be able to follow along using the information on their website if you're on a different platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew update
$ brew install crystal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your installation by running the &lt;code&gt;crystal&lt;/code&gt; executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal version
Crystal 0.32.1 (2019-12-18)

LLVM: 9.0.0
Default target: x86_64-apple-macosx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a project for our code to go into. This can all be doing using Crystal's CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ crystal init app api
    create  api/.gitignore
    create  api/.editorconfig
    create  api/LICENSE
    create  api/README.md
    create  api/.travis.yml
    create  api/shard.yml
    create  api/src/api.cr
    create  api/spec/spec_helper.cr
    create  api/spec/api_spec.cr
Initialized empty Git repository in /Users/me/crystal/learning/api/.git/
$ cd api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, create a database for our application to connect to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cockroach demo --nodes=1 --empty
...
root@127.0.0.1:64044/defaultdb&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the terminal prompt resulting from a successful call to this command will be the address of the cluster we'll be connect to. In this case, "127.0.0.1:64044".&lt;/p&gt;

&lt;p&gt;Using the resulting terminal prompt, we can create a table to house our data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@127.0.0.1:64044/defaultdb&amp;gt; create table "todo" (
    "id" uuid primary key default uuid_v4()::uuid,
    "value" text not null
);
CREATE TABLE

Time: 3.213ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  The components
&lt;/h5&gt;

&lt;p&gt;Crystal's standard library - like the standard libraries of most other programming languages - doesn't contain specific database drivers. We'll need a Postgres driver in order to talk to CockroachDB. According to the Crystal &lt;a href="https://crystal-lang.org/reference/database"&gt;docs&lt;/a&gt;, &lt;a href="https://github.com/will/crystal-pg"&gt;will/crystal-pg&lt;/a&gt; is the one to use.&lt;/p&gt;

&lt;p&gt;Crystal's dependency manager is called &lt;code&gt;Shards&lt;/code&gt; and in your api directory, the &lt;code&gt;crystal init&lt;/code&gt; command would have created a file called &lt;code&gt;shard.yml&lt;/code&gt; for you. Into that file, add the following to reference the Postgres driver as a dependency:&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;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;will/crystal-pg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run the following to pull in the dependencies listed above. Note that it's possible to specify versions for your dependencies but for this example, the latest version will be fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ shards install
Fetching https://github.com/will/crystal-pg.git
Fetching https://github.com/crystal-lang/crystal-db.git
Using pg (0.20.0)
Using db (0.8.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than rolling my own API routing code, I'm going to use the well-known &lt;a href="https://kemalcr.com"&gt;Kemal&lt;/a&gt; web framework. It creates &lt;a href="https://crystal-lang.org/reference/syntax_and_semantics/macros.html"&gt;macros&lt;/a&gt; that result in very simple and elegant API code. Bring in this dependency in the same way you brought in the database driver, by adding it to the &lt;code&gt;dependencies&lt;/code&gt; section of your &lt;code&gt;shard.yml&lt;/code&gt; file:&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;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kemal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kemalcr/kemal&lt;/span&gt;
  &lt;span class="na"&gt;pg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;will/crystal-pg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ shards install
Fetching https://github.com/kemalcr/kemal.git
Fetching https://github.com/luislavena/radix.git
Fetching https://github.com/jeromegn/kilt.git
Fetching https://github.com/crystal-loot/exception_page.git
Fetching https://github.com/will/crystal-pg.git
Fetching https://github.com/crystal-lang/crystal-db.git
Using kemal (0.26.1)
Using radix (0.3.9)
Using kilt (0.4.0)
Using exception_page (0.1.2)
Using pg (0.20.0)
Using db (0.8.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  The code
&lt;/h5&gt;

&lt;p&gt;All of our code will go into src/api.cr. First, we'll want to reference our dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"kemal"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"pg"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reference to &lt;code&gt;kemal&lt;/code&gt; will allow us to use the fancy HTTP macros and start the server. The reference to &lt;code&gt;db&lt;/code&gt; (a dependency automatically required by our explicit &lt;code&gt;crystal-pg&lt;/code&gt; dependency) will allow us to make calls to the database. The reference to &lt;code&gt;pg&lt;/code&gt; brings in the Postgres driver and provides a helper method we can use to connect to CockroachDB. The reference to &lt;code&gt;json&lt;/code&gt; (in the standard library) will allow us to respond to callers in JSON.&lt;/p&gt;

&lt;p&gt;Next, we open a connection to the database with the connection information we obtained when starting the CockroachDB cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt; &lt;span class="s2"&gt;"postgres://root@localhost:64044/defaultdb?sslmode=disable"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the code clean, I create a &lt;code&gt;Todo&lt;/code&gt; class to store the information on the todos being dealt with by the API. In this class definition, there's a constructor, which provides shortcut syntax that creates two instance variables; "id" and "value" and a JSON.mapping function, which tells Crystal how the Todo class is to be serialised to and deserialised from JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;value:  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kemal provides some very cool macros which are listed in the &lt;a href="https://kemalcr.com/guide"&gt;guide&lt;/a&gt;. I'm using a "filter" macro called "before_all", which - as you'll probably guess - gets invoked before all endpoint calls, allowing me to jump into the web call flow and inject a response header to ensure the caller always gets a "Content-Type" of "application/json":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;before_all&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Onto the handlers!&lt;/p&gt;

&lt;h6&gt;
  
  
  POST /todos
&lt;/h6&gt;

&lt;p&gt;Let's start by getting some todos into the database. We'll need a POST endpoint for that, and the "post" function created from a macro in Kemal is just the thing. Kemal captures the request and the convention for that, is &lt;code&gt;|env|&lt;/code&gt;. From env, we can extract stuff from the JSON body.&lt;/p&gt;

&lt;p&gt;Once inserted into the database, we'll want the returned ID, so the &lt;code&gt;scalar&lt;/code&gt; function is perfect for this, as we'll return it from the INSERT statement. Once we've got the ID, we can build up a simple JSON object to return to the caller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/todos"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"insert into todo (value) values ($1) returning id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  GET /todos
&lt;/h6&gt;

&lt;p&gt;Next, we'll create an endpoint to view all of the todos in the database. This time we don't need to capture the user's request and will use the &lt;code&gt;query&lt;/code&gt; function to fetch all of the todos with a ResultSet, building up an array of todos before returning the JSON representation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/todos"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt; &lt;span class="s2"&gt;"select id, value from todo"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;id&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;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&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;todos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  GET /todos/:id
&lt;/h6&gt;

&lt;p&gt;Finally, we'll make use of the &lt;code&gt;query_one&lt;/code&gt; function to fetch a single user. To narrow down the search, we'll capture the ID of the todo the user wants to find and use it when querying the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/todos/:id"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nb"&gt;id&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt; &lt;span class="s2"&gt;"select id, value from todo where id = $1 limit 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&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;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice that the todo's ID is already provided, so I could have used the &lt;code&gt;scalar&lt;/code&gt; function and return just the value of the todo but in a real-world scenario, there will likely be more columns to return and &lt;code&gt;query_one&lt;/code&gt; is the correct function for that.&lt;/p&gt;

&lt;h5&gt;
  
  
  Kemal
&lt;/h5&gt;

&lt;p&gt;The only remaining thing to do now, is kick off the Kemal server. To do that, we run the &lt;code&gt;Kemal.run&lt;/code&gt; function. By default, Kemal will listen on port 3000 but a custom port can be provided:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Kemal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="mi"&gt;1234&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, Kemal will log all of the requests it receives. To disable this, run the following instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;logging&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="no"&gt;Kemal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="mi"&gt;1234&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Testing
&lt;/h5&gt;

&lt;p&gt;Create a todos using the POST /todos endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X POST \
  http://localhost:1234/todos \
  -H 'Content-Type: application/json' \
  -d '{
    "value": "Productionise todo API"
}'
{"id":"e548f471-5772-473b-b7d2-39e18c57d483"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetch all of the todos using the GET /todos endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X GET \
  http://localhost:1234/todos \
  -H 'Content-Type: application/json' \
  -d '{
    "value": "Productionise todo API"
}'
[{"id":"e548f471-5772-473b-b7d2-39e18c57d483","value":"Productionise todo API"}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetch the todo we created with the GET /todos/:id endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X GET \
  http://localhost:1234/todos/e548f471-5772-473b-b7d2-39e18c57d483 \
  -H 'Content-Type: application/json' \
  -d '{
    "value": "Productionise todo API"
}'
{"id":"e548f471-5772-473b-b7d2-39e18c57d483","value":"Productionise todo API"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Kemal server would have been logging these requests as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2019-12-30 21:31:02 UTC 200 POST /todos 2.15ms
2019-12-30 21:33:17 UTC 200 GET /todos 922.05µs
2019-12-30 21:34:06 UTC 200 GET /todos/e548f471-5772-473b-b7d2-39e18c57d483 1.52ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm still learning the Crystal ropes, so will continue to blog about the language as I stumble across new things!&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>crystal</category>
      <category>api</category>
    </item>
    <item>
      <title>The Ethical Cloud</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 09 Dec 2019 16:29:44 +0000</pubDate>
      <link>https://dev.to/robreid/the-ethical-cloud-3hc3</link>
      <guid>https://dev.to/robreid/the-ethical-cloud-3hc3</guid>
      <description>&lt;p&gt;On October 17th, I gave a talk at the inaugural east-cost multi-cloud conference, &lt;a href="https://escapeconference.io"&gt;ESCAPE&lt;/a&gt; in Manhattan, organised and hosted by &lt;a href="https://cockroachlabs.com"&gt;Cockroach Labs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to deliver this talk for a number of reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tech industry isn't talking about these issues as much as it should be.&lt;/li&gt;
&lt;li&gt;There are very few tech conferences where this kind of talk would be encouraged.&lt;/li&gt;
&lt;li&gt;I know next to nothing about the subject and there's no greater kick up the arse to learn something than commit to speaking at a conference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I work at &lt;a href="https://lush.com"&gt;Lush&lt;/a&gt;, a company that's committed to having a positive impact on the world and its inhabitants.&lt;/p&gt;

&lt;p&gt;In my talk, I covered the environmental, data privacy, and human impacts of cloud computing. Each of the 3 sections included information on what Lush are doing in this area (limited to stuff that's relevant to the talk), along with suggestions to the attendees on what they could do.&lt;/p&gt;

&lt;p&gt;In my opinion, this stuff is too easily ignored and too readily forgotten.&lt;/p&gt;

&lt;h4&gt;
  
  
  Environment
&lt;/h4&gt;

&lt;p&gt;I opened my talk with the following question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who's in the cloud?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Around 80% of hands went up. Not unsurprising for a cloud conference! I then asked the particpants to keep their hands up if they knew:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much of your cloud provider's energy is renewable?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every single hand went down. No one knew how much of their cloud provider's energy usage was green. This was validation that this talk needed to happen and threw into sharp focus, the fact that the environment can be an afterthought for all of us in the tech industry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Internet is likely to be the biggest thing we create as a species - &lt;a href="https://www.greenpeace.org/international/publication/6826/clicking-clean-2017/"&gt;&lt;em&gt;Greenpeace&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Greenpeace are doing a lot of great work in this area, they publish reports detailing the major cloud and managed service providers and their energy impacts. To me, this quote highlights the scale of the impact the cloud will have if we don't help and push our providers to acheive 100% renewable energy soon.&lt;/p&gt;

&lt;p&gt;As of 2019, cloud &lt;a href="https://hostingtribunal.com/blog/cloud-adoption-statistics"&gt;spending&lt;/a&gt; is around ~$200B. By 2022, that's going to increase to around $1.3T, a six-fold increase to an industry that already uses 3% of our globally available power and produces as much emissions as the entire aviation industry.&lt;/p&gt;

&lt;p&gt;To put some figures to this, global data centre power consumption is around 100B kWh. Amazingly, 30-40% of this consumption goes to cooling to them. Google are raising the temperature in their data centres to reduce the amount of power they use on cooling. The &lt;a href="http://www.airpacinc.com/blog/bid/83313/How-Google-Saved-1-Billion-Dollars-in-Data-Center-Cooling-Costs"&gt;result&lt;/a&gt;? They now direct around just 10% of their total power consumption to cooling, saving energy and billions of dollars in the process.&lt;/p&gt;

&lt;h5&gt;
  
  
  Virginia
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://www.greenpeace.org/usa/reports/click-clean-virginia"&gt;Virginia&lt;/a&gt; is a major state for data centres. Many of the world's largest providers have a massive presence in the area. One provider in particular ramped up its operations by 60% in the past 2 years alone. All in a state whose available renewable energy represents just 4% of its total available energy, putting it 38th in the rankings for greenest state.&lt;/p&gt;

&lt;p&gt;There are neighbouring states with better access to renewable energy!&lt;/p&gt;

&lt;p&gt;Sadly, new fossil fuel infrastructure is being built to cater for this demand and it's coming in the form of the 42" Atlantic Coast Pipeline, delivering massive amounts of gas to the power-hungry data centres. This represents a huge step backwards in our critically important need to be sustainable.&lt;/p&gt;

&lt;h5&gt;
  
  
  Our clouds need your help.
&lt;/h5&gt;

&lt;p&gt;Employees from Amazon, Google, and Microsoft are campaigning for their clouds to act faster in going green and they need our help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/amznforclimate?lang=en"&gt;Amazon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/googlewac?lang=en"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/msworkers4?lang=en"&gt;Microsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like Lush, these groups all walked out on September 20th in support of the global climate crisis strikes and we need everyone to join us, show support, and tell the world that its biggest and most influencial companies have big concerns for the future of our planet.&lt;/p&gt;

&lt;p&gt;The 3 big cloud providers publish reports that detail their aspirations for 100% renewable energy consumption (along with their progress) and while you should try to independently validate the claims being made, they make for interesting reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sustainability.aboutamazon.com/"&gt;Amazon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sustainability.google/reports/"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.microsoft.com/en-us/corporate-responsibility/sustainability"&gt;Microsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What Lush are doing
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Being picky - a few years ago, we &lt;a href="https://cloud.google.com/customers/lush"&gt;moved all of our workloads&lt;/a&gt; from one cloud provider to another (in 22 days, over Christmas, the team are incredible). One of the reasons we did this was to align ourselves with a cloud provider whose environmental ethics closely matched ours.&lt;/li&gt;
&lt;li&gt;Asking our managed service providers to do the same - we don't shy away from asking those who provide us with a managed service if they'd be prepared to switch clouds to join us. They might not be able to (and that's OK) but we think it's important to ask.&lt;/li&gt;
&lt;li&gt;Minimising over-provisioning - we've taken a look at our infrastructure and where we're being wasteful. The outcome of this, is a project to reduce our production estate by around one third; maximising our utilisation of the cloud and having the happy side-effect of saving us a lot of money in the process.&lt;/li&gt;
&lt;li&gt;Redesigning our applications for efficiency - we're committed to using best-in-class software to make sure that our applications run as efficiently as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What you can do
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Non-cloud users - consider moving into the cloud! There are massive efficiencies to being in the cloud due to the hardware available and the virtualisation technologies used. Even if your concerns aren't for the environment, allow it to be a side-effect of the decisions you make.&lt;/li&gt;
&lt;li&gt;Choose your provider carefully - some clouds appear to be in a period of growth-at-any-cost, which is resulting in a balooning demand for fossil fuels, while others are on a commit course to being greener.&lt;/li&gt;
&lt;li&gt;Consider the consequencies of over-provisioning your estate - beyond simply looking at it from a cost perspective. Wherever you're over-provisioning, there's power (and money) being wasted.&lt;/li&gt;
&lt;li&gt;Speak with your managed service providers - ask them to jump to a greener cloud. They might not be able/prepared to but hopefully it'll start them thinking about the issue as well.&lt;/li&gt;
&lt;li&gt;Optimise your cloud use - the 3 big cloud providers provide tools to help you get the best from their services, with suggestions like reducing instance sizes for workloads that never stress them:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/07/introducing-amazon-ec2-resource-optimization-recommendations/"&gt;Amazon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/compute/docs/instances/apply-sizing-recommendations-for-instances"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/cost-management/tutorial-acm-opt-recommendations"&gt;Microsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Data privacy
&lt;/h4&gt;

&lt;p&gt;I opened this segment up by asking a series of questions to gauge how much the audience knew about the implications of data privacy laws on their customers' privacy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who’s familiar with the 4th amendment?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Around 5% of the audience were familiar with the 4th amendment, which centres around:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I then asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who's familiar with the Third Party Doctrine?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One hand remained in the air. Before giving this talk, I would have been among the vast majority of people with my hand in my lap. The Third Party Doctrine states that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People who voluntarily give information to third parties such as banks, phone companies, Internet Service Providers, and email servers have no "reasonable expectation of privacy."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There have been a number of court cases seeking to establish a legal precedent and they demonstrate how challenging this law can be to navigate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Smith_v._Maryland"&gt;Smith v. Maryland 1979&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/United_States_v._Miller_(1976)"&gt;United States v. Miller 1976&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the case of Smith v. Maryland, Michael Lee Smith was deemed not to have a legitimate expectation of privacy because he'd shared the number he was calling with the phone company, simply by dialling it. His phone record was therefore available to be used against him during his prosecution.&lt;/p&gt;

&lt;p&gt;Don't forget that the doctrine also relates to Internet Service Providers and therefore the data we share with our cloud providers.&lt;/p&gt;

&lt;p&gt;I then asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who’s familiar with the CLOUD Act?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One hand again (a different one this time) and as with the last question, I would have been in the vast majority before reseaching for this talk.&lt;/p&gt;

&lt;p&gt;CLOUD stands for Clarifying Lawful Overseas Use of Data and was signed into law by President Trump on March 23rd 2018 without being debated in congress. It:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Allows federal law enforcement to compel US-based technology companies via warrant or subpoena to provide requested data stored on servers regardless of whether the data are stored in the US or foreign soil.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the CLOUD act, the legal protection of an individual's right to privacy depends on an objection from a provider (some clouds will more readily object than others). There's no direct mechanism for individuals to challenge an order under the CLOUD Act.&lt;/p&gt;

&lt;p&gt;US courts can require production of data despite an objection, even where the laws of another nation would be violated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Three can keep a secret if two of them are dead - &lt;em&gt;Benjamin Franklin&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  House Bill 57
&lt;/h5&gt;

&lt;p&gt;House Bill (HB) 57 was signed into law in Utah and states that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A law enforcement agency may not obtain, without a search warrant issued by a court upon probably cause:&lt;br&gt;
&lt;br&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;
i. location, stored, or transmitted mobile data.&lt;br&gt;
&lt;br&gt;ii. data transmitted to a remote service provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Probable cause is key here and its remit of data covers our modern technology landscape, representing a great win for Utah and a big step in the right direction for data privacy.&lt;/p&gt;

&lt;p&gt;So far it's only Utah and I've not ventured too far down the rabbit hole with how &lt;a href="https://epic.org/privacy/nsl"&gt;National Security Letters&lt;/a&gt; (reseach them, they're terrifying) would affect someone's privacy in the face of an objection via HB57.&lt;/p&gt;

&lt;h5&gt;
  
  
  Encryption-at-rest
&lt;/h5&gt;

&lt;p&gt;I then asked the audience:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who relies on cloud-provided encryption-at-rest to protect their customers’ data?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Around 5% of people had their hands up this time. Which surprised me, given encryption-at-rest is a core part of data security in the cloud.&lt;/p&gt;

&lt;p&gt;Most people will get encryption-at-rest for free from their cloud providers. It ensures that any data stored within the cloud will be encrypted before being persisted and adds a level of security to your data in the event that it is stolen.&lt;/p&gt;

&lt;p&gt;Unfortunately for your data (and those in my audience who weren't aware their data was being encrypted to start with), by default, encryption-at-rest will use a Data Encryption Key (DEK) that's generated by the cloud provider. If a government requests data, it'll be the data and the DEK that gets provided to them. Bye bye privacy!&lt;/p&gt;

&lt;h5&gt;
  
  
  Transparency reports
&lt;/h5&gt;

&lt;p&gt;The 3 big cloud providers publish reports that detail the number of requests for data they've received from the goverment, along with the number of request they've responded to with customer data, or objected to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://d1.awsstatic.com/certifications/Information_Request_Report_June_2019.pdf"&gt;Amazon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://transparencyreport.google.com/user-data/overview?hl=en_GB"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.microsoft.com/en-us/corporate-responsibility/law-enforcement-requests-report"&gt;Microsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What Lush are doing:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Keeping stored customer data to an absolute minimum - our user's privacy is a primary concern for us. Their right to privacy always trumps our need to store their data and it has done so for so long that it's part of our core work ethic.&lt;/li&gt;
&lt;li&gt;Asking questions - we don't have all of the answers. This is a legal minefield and we're certainly not experts in navigating it! We've spoken with our cloud provider's technical and legal teams to learn more.&lt;/li&gt;
&lt;li&gt;Considering domiciling sensitive data in countries with stricter data protection laws - as with cloud providers, some countries will more readily hand over your data when asked. &lt;a href="https://www.privacypolicies.com/blog/privacy-law-by-country/"&gt;This site&lt;/a&gt; details the countries whose data privacy laws will best protect your privacy and the privacy of your customers.&lt;/li&gt;
&lt;li&gt;Looking at user-generated DEKs and KEKs (Key Encryption Keys, keys that are used to encrypt your DEKs, providing another level of security and an ability to change or "rotate" your keys without having to re-encrypt your data) - ensures that any data handed over to a government will be unintelligible. It takes the fight for our customer's privacy into our hands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What can you do?
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Don't store what you don't need - if you can give your customers a great experience without storing all of their information, don't store all of their information!&lt;/li&gt;
&lt;li&gt;Understand the legal minefield - speak with your cloud provider's technical and legal teams if you have concerns; they're there to help.&lt;/li&gt;
&lt;li&gt;Be picky - some cloud providers will more readily protect your privacy and the privacy of your customers, so consider this before deciding on your cloud partner.&lt;/li&gt;
&lt;li&gt;Take the fight into your own hands - generate your own DEKs and KEKs to add another level of protection to your data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Human cost
&lt;/h4&gt;

&lt;p&gt;I opened up my last segment with the following question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is familiar with 3TGs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No hands up. Again, I would have had no idea what this was before researching for my talk.&lt;/p&gt;

&lt;p&gt;It stands for the 4 registered conflict minerals; tin (cassiterite), tantalum (coltan), tungsten (wolframite), and gold. A conflict mineral is any mineral whose mining proceeds fund violent armed groups.&lt;/p&gt;

&lt;h5&gt;
  
  
  Mining
&lt;/h5&gt;

&lt;p&gt;One of the hardest hit nations for mineral mining and the conflict their proceeds generate is The Democratic Republic of the Congo (DRC). In the DRC alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.4m people have been killed since 1998 as a result of conflict funded by mineral mining.&lt;/li&gt;
&lt;li&gt;4.5m people have been displaced and 800k people have had to seek refuge in other nations as a result of conflict.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mind-blowing $24T worth of minerals are estimated to remain in the ground in the DRC, which shows how desperate the need for fair treatment and safe working conditions for the artisinal miners, along with a carefully controlled supply chain to prevent mining proceeds from getting into the wrong hands is.&lt;/p&gt;

&lt;h5&gt;
  
  
  Production
&lt;/h5&gt;

&lt;p&gt;China has some of the largest technology assembly facilities in the world and as of the time of writing, workers in these facilities can be subject to appalling conditions and exploitation.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.china-briefing.com/news/labor-dispatch-in-china-definition-scope-and-limit/"&gt;law&lt;/a&gt; exists within China that stipulates that a given percentage of a workforce must be permanently employed (as opposed to temporary or "labour dispatch"). Large companies outside of China are turning a blind eye to massive &lt;a href="https://www.bloomberg.com/news/articles/2019-09-09/apple-foxconn-broke-a-chinese-labor-law-for-iphone-production"&gt;violations&lt;/a&gt; in this law which are reducing job stability for people working in these facilities. The law states that no more than 10% of a workforce can be labour dispatch employees. In reality, they make up 50% of some workforces.&lt;/p&gt;

&lt;p&gt;Another &lt;a href="https://ins-globalconsulting.com/china-labor-law-guide/china-working-overtime-pay-policy-1-minute/"&gt;law&lt;/a&gt; exists within China to prevent people from being forced to work too many hours of overtime in any given month. This too is being completely vioated, with some workers having to work 4 hours in overtime every &lt;em&gt;day&lt;/em&gt; to keep up with the expectations on them. Well over twice the legal limit. Not only are the overtime limits being violated but the law also states how overtime must be paid. Sadly, people are being forced to work overtime on weekdays and weekends without pay.&lt;/p&gt;

&lt;p&gt;Following a spate of suicides in one of the world's largest assembly facilities, perhaps most famous and influential tech leader of the 21st century &lt;a href="https://www.theguardian.com/technology/2017/jun/18/foxconn-life-death-forbidden-city-longhua-suicide-apple-iphone-brian-merchant-one-device-extract"&gt;responded&lt;/a&gt; that the suicide rate in the facility was within the national average.&lt;/p&gt;

&lt;p&gt;Should we as leaders in technology not be aspiring to give our people a safe and happy working environment by looking after their physical and mental wellbeing?&lt;/p&gt;

&lt;h5&gt;
  
  
  Post-production
&lt;/h5&gt;

&lt;p&gt;Once a piece of hardware reaches end-of-life it becomes what's commonly referred to as e-waste and needs to be recycled.&lt;/p&gt;

&lt;p&gt;Unfortunately, recycling isn't the fate for around 80% of e-waste, which is illegally traded or dumped. To put this into perspective, that's 50M tonnes, the area of Manhattan, the weight of 126 Empire State Buildings, or all of the commercial aircraft we've ever built, dumped. Every year. And it's worth a staggering $62B.&lt;/p&gt;

&lt;p&gt;Worst of all, deadly amounts of mercury, lead, and beryllium are &lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3908467"&gt;leaching&lt;/a&gt; into the air, soil, and groundwater in these e-waste dumps. &lt;a href="https://theecologist.org/2014/aug/07/e-waste-ghana-where-death-price-living-another-day"&gt;Children&lt;/a&gt; are growing up being exposed to chemicals that will irriversibly affect their bodies. Agbobloshie in Ghana now ranks as the &lt;a href="https://www.bbc.co.uk/news/science-environment-24994209"&gt;most polluted place in the world&lt;/a&gt;, beating even Chernobyl to the top spot.&lt;/p&gt;

&lt;p&gt;Ghana's imports of this toxic waste are set to double in 2020.&lt;/p&gt;

&lt;h5&gt;
  
  
  Closing the loop
&lt;/h5&gt;

&lt;p&gt;It's 13x cheaper to extract minerals from old hardware than it is to mine for new ones. And it generates 80% less emissions.&lt;/p&gt;

&lt;p&gt;There are some amazing things happening to increase extraction efforts the world over but we're just scratching the surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In 2015, Apple &lt;a href="https://www.theverge.com/2016/4/15/11438840/apple-recovered-nearly-40-million-in-gold-through-its-recycling"&gt;recycled&lt;/a&gt; around 1 tonne of gold worth $40M and around 1,300 tonnes of copper worth $6M.&lt;/li&gt;
&lt;li&gt;People all over Japan gave up their &lt;a href="https://www.businessinsider.com/tokyo-2020-olympic-paralympic-medals-made-from-old-phones-gadgets-2019-7?r=US&amp;amp;IR=T"&gt;unused phones&lt;/a&gt;. The gold, silver, and bronze from these devices was enough to create the medals for the 2020 olympics. In total, around 30k of gold, over 3 tonnes of silver, and over 2 tonnes of bronze were extracted, worth a collective $3M.&lt;/li&gt;
&lt;li&gt;Websites like &lt;a href="http://e-stewards.org/data/list-recyclers/"&gt;e-Stewards&lt;/a&gt; list recyclers who are committed to never exporting hazardous materials to developing countries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What Lush are doing
&lt;/h5&gt;

&lt;p&gt;We're looking into building our own ethical tablet, considering the fair treatment and payment of everyone involved, be they producing the minerals or components, or assembling it. In doing so, we hope to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Influence - we're sharing our experiences and trying to lead by example.&lt;/li&gt;
&lt;li&gt;Create transparency - or as much as we're able to on the origins of materials and components, by visiting people in assembly facilities and e-waste dumps to see for ourselves.&lt;/li&gt;
&lt;li&gt;Ask questions - we're asking the kinds of questions that people have never been asked, simply because these things have never been a priority for other companies before.&lt;/li&gt;
&lt;li&gt;Partner up - the more people actively working on this, the better.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  What can you do?
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Cloud users - ask your provider what they're doing to to protect people across the whole production lifecycle. They're too wealthy to not be accountable for this.&lt;/li&gt;
&lt;li&gt;Hardware producers - pay your people fairly and give them safe and happy conditions to work in and others will be forced to do the same. Understand your supply chain - don't rely on the goodwill of other companies. Go to see for yourself to understand the conditions that people of all ages are being forced to live and work in.&lt;/li&gt;
&lt;li&gt;Hardware consumers - know where your products or components are coming from across the whole supply chain.&lt;/li&gt;
&lt;li&gt;Close the loop - billions of dollars worth of minerals are dumped every year. There is more gold in 1 tonne of &lt;a href="https://www.bbc.com/future/article/20161017-your-old-phone-is-full-of-precious-metals"&gt;old phones&lt;/a&gt; than there is in 1 tonne of gold ore. Rather than mining for new minerals, we can recycle and reduce the amount of e-waste that gets dumped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Closing
&lt;/h4&gt;

&lt;p&gt;The Internet is growing. There are around 4.5B people online now, with around 1M new users every &lt;em&gt;day&lt;/em&gt;. Collectively, we spend around 1.2B &lt;em&gt;years&lt;/em&gt; online, that's 100 days per person per year.&lt;/p&gt;

&lt;p&gt;Despite what some world leaders would have you believe, the world is warming and desperately needs help before we run out of time and completely destroy our planet.&lt;/p&gt;

&lt;p&gt;Whether it's artisinal miners in the DRC, or people assembling hardware, people are truly suffering in ways that most of us can't imagine. And it's all because of our desire to grow the Internet and the businesses that hang from it.&lt;/p&gt;

&lt;p&gt;It's not just about securing the cheapest price. We can't afford to blindly focus on cost while the decisions we make to extend our business reach damage the planet and the beings that inhabit it.&lt;/p&gt;

&lt;p&gt;The is no higher-power shouldering our personal responsibility for the impact we'll have. Unless we all act now, this is on us.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's our job to know and once we know, it's our responsibility to act. - &lt;em&gt;Lush&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>cloud</category>
      <category>ethics</category>
    </item>
    <item>
      <title>Geo-partitioning. No column? No problem!</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 09 Dec 2019 16:26:31 +0000</pubDate>
      <link>https://dev.to/robreid/geo-partitioning-no-column-no-problem-ob4</link>
      <guid>https://dev.to/robreid/geo-partitioning-no-column-no-problem-ob4</guid>
      <description>&lt;p&gt;CockroachDB's &lt;a href="https://www.cockroachlabs.com/docs/v19.1/topology-geo-partitioned-replicas.html"&gt;Geo-partitioning&lt;/a&gt; allows you to partition data using location information like country codes. If you're wanting to partition your data by-country, your data structure will likely already have the necessary components to do so. For example, the following table lends itself to being partitioned on its "country" column, meaning rows containing a country code of 'DE', 'FR', and 'UK' can be pinned to European data centres, while rows containing a country column of 'US' can be pinned to North American data centres etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;uuid_v4&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"primary"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;ASC&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;h5&gt;
  
  
  No column?
&lt;/h5&gt;

&lt;p&gt;The data we have available to use might not always be in a structure that allows for straightforward partitioning. Suppose that instead of a top-level column, our country information was buried in a semi-structured JSON blob as follows:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happily, CockroachDB allows you to include &lt;a href="https://www.cockroachlabs.com/docs/stable/computed-columns.html"&gt;computed columns&lt;/a&gt; in your primary keys, meaning we can &lt;a href="https://www.cockroachlabs.com/docs/stable/inverted-indexes.html"&gt;lift&lt;/a&gt; the "country" field out of our JSON structure and partition with that!&lt;/p&gt;

&lt;p&gt;To spin up an example database to work with, I'll be making use of v19.2.0's updates to the &lt;a href="https://www.cockroachlabs.com/docs/dev/cockroach-demo.html"&gt;demo command&lt;/a&gt;, which allow it to spin up an in-memory, multi-node, geo-partitioned cluster 🎉. The following command spins up an empty 6-node multi-region cluster, with 3 nodes on the east coast and 3 nodes in west Europe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cockroach demo \
    --empty \
    --nodes 6 \
    --demo-locality=region=us-east1,az=1:region=us-east1,az=2:region=us-east1,az=3:region=europe-west1,az=1:region=europe-west1,az=2:region=europe-west1,az=3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo command spins up an enterprise cluster, meaning we can view the cluster in &lt;a href="https://www.cockroachlabs.com/docs/stable/enable-node-map.html"&gt;node map&lt;/a&gt; view to get a sense of where data is being stored.&lt;/p&gt;

&lt;h5&gt;
  
  
  No problem!
&lt;/h5&gt;

&lt;p&gt;This time in our CREATE statement, we include a column that takes the country field out of the JSON data and creates a computed column from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;uuid_v4&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'country'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"primary"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;ASC&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;What we're asking CockroachDB to do, is look for "data.country" in the JSON blob, lift it out as a string, create a standard column from it, and include that in the primary key. Our table from a partitioning perspective, is now equivalent to the first example.&lt;/p&gt;

&lt;p&gt;To know we've successfully partitioned our table, let's insert some rows to observe where they're being stored once the partitions have taken effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"data": {"country": "DE", "name": "Alice"}}'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"data": {"country": "FR", "name": "Bob"}}'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"data": {"country": "UK", "name": "Carol"}}'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"data": {"country": "US", "name": "Eve"}}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure a partition to split the data into 2 regions, one for the US and one for Europe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;LIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="n"&gt;us&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'US'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="n"&gt;eu&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'UK'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="n"&gt;us&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="n"&gt;CONFIGURE&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'[+region=us-east1]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="n"&gt;eu&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"person"&lt;/span&gt; &lt;span class="n"&gt;CONFIGURE&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'[+region=europe-west1]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the following to view how data is being replicated. It'll show that your US data is being pinned to your us-east1 region, while your DE, FR, and UK is being pinned to your europe-west1 region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;start_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replica_localities&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;end_key&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%/PrefixEnd'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;start_key&lt;/th&gt;
&lt;th&gt;replicas&lt;/th&gt;
&lt;th&gt;replica_localities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/"US"&lt;/td&gt;
&lt;td&gt;{1,2,3}&lt;/td&gt;
&lt;td&gt;{"region=us-east1,az=1","region=us-east1,az=2","region=us-east1,az=3"}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/"FR"&lt;/td&gt;
&lt;td&gt;{4,5,6}&lt;/td&gt;
&lt;td&gt;{"region=europe-west1,az=1","region=europe-west1,az=2","region=europe-west1,az=3"}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/"UK"&lt;/td&gt;
&lt;td&gt;{4,5,6}&lt;/td&gt;
&lt;td&gt;{"region=europe-west1,az=1","region=europe-west1,az=2","region=europe-west1,az=3"}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/"DE"&lt;/td&gt;
&lt;td&gt;{4,5,6}&lt;/td&gt;
&lt;td&gt;{"region=europe-west1,az=1","region=europe-west1,az=2","region=europe-west1,az=3"}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
US data is pinned to region us-east1 (replicas 1, 2, 3), while DE, FR, and UK data is pinned to europe-west1 (replicas 4, 5, 6).&lt;/p&gt;

&lt;p&gt;Note that this example assumes that the &lt;code&gt;person&lt;/code&gt; table does not yet exist. If it were to exist, you wouldn't be able to update the table's primary key to include information from the JSON data without first creating a new table (with an updated primary key) and copying over the data as described &lt;a href="https://www.cockroachlabs.com/docs/stable/constraints.html#change-constraints"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>cockroachdb</category>
    </item>
    <item>
      <title>CockroachDB Demo Locations</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 09 Dec 2019 16:20:01 +0000</pubDate>
      <link>https://dev.to/robreid/cockroachdb-demo-locations-2fal</link>
      <guid>https://dev.to/robreid/cockroachdb-demo-locations-2fal</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.cockroachlabs.com/blog/get-started-geo-partitioning-data-with-our-command-line-cockroachdb-demo/"&gt;&lt;code&gt;cockroach demo&lt;/code&gt;&lt;/a&gt; command is my current favourite dev tool. It's instant access to an N-node CockroachDB cluster, allowing you to get up and running with an enterprise cluster straight from your dev environment, reducing feedback loops massively by giving you all the features you need to create with confidence.&lt;/p&gt;

&lt;p&gt;If you start a cluster with &lt;code&gt;cockroach demo&lt;/code&gt;, and don't provide any arguments for &lt;code&gt;--demo-locality&lt;/code&gt;, you'll get the default values that are provided by the &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/cli/demo.go#L73"&gt;demo code&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;--nodes&lt;/th&gt;
&lt;th&gt;Regions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;us-east1 x1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;us-east1 x2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;us-east1 x3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x3 + europe-west1 x1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x3 + europe-west1 x2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;us-east1 x3 + us-west1 x3 + europe-west1 x3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
The increase is cyclical, with every additional node for the first 3 additional nodes, adding to the us-east1 region and so on.&lt;/p&gt;

&lt;p&gt;Currently, only the us-east1, us-west1, and europe-west1 regions have &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/cli/demo.go#L107"&gt;locations&lt;/a&gt; configured, so if you'd like spin up nodes in other regions and have them represented in the cluster map, it's a very simple two-step process:&lt;/p&gt;
&lt;h5&gt;
  
  
  Step 1 - create your cluster with custom regions
&lt;/h5&gt;

&lt;p&gt;The following spins up a 10-node cluster across 3 regions; us-central1 (4 AZs), asia-northeast1 (3 AZs), and europe-west1 (3 AZs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cockroach demo \
--empty \
--nodes 10 \
--demo-locality=region=us-central1,az=a:region=us-central1,az=b:region=us-central1,az=c:region=us-central1,az=f:region=asia-northeast1,az=a:region=asia-northeast1,az=b:region=asia-northeast1,az=c:region=europe-west1,az=a:region=europe-west1,az=b:region=europe-west1,az=c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you open the cluster map in the web UI now, you'll see a message that explains that not all of your regions have been assigned locations. In the knowledge that only a handful of regions come pre-loaded with location information, we can infer that the asia-northeast1 region needs to be given a location.&lt;/p&gt;

&lt;p&gt;This can be confirmed by running the following SQL in your console session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;localityKey&lt;/th&gt;
&lt;th&gt;localityValue&lt;/th&gt;
&lt;th&gt;latitude&lt;/th&gt;
&lt;th&gt;longitude&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;europe-west1&lt;/td&gt;
&lt;td&gt;50.448160000000000&lt;/td&gt;
&lt;td&gt;3.818860000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;us-central1&lt;/td&gt;
&lt;td&gt;42.032974000000000&lt;/td&gt;
&lt;td&gt;-93.581543000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;us-east1&lt;/td&gt;
&lt;td&gt;33.836082000000000&lt;/td&gt;
&lt;td&gt;-81.163727000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;us-east4&lt;/td&gt;
&lt;td&gt;37.478397000000000&lt;/td&gt;
&lt;td&gt;-76.453077000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;us-west1&lt;/td&gt;
&lt;td&gt;43.804133000000000&lt;/td&gt;
&lt;td&gt;-120.554201000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Helpfully, the cluster map provides a &lt;a href="https://www.cockroachlabs.com/docs/v19.2/enable-node-map.html#location-coordinates-for-reference"&gt;link&lt;/a&gt; to docs that will help you assign locations to your regions. You can follow the docs, or continue to read to find out how this can be done.&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 2 - assign localities to custom regions
&lt;/h5&gt;

&lt;p&gt;Insert location information for the asia-northeast1 region by running the following SQL in your console session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'region'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'asia-northeast1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;689487&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;691706&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you refresh your node map now, you'll see a 9-node cluster, with 3 regions configured in the locations you've configured.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>cockroachdb</category>
    </item>
    <item>
      <title>CockroachDB Integration Testing</title>
      <dc:creator>Rob Reid</dc:creator>
      <pubDate>Mon, 09 Dec 2019 16:13:46 +0000</pubDate>
      <link>https://dev.to/robreid/cockroachdb-integration-testing-2lkg</link>
      <guid>https://dev.to/robreid/cockroachdb-integration-testing-2lkg</guid>
      <description>&lt;p&gt;Testing is vitally important to the success of software and software that connects to databases is no exception. There are a number of ways to test database code, ranging from mocks, to full integration tests that talk to an actual database.&lt;/p&gt;

&lt;p&gt;When writing mocks, I've had some great experiences with Data Dog's &lt;a href="https://github.com/DATA-DOG/go-sqlmock"&gt;sqlmock&lt;/a&gt; and I'd recommend that you check it out. In this post however, I'm going to devle into the steps involved in integration testing a CockroachDB database using &lt;code&gt;docker-compose&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All code can be found in &lt;a href="https://github.com/codingconcepts/cockroachdb-testing"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Test code
&lt;/h5&gt;

&lt;p&gt;Firstly, let's go over the test code. It's super contrived but hopefully gets the point across. If anything's not clear, I'd be happy to discuss, send me a message on &lt;a href="https://twitter.com/go_robreid"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I use the &lt;a href="https://golang.org/pkg/testing/#hdr-Main"&gt;&lt;code&gt;TestMain&lt;/code&gt;&lt;/a&gt; method to bootstrap my tests, which allows me to capture command line arguments and gives my tests a logical place to setup and teardown database connections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.philosophicalhacker.com/post/integration-tests-in-go/"&gt;This post&lt;/a&gt; gives a lovely introduction to using Go's flags with your tests and I'll be using lessons from there to determine whether or not to run database integration tests in this example.&lt;/p&gt;

&lt;p&gt;My simple test code uses two global variables (the horror!) and depending on your test directory structure, you might want to do this differently. One variable holds the flag value that will determine whether database integration tests are run and the other holds a reference to the database connection itself.&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;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dbTests&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TestMain&lt;/code&gt; is an entry point for Go tests. In the following code you'll see that I'm capturing whether the caller wants to run database integration tests and setting up/tearing down the database connection if they've asked for database integration tests to be run.&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;TestMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dbTests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"run database integration tests"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dbTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;setupDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dbTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;teardownDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exitCode&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;The database setup code expects an environment variable called "CONN_STR" and crashes the test if it doesn't exist. Hence I wrap calls to &lt;code&gt;setupDatabase&lt;/code&gt; in a check to &lt;code&gt;*dbTests&lt;/code&gt;, allowing me to run unit tests that don't care about database tests (or connection strings from the environment) without having to provide this information.&lt;/p&gt;

&lt;p&gt;The guts of &lt;code&gt;setupDatabase&lt;/code&gt; is the database connectivity logic. A call is made to open a connection to the database and another is made to verify that the connection is open and ready:&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;setupDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LookupEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CONN_STR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&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;"connection string env var not found"&lt;/span&gt;&lt;span class="p"&gt;)&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;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;db&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;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connStr&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="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;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error connecting to the database: %v"&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="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="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ping&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="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;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error pinging the database: %v"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;teardownDatabase&lt;/code&gt; function could simply be replaced with a call to &lt;code&gt;db.Close()&lt;/code&gt; but I preferred the consistency in the &lt;code&gt;TestMain&lt;/code&gt; function, so decided to keep it:&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;teardownDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&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;Finally, I've written an integration test. It currently does nothing useful, further to logging what it's doing (running database integration tests or not):&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;TestStuff&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;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&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="o"&gt;!*&lt;/span&gt;&lt;span class="n"&gt;dbTests&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;Skipf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not running database tests"&lt;/span&gt;&lt;span class="p"&gt;)&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;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"write some database tests..."&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;h5&gt;
  
  
  Containerise
&lt;/h5&gt;

&lt;p&gt;To be able to run our database integration tests reliably and get repeatable results, we're going to need to run 2 Docker containers, one to host an instance of CockroachDB and another to execute our tests.&lt;/p&gt;

&lt;h6&gt;
  
  
  Dockerfile
&lt;/h6&gt;

&lt;p&gt;To start with, let's create a &lt;a href="https://docs.docker.com/engine/reference/builder/"&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt; to run our tests. The below spins up a very lightweight &lt;a href="https://alpinelinux.org/"&gt;&lt;code&gt;Alpine&lt;/code&gt;&lt;/a&gt;-based container, copies our project's top-level directory into an "app" directory inside the container, and sets the working directory for the container to that directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:latest&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Build
&lt;/h6&gt;

&lt;p&gt;As we're using the Alpine base image, we won't have the means to run Go code (or Go tests). We'll therefore &lt;a href="https://golang.org/pkg/cmd/go/internal/test/"&gt;build the tests&lt;/a&gt; so they can simply be executed within the container, just like any other executable.&lt;/p&gt;

&lt;p&gt;The following builds a test executable called "crdb-test" for the Alpine Linux container that when executed, will be the same as running &lt;code&gt;go test ./... -v&lt;/code&gt;, without having to rely on the Go executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ GOOS=linux go test ./... -v -c -o crdb-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Image
&lt;/h6&gt;

&lt;p&gt;Next, we need to build the Docker image. The following builds a docker image called "crdb-test", using the Dockerfile we created earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker build -t crdb-test .
Sending build context to Docker daemon   6.18MB
Step 1/3 : FROM alpine:latest
 ---&amp;gt; 5cb3aa00f899
Step 2/3 : COPY . /app
 ---&amp;gt; Using cache
 ---&amp;gt; 936e88998fa5
Step 3/3 : WORKDIR /app
 ---&amp;gt; Using cache
 ---&amp;gt; 30e76628fce3
Successfully built 30e76628fce3
Successfully tagged crdb-test:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can run the Docker image and hop into it via the terminal. The following command will open up a terminal and put you straight into the container's working directory, allowing you to poke around and check that your "crdb-test" test binary is where you're expecting it to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker run -it crdb-test:latest sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Compose
&lt;/h5&gt;

&lt;p&gt;To tie our test container and the CockroachDB container together, we'll use &lt;code&gt;docker-compose&lt;/code&gt;. Here's the full file, which I'll break into smaller parts and describe below:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cockroach_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb/cockroach:v2.1.6&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start --insecure&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;26257:26257"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./cockroach-data/roach1:/cockroach/cockroach-data&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

  &lt;span class="na"&gt;app_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crdb-test:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CONN_STR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://root@cockroach_test:26257/defaultdb?sslmode=disable&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./crdb-test -db&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach_test&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've named the first container (or "service") "cockroach_test". This will become the DNS name that I'll use to connect my test container to.&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;cockroach_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb/cockroach:v19.2.0&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start --insecure&lt;/span&gt;
  &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;code&gt;image&lt;/code&gt; parameter, I pass the current latest version of the CockroachDB Docker image. You can substitute another version of the database for your tests.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;command&lt;/code&gt; parameter, I pass &lt;code&gt;start --insecure&lt;/code&gt;. These are the command line arguments that you'd pass to the &lt;code&gt;cockroach&lt;/code&gt; CLI if running outside of Docker.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;logging&lt;/code&gt; parameter, I pass an argument that disables logging for the CockroachDB container. This is entirely optional and just means the logging output is more test-focused. Helpful if you're trawling through test logs to figure out why a test is unexpectedly failing!&lt;/p&gt;

&lt;p&gt;I've also configured a bridge network for the continers to talk to each other, so for the &lt;code&gt;networks&lt;/code&gt; parameter, I pass "app-network", the name of that network.&lt;/p&gt;

&lt;p&gt;I spin up a container to execute my test code and refer to it as "app_test".&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;app_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crdb-test:latest&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CONN_STR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://root@cockroach_test:26257/defaultdb?sslmode=disable&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./crdb-test -db&lt;/span&gt;
  &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach_test&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;code&gt;image&lt;/code&gt; parameter, I refer back to the image I built earlier, "crdb-test", suffixing ":latest", the default version of a freshly minted local Docker image.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;environment&lt;/code&gt; parameter, I pass "CONN_STR", the full connection string to the CockroachDB database. Note that instead of "localhost", I'm passing "cockroach_test", the name of the CockroachDB docker-compose service.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;command&lt;/code&gt; parameter, I pass "./crdb-test -db". This tells docker-compose what to run from within the test container's working directory. The "-db" flag tells our test executable to run database integration tests.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;depends_on&lt;/code&gt; parameter, I pass "cockroach_test", telling docker-compose that we want to wait until CockroachDB is up before trying to execute our tests.&lt;/p&gt;

&lt;p&gt;Finally, to run everything, we call the &lt;code&gt;docker-compose&lt;/code&gt; CLI passing the name of our docker-compose file ("crdb-test.yaml" in this case) and a few additional flags you might find helpful, including &lt;code&gt;--abort-on-container-exit&lt;/code&gt;, which tears down all containers when one of them exists, cleaning everything up once our tests are run and passing our exit code back to the caller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker-compose --no-ansi -f crdb-test.yaml up --abort-on-container-exit --force-recreate
Recreating cockroachdb-testing_cockroach_test_1 ... 
Recreating cockroachdb-testing_cockroach_test_1 ... done
Recreating cockroachdb-testing_app_test_1       ... 
Recreating cockroachdb-testing_app_test_1       ... done
Attaching to cockroachdb-testing_cockroach_test_1, cockroachdb-testing_app_test_1
cockroach_test_1  | WARNING: no logs are available with the 'none' log driver
app_test_1        | run database tests true
app_test_1        | PASS
cockroachdb-testing_app_test_1 exited with code 0
Aborting on container exit...
Stopping cockroachdb-testing_cockroach_test_1   ... 
Stopping cockroachdb-testing_cockroach_test_1   ... done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>testing</category>
      <category>database</category>
      <category>docker</category>
      <category>cockroachdb</category>
    </item>
  </channel>
</rss>
