<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Ryan McKay Against Entropy</title><link href="https://ryanmckaytx.github.io/" rel="alternate"></link><link href="https://ryanmckaytx.github.io/feeds/all.atom.xml" rel="self"></link><id>https://ryanmckaytx.github.io/</id><updated>2024-11-23T17:00:00-06:00</updated><entry><title>DuckDB vs. Bespoke Python</title><link href="https://ryanmckaytx.github.io/duckdb-vs-bespoke-python.html" rel="alternate"></link><published>2024-11-23T17:00:00-06:00</published><updated>2024-11-23T17:00:00-06:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2024-11-23:/duckdb-vs-bespoke-python.html</id><summary type="html">&lt;p&gt;I recently encountered an interview problem that involved answering various analytical questions over some number of delimited data files. 
It mentioned running on a laptop, and asked the interviewee to consider scalability in terms of data volume and extensibility in terms of answering new questions.
Like many interview problems, the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently encountered an interview problem that involved answering various analytical questions over some number of delimited data files. 
It mentioned running on a laptop, and asked the interviewee to consider scalability in terms of data volume and extensibility in terms of answering new questions.
Like many interview problems, the suggested solution hadn't been updated in a while (years), and was looking for a bespoke Python solution. &lt;/p&gt;
&lt;p&gt;There are many modern data processing libraries to choose from for this type of problem - 
Polars, DuckDB, Pandas, or Pyspark for Python, 
scala-polars or Tablesaw for Java, 
even gota for golang or Danfo.js for javascript. 
Speaking from experience, the first 3 are very easy to use, and the first 2 are extremely fast on moderately sized datasets.
Let’s take a look at DuckDB.
&lt;a href="https://duckdb.org/"&gt;&lt;img alt="DuckDB" src="https://ryanmckaytx.github.io/images/duckdb.png" title="DuckDB"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="setup"&gt;Setup&lt;a class="headerlink" href="#setup" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I wanted to use non-proprietary data so that anybody could reproduce the results. So I downloaded this &lt;a href="https://github.com/namebrandon/Sparkov_Data_Generation"&gt;credit card transaction data generator&lt;/a&gt;. 
It generates data with plenty of interesting facets to exercise various queries. 
I generated 4 data sets. 
The raw data is in csv files and takes up the following disk space:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;➜&lt;span class="w"&gt; &lt;/span&gt;du&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;data_*
31G&lt;span class="w"&gt;     &lt;/span&gt;data_100mm_txn
&lt;span class="m"&gt;3&lt;/span&gt;.1G&lt;span class="w"&gt;    &lt;/span&gt;data_10mm_txn
314M&lt;span class="w"&gt;    &lt;/span&gt;data_1mm_txn
62G&lt;span class="w"&gt;     &lt;/span&gt;data_200mm_txn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The data generator doesn't provide an easy way to directly request a specific number of data records, only a number of customers, so I had to iterate a few times to get the data set sizes I wanted.&lt;br&gt;
For roughly 1mm records, I landed on 1200 customers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;datagen.py&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gen_data/data_1mm_txn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;-01-2023&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;-31-2023
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This produces a collection of csv files with a total record count of &lt;code&gt;1,054,548&lt;/code&gt;.&lt;br&gt;
I just scaled 1200 up for the other data sets.
A sample of one file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;➜ head data_1mm_txn/adults_2550_female_rural_0000-0239.csv
ssn|cc_num|first|last|gender|street|city|state|zip|lat|long|city_pop|job|dob|acct_num|profile|trans_num|trans_date|trans_time|unix_time|category|amt|is_fraud|merchant|merch_lat|merch_long
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|bdadffbae7329592393b079c5827490e|2023-08-01|01:34:03|1690871643|grocery_pos|370.53|1|fraud_Kovacek, Dibbert and Ondricka|38.283713|-80.810153
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|629fbb975c2ee41a9345f01afba7e6cf|2023-08-01|01:24:17|1690871057|misc_net|339.88|1|fraud_Kuphal-Predovic|37.196506|-81.913850
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|b78ba4de847d0adfd821c6ccfe261477|2023-08-01|02:39:52|1690875592|misc_pos|873.23|1|fraud_Eichmann-Russel|37.519118|-81.765044
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|6aac07c0b9182547c34676ab24b32a81|2023-08-01|01:07:41|1690870061|shopping_pos|933.84|1|fraud_Beier-Hyatt|38.361891|-81.390966
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|a5cd41f69faaeffe2a0a3a7b58918e3b|2023-08-01|11:26:02|1690907162|shopping_net|1077.35|1|fraud_Kerluke-Abshire|38.148218|-81.294663
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|ec72a087177503da0a5eee37e1d236e8|2023-08-01|01:12:46|1690870366|grocery_pos|838.74|1|fraud_Doyle Ltd|37.900884|-81.505876
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|39cb621398e00c3059e1bf3d4527fcb3|2023-08-02|20:51:54|1691027514|shopping_pos|1002.48|1|fraud_Friesen Inc|38.328737|-81.958906
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|5707a4463909b44faf5c62740e70ba4a|2023-08-02|21:23:06|1691029386|health_fitness|577.38|1|fraud_Greenholt Ltd|37.390618|-80.675238
405-64-0484|6525324472099933|Maria|Porter|F|14626 Dawn Union Apt. 806|Smithers|WV|25186|38.1543|-81.278|881|Mechanical engineer|1999-03-18|599060683120|adults_2550_female_rural.json|9af5738414987cc043e504067843ad61|2023-08-02|23:49:09|1691038149|kids_pets|585.89|1|fraud_Bernier and Sons|37.342443|-80.712102
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These datasets are significantly wider than the data described in the original problem.&lt;/p&gt;
&lt;h1 id="data-exploration"&gt;Data Exploration&lt;a class="headerlink" href="#data-exploration" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The first advantage these libraries have is how quickly you can get to something useful. DuckDB can directly run SQL over a set of CSV files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;➜&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM &amp;#39;data_1mm_txn/*adults_*.csv&amp;#39; LIMIT 10;&amp;quot;&lt;/span&gt;
┌─────────────┬─────────────────┬─────────┬─────────┬─────────┬─────────────────────┬───────────┬───┬────────────┬──────────────┬────────┬──────────┬──────────────────────┬───────────┬────────────┐
│&lt;span class="w"&gt;     &lt;/span&gt;ssn&lt;span class="w"&gt;     &lt;/span&gt;│&lt;span class="w"&gt;     &lt;/span&gt;cc_num&lt;span class="w"&gt;      &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;first&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;last&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;gender&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;       &lt;/span&gt;street&lt;span class="w"&gt;        &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;city&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;unix_time&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;category&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;amt&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;is_fraud&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;       &lt;/span&gt;merchant&lt;span class="w"&gt;       &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;merch_lat&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;merch_long&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt;   &lt;/span&gt;varchar&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;      &lt;/span&gt;int64&lt;span class="w"&gt;      &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;varchar&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;varchar&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;boolean&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;       &lt;/span&gt;varchar&lt;span class="w"&gt;       &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;varchar&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;int64&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;varchar&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;double&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;int64&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;       &lt;/span&gt;varchar&lt;span class="w"&gt;        &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;double&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;double&lt;span class="w"&gt;   &lt;/span&gt;│
├─────────────┼─────────────────┼─────────┼─────────┼─────────┼─────────────────────┼───────────┼───┼────────────┼──────────────┼────────┼──────────┼──────────────────────┼───────────┼────────────┤
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682148836&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;grocery_pos&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.83&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Cole&lt;span class="w"&gt; &lt;/span&gt;PLC&lt;span class="w"&gt;       &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;.084605&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;-84.59263&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682147498&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;grocery_pos&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;329&lt;/span&gt;.81&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Stracke-Lemke&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;.909412&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-83.336348&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682150282&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;misc_net&lt;span class="w"&gt;     &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;341&lt;/span&gt;.76&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Kuhn&lt;span class="w"&gt; &lt;/span&gt;LLC&lt;span class="w"&gt;       &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;.869572&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-84.365167&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682146929&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;shopping_net&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;832&lt;/span&gt;.34&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Kuhic&lt;span class="w"&gt; &lt;/span&gt;LLC&lt;span class="w"&gt;      &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;.540181&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-83.670669&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682139650&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;grocery_net&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;915&lt;/span&gt;.86&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Swift,&lt;span class="w"&gt; &lt;/span&gt;Bradt…&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;34&lt;/span&gt;.058672&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-83.259417&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682151402&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;misc_pos&lt;span class="w"&gt;     &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;915&lt;/span&gt;.74&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Gutmann-Upton&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;.45225&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-82.707481&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682306219&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;food_dining&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;552&lt;/span&gt;.77&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_O&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;Hara-Wilde…&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;.054957&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-83.575527&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682305693&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;home&lt;span class="w"&gt;         &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;120&lt;/span&gt;.65&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Reilly&lt;span class="w"&gt; &lt;/span&gt;LLC&lt;span class="w"&gt;     &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;.433593&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-83.425256&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682288784&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;food_dining&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;107&lt;/span&gt;.78&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Koss,&lt;span class="w"&gt; &lt;/span&gt;McLaug…&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;34&lt;/span&gt;.060598&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;  &lt;/span&gt;-83.46855&lt;span class="w"&gt; &lt;/span&gt;│
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;424&lt;/span&gt;-64-6205&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Tiffany&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Jones&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Wheeler&lt;span class="w"&gt; &lt;/span&gt;Ridges&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;Good&lt;span class="w"&gt; &lt;/span&gt;Hope&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;…&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1682308774&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;food_dining&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;.22&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;fraud_Powlowski-We…&lt;span class="w"&gt;  &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;34&lt;/span&gt;.303291&lt;span class="w"&gt; &lt;/span&gt;│&lt;span class="w"&gt; &lt;/span&gt;-82.917523&lt;span class="w"&gt; &lt;/span&gt;│
├─────────────┴─────────────────┴─────────┴─────────┴─────────┴─────────────────────┴───────────┴───┴────────────┴──────────────┴────────┴──────────┴──────────────────────┴───────────┴────────────┤
│&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rows&lt;span class="w"&gt;                                                                                                                                                                     &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;columns&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shown&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;│
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not bad! Notice that it figured out a bunch of stuff by itself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the fact that there is a header row&lt;/li&gt;
&lt;li&gt;the delimiter&lt;/li&gt;
&lt;li&gt;the type of each column&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It does this by looking at a few (20,480 by default) records. It did get a couple of things wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gender “F” was interpreted as a boolean (false)&lt;/li&gt;
&lt;li&gt;zip was interpreted as an int (really needs to be varchar because zips can start with 0)&lt;/li&gt;
&lt;li&gt;maybe the same problem with cc_num and acct_num&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These issues are easy to fix (note how types were specified this time).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM READ_CSV(&amp;#39;data_1mm_txn/*adults_*.csv&amp;#39;, types = {&amp;#39;gender&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;cc_num&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;acct_num&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;zip&amp;#39;: &amp;#39;varchar&amp;#39;}) LIMIT 10;&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌─────────────┬─────────────────┬─────────┬─────────┬─────────┬─────────────────────┬───────────┬───┬────────────┬──────────────┬────────┬──────────┬──────────────────────┬───────────┬────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;ssn&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;cc_num&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gender&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unix_time&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_fraud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merch_lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merch_long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├─────────────┼─────────────────┼─────────┼─────────┼─────────┼─────────────────────┼───────────┼───┼────────────┼──────────────┼────────┼──────────┼──────────────────────┼───────────┼────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682148836&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grocery_pos&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mf"&gt;5.83&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Cole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PLC&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;33.084605&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;84.59263&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682147498&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grocery_pos&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;329.81&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Stracke&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Lemke&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;32.909412&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;83.336348&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682150282&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;misc_net&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;341.76&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Kuhn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLC&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;32.869572&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;84.365167&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682146929&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shopping_net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;832.34&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Kuhic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLC&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;33.540181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;83.670669&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682139650&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grocery_net&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;915.86&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Swift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bradt&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;34.058672&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;83.259417&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682151402&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;misc_pos&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;915.74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Gutmann&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Upton&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;33.45225&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;82.707481&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682306219&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;food_dining&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;552.77&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_O&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hara-Wilde…  │ 33.054957 │ -83.575527 │&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682305693&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;120.65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Reilly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLC&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;33.433593&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;83.425256&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682288784&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;food_dining&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;107.78&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Koss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;McLaug&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;34.060598&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;83.46855&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6205&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;371793271040546&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tiffany&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2181&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wheeler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ridges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Good&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682308774&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;food_dining&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;140.22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Powlowski&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;We&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;34.303291&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;82.917523&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├─────────────┴─────────────────┴─────────┴─────────┴─────────┴─────────────────────┴───────────┴───┴────────────┴──────────────┴────────┴──────────┴──────────────────────┴───────────┴────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="w"&gt;                                                                                                                                                                     &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can also ask for a summarization of the data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;summarize SELECT * FROM READ_CSV(&amp;#39;data_1mm_txn/*adults_*.csv&amp;#39;, types = {&amp;#39;gender&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;cc_num&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;acct_num&amp;#39;: &amp;#39;varchar&amp;#39;, &amp;#39;zip&amp;#39;: &amp;#39;varchar&amp;#39;}, header=true);&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌─────────────┬─────────────┬──────────────────────┬──────────────────────┬───────────────┬───┬────────────────────┬────────────────────┬────────────────────┬─────────┬─────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;approx_unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;q25&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;q50&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;q75&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null_percentage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&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="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├─────────────┼─────────────┼──────────────────────┼──────────────────────┼───────────────┼───┼────────────────────┼────────────────────┼────────────────────┼─────────┼─────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssn&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;001&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1810&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;899&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1248&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1211&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cc_num&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;060400268763&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;676394485863&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1196&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Aaron&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Zachary&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Acevedo&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Zimmerman&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;550&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gender&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0009&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hills&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9992&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Terrace&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1190&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Abilene&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Yukon&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;842&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AK&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WY&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;01010&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99654&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1130&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOUBLE&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20.7441&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;61.5923&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1117&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;33.78882546408646&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.508994491911075&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;41.18956704202229&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOUBLE&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;159.6694&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;68.4121&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1135&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;98.23946762402772&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;87.06472635623118&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;79.77149230109092&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;city_pop&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;199&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2906700&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;890&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19416&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65061&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;241033&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Academic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;librarian&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Youth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;534&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dob&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATE&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1928&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2008&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1183&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acct_num&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10258404917&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999901662733&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;1203&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adults_2550_female&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;young_adults_male_&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_num&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;00001340&lt;/span&gt;&lt;span class="n"&gt;d8e6b1766b&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fffff990530a101168&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1080518&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_date&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATE&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;366&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_time&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="mi"&gt;88400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unix_time&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1672552808&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1704088796&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1009172&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1682651789&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1690184881&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1698137297&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entertainment&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;travel&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOUBLE&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26810.71&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="mi"&gt;52142&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.175574441235733&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;44.90613575342282&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;82.69103793265246&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_fraud&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Abbott&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Rogahn&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fraud_Zulauf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLC&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;716&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merch_lat&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOUBLE&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19.845823&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;62.589931&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1026312&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;33.69838307624862&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.44309616618485&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;41.26469799023738&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merch_long&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOUBLE&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;160.669149&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;67.413594&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;1053435&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;98.36918390322704&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;86.93663111949701&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;79.54040155192297&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1051343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├─────────────┴─────────────┴──────────────────────┴──────────────────────┴───────────────┴───┴────────────────────┴────────────────────┴────────────────────┴─────────┴─────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="w"&gt;                                                                                                                                                          &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1 id="evaluation"&gt;Evaluation&lt;a class="headerlink" href="#evaluation" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The problem had two main areas of evaluation - dealing with data scale, and extensibility to support changing/additional requirements.&lt;/p&gt;
&lt;h2 id="runtime-performance-raw-csv"&gt;Runtime Performance - Raw CSV&lt;a class="headerlink" href="#runtime-performance-raw-csv" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As we’ve seen, duckdb can run queries directly on a set of csv files. Lets see how this performs with a query similar to the first one called for in the original problem.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM READ_CSV(&amp;#39;data_1mm_txn/*adults_*.csv&amp;#39;, header = true) WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌───────────┐&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;├───────────┤&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;160771&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;07&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└───────────┘&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;21s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;09s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;690&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;623&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;303696&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;
&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM READ_CSV(&amp;#39;data_10mm_txn/*adults_*.csv&amp;#39;, header = true) WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────────────────────┐&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;├────────────────────┤&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1529945&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0599999998&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────────────────────┘&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;31s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;58s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;780&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;909&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1270608&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;
&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM READ_CSV(&amp;#39;data_100mm_txn/*adults_*.csv&amp;#39;, header = true) WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌───────────────────┐&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;├───────────────────┤&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;16001193&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;47999998&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└───────────────────┘&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;42&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;19s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;07s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;463&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;853&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;6491968&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;
&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM READ_CSV(&amp;#39;data_200mm_txn/*adults_*.csv&amp;#39;, header = true) WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌───────────────────┐&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;├───────────────────┤&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;32126444&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;01999999&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└───────────────────┘&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;74&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;13s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;85s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;418&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;21&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;984&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;10977920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not bad, but not great either - as a user, I really want these queries finishing in well under 5 seconds. 
I’m not too concerned about how much CPU and memory it uses as long as it successfully runs on my laptop. 
Note that it did use multiple CPUs in parallel, without me having to build that. 
It's not too surprising that the performance was poor - CSV is not optimized for ad-hoc querying (or really much of
anything other than pulling into a spreadsheet). 
To summarize:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Num Records&lt;/th&gt;
&lt;th&gt;User Time (s)&lt;/th&gt;
&lt;th&gt;Memory Used (GB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1mm&lt;/td&gt;
&lt;td&gt;4.21&lt;/td&gt;
&lt;td&gt;0.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10mm&lt;/td&gt;
&lt;td&gt;14.31&lt;/td&gt;
&lt;td&gt;1.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100mm&lt;/td&gt;
&lt;td&gt;42.19&lt;/td&gt;
&lt;td&gt;6.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200mm&lt;/td&gt;
&lt;td&gt;74.13&lt;/td&gt;
&lt;td&gt;10.47&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="runtime-performance-native-duckdb"&gt;Runtime Performance - Native DuckDB&lt;a class="headerlink" href="#runtime-performance-native-duckdb" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can easily import data from CSV to a native DuckDB table file. Let's see if that is any better.&lt;/p&gt;
&lt;h3 id="load-data"&gt;Load Data&lt;a class="headerlink" href="#load-data" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duckdb&lt;/span&gt;
&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;b1486d11&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.help&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Connected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.open FILENAME&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reopen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;persistent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_1mm_txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;READ_CSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data_1mm_txn/*adults_*.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cc_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;acct_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.674&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.352345&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.190694&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_10mm_txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;READ_CSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data_10mm_txn/*adults_*.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cc_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;acct_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.230&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.859433&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.095903&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_100mm_txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;READ_CSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data_100mm_txn/*adults_*.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cc_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;acct_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;31.078&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;262.523123&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.148275&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_200mm_txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;READ_CSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data_200mm_txn/*adults_*.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cc_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;acct_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;varchar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;62.741&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;494.657874&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22.505040&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The time taken to load is quite a bit longer than to run a single query, so we will want the query speed to be dramatically improved to justify it. 
Though presumably we would be querying much more frequently than loading massive amounts of more CSV data.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;➜ ls -lh *.db
-rw-r--r-- 1 rmckay staff 4.9G Aug 6 17:35 data_100mm_txn.db
-rw-r--r-- 1 rmckay staff 713M Aug 6 17:33 data_10mm_txn.db
-rw-r--r-- 1 rmckay staff 48M Aug 6 17:32 data_1mm_txn.db
-rw-r--r-- 1 rmckay staff 9.4G Aug 6 17:37 data_200mm_txn.db
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The resulting files are much smaller than the source CSV data.&lt;/p&gt;
&lt;h3 id="run-queries"&gt;Run Queries&lt;a class="headerlink" href="#run-queries" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_1mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM txn WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌───────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├───────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;160771&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;07&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└───────────┘&lt;/span&gt;
&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_1mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;04s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;01s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;123&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;039&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;25664&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_10mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM txn WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1529945&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0600000003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────────────────────┘&lt;/span&gt;
&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_10mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;22s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;03s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;378&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;065&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;94928&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_100mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM txn WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;16001193&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;479999982&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────────────────────┘&lt;/span&gt;
&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_100mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;73s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;17s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;708&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;268&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;670576&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_200mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT SUM(amt) FROM txn WHERE category=&amp;#39;grocery_pos&amp;#39; AND trans_date between &amp;#39;2023-07-01&amp;#39; AND &amp;#39;2023-08-31&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;┌───────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;amt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;double&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├───────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;32126444&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;02000001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└───────────────────┘&lt;/span&gt;
&lt;span class="nt"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data_200mm_txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;40s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;30s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;767&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;482&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;total&lt;/span&gt;
&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1254576&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Num Records&lt;/th&gt;
&lt;th&gt;User Time (s)&lt;/th&gt;
&lt;th&gt;Memory Used (GB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1mm&lt;/td&gt;
&lt;td&gt;0.04&lt;/td&gt;
&lt;td&gt;0.024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10mm&lt;/td&gt;
&lt;td&gt;0.22&lt;/td&gt;
&lt;td&gt;0.091&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100mm&lt;/td&gt;
&lt;td&gt;1.73&lt;/td&gt;
&lt;td&gt;0.640&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200mm&lt;/td&gt;
&lt;td&gt;3.40&lt;/td&gt;
&lt;td&gt;1.196&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That was blazingly fast and used way less memory than the raw CSV approach.&lt;/p&gt;
&lt;h1 id="extensibility"&gt;Extensibility&lt;a class="headerlink" href="#extensibility" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;It’s easy to see that pretty much any query our users could think of would be easily supported. 
Just for fun lets run some stand-ins for the other queries called for in the original problem. 
We’ll just stick with the 200mm dataset for all of these.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_200mm_txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;b1486d11&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.help&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;┌────────────────┬──────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count_star&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────┼──────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;misc_pos&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13667090&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shopping_pos&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20627532&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grocery_pos&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19843040&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;food_dining&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15616003&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;personal_care&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14653811&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shopping_net&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15559261&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;health_fitness&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12955427&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19517464&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gas_transport&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17922579&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;misc_net&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9632379&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;travel&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6645464&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kids_pets&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18068316&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grocery_net&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8885726&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entertainment&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15113737&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────┴──────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└───────────────────────────────┘&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.482&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.546682&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.016826&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;travel&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-06-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-07-31&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;┌────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2926518.8900000015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────────────────────┘&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.324&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.115719&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.006838&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-06-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-06-30&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;┌────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├────────────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1344176312.4700007&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────────────────────┘&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.171&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.495202&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.064187&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_amount&lt;/span&gt;
&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;
&lt;span class="n"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trans_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-06-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-08-31&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="n"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DESC&lt;/span&gt;
&lt;span class="n"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;┌───────────────┬───────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varchar&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├───────────────┼───────────────┤&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shopping_pos&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1398272714.98&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entertainment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;986193998.59&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shopping_net&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;874592892.17&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;food_dining&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;567098508.99&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;misc_pos&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;144910582.16&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└───────────────┴───────────────┘&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.337&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.183682&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.008045&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I used the DuckDB’s interactive mode this time. 
It is sitting at 1.27GB memory used according to Activity Monitor.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The original problem mentioned running in AWS or on a user’s laptop. 
For both scenarios, I think distributing the data as a DuckDB database file and using DuckDB to query it would be an excellent solution. 
Certainly much, much better than CSV + bespoke summarizing logic written in Python. 
If you want to interview for big&lt;sup&gt;*&lt;/sup&gt; data competence, look for familiarity with modern data libraries.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt; most orgs' data &lt;a href="https://motherduck.com/blog/big-data-is-dead/"&gt;isn't really that big&lt;/a&gt;&lt;/p&gt;</content><category term="misc"></category><category term="duckdb"></category><category term="python"></category></entry><entry><title>Pelican Static Site Generator</title><link href="https://ryanmckaytx.github.io/pelican-static-site-generator.html" rel="alternate"></link><published>2024-11-02T17:00:00-05:00</published><updated>2024-11-02T17:00:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2024-11-02:/pelican-static-site-generator.html</id><summary type="html">&lt;p&gt;I'm migrating my blogs on various sites (primarily &lt;a href="https://againstentropy.blogspot.com/"&gt;blogspot&lt;/a&gt;) to github pages.  I like specifying the content in markdown and having revision control.&lt;/p&gt;
&lt;p&gt;The default static site generator on github pages is Jekyll, which is written in Ruby.
I tried it out briefly, but decided to go for a generator …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm migrating my blogs on various sites (primarily &lt;a href="https://againstentropy.blogspot.com/"&gt;blogspot&lt;/a&gt;) to github pages.  I like specifying the content in markdown and having revision control.&lt;/p&gt;
&lt;p&gt;The default static site generator on github pages is Jekyll, which is written in Ruby.
I tried it out briefly, but decided to go for a generator written in the more familiar Python,
so I can hack it if necessary - thus &lt;a href="https://docs.getpelican.com/en/latest/index.html"&gt;Pelican&lt;/a&gt;.
Let's explore some of the features.
The source for this site is at &lt;a href="https://github.com/ryanmckaytx/ryanmckaytx.github.io"&gt;https://github.com/ryanmckaytx/ryanmckaytx.github.io&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="themes"&gt;Themes&lt;a class="headerlink" href="#themes" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I pretty quickly found a theme I like, and &lt;a href="https://github.com/ryanmckaytx/pelican-mediumfox"&gt;forked&lt;/a&gt;
it to switch out the background image, fix a bug, and make a few styling improvements. 
I also &lt;a href="https://github.com/ryanmckaytx/pelican-mediumfox/commit/1f85c794b2a4ae86b24baf594c9ff96d06a9275a"&gt;updated&lt;/a&gt; it to support the &lt;code&gt;GOOGLE_ANALYTICS&lt;/code&gt; config option.
I pulled the forked theme into this repo with a git submodule.&lt;/p&gt;
&lt;h1 id="github-pages"&gt;Github Pages&lt;a class="headerlink" href="#github-pages" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Pelican comes with a github action to publish your site to github pages.  But it uses pip and I like to use poetry, primarily for the integrated virtual env and lockfile management.  So I &lt;a href="https://github.com/ryanmckaytx/ryanmckaytx.github.io/blob/main/.github/workflows/pelican_github_pages.yml"&gt;tweaked&lt;/a&gt; it a bit.&lt;/p&gt;
&lt;h1 id="python-markdown"&gt;Python-Markdown&lt;a class="headerlink" href="#python-markdown" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The markdown processor in Pelican is &lt;a href="https://python-markdown.github.io/"&gt;Python-Markdown&lt;/a&gt;.
It has several &lt;a href="https://python-markdown.github.io/extensions/#officially-supported-extensions0"&gt;officially supported extensions&lt;/a&gt; and many &lt;a href="https://github.com/Python-Markdown/markdown/wiki/Third-Party-Extensions"&gt;third-party extensions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="headerlink" href="#table-of-contents" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#themes"&gt;Themes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#github-pages"&gt;Github Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#python-markdown"&gt;Python-Markdown&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#table-of-contents"&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#code-blocks"&gt;Code Blocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mermaid-diagrams"&gt;Mermaid Diagrams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#inline-image"&gt;Inline Image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#table"&gt;Table&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#import-from-blogspot"&gt;Import from Blogspot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;This is one of the built-in extensions.
It adds the permalink to each heading and creates the TOC component.
The permalink and TOC look a lot better after some styling.&lt;/p&gt;
&lt;h2 id="code-blocks" style="clear: both"&gt;Code Blocks&lt;a class="headerlink" href="#code-blocks" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Another built-in extension.  Not bad - I wish it had a copy button though.&lt;/p&gt;
&lt;h2 id="mermaid-diagrams"&gt;Mermaid Diagrams&lt;a class="headerlink" href="#mermaid-diagrams" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Support added through the third-party &lt;a href="https://github.com/Lee-W/markdown-mermaidjs"&gt;markdown-mermaidjs&lt;/a&gt; python-markdown extension.&lt;/p&gt;
&lt;div class="mermaid"&gt;
  graph TD;
      A--&gt;B;
      A--&gt;C;
      B--&gt;D;
      C--&gt;D;
&lt;/div&gt;

&lt;h2 id="inline-image"&gt;Inline Image&lt;a class="headerlink" href="#inline-image" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Bluebonet" src="https://ryanmckaytx.github.io/images/bluebonnet-7837830_300.jpg" title="Bluebonnet"&gt;&lt;/p&gt;
&lt;p&gt;Image by &lt;a href="https://pixabay.com/users/ray_shrewsberry-7673058/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=7837830"&gt;Ray Shrewsberry •&lt;/a&gt; from &lt;a href="https://pixabay.com//?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=7837830"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table"&gt;Table&lt;a class="headerlink" href="#table" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;First Header&lt;/th&gt;
&lt;th&gt;Second Header&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content Cell&lt;/td&gt;
&lt;td&gt;Content Cell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content Cell&lt;/td&gt;
&lt;td&gt;Content Cell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It rendered to html correctly.  The template styling for tables needed some tweaking.&lt;/p&gt;
&lt;h1 id="import-from-blogspot"&gt;Import from Blogspot&lt;a class="headerlink" href="#import-from-blogspot" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Pelican provides the &lt;a href="https://docs.getpelican.com/en/latest/importer.html"&gt;pelican-import&lt;/a&gt; tool for importing content from various sources.
The exported content from blogspot was about 400KB of xml.  After running the tool, all of my blog entries became pages as expected, but there were some unexpected aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The comments from the original blog entries each became another page&lt;/li&gt;
&lt;li&gt;The page content had a lot of embedded html that didn't look good (&lt;a href="/drafts/docker-java-example-part-1-initializing-original-import"&gt;example&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The images were not transferred&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So there is a fair amount of cleanup/translation to markdown required.&lt;/p&gt;
&lt;script type="module"&gt;
    import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
    mermaid.initialize({ startOnLoad: true });
&lt;/script&gt;</content><category term="misc"></category><category term="python"></category><category term="ssg"></category><category term="pelican"></category></entry><entry><title>Modern Python Tooling</title><link href="https://ryanmckaytx.github.io/modern-python-tooling.html" rel="alternate"></link><published>2022-08-31T12:00:00-05:00</published><updated>2022-08-31T12:00:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2022-08-31:/modern-python-tooling.html</id><summary type="html">&lt;p&gt;Modern Python tooling for managing dependencies, testing, and linting&lt;/p&gt;</summary><content type="html">&lt;p&gt;Modern Python development involves managing dependencies, and testing and linting your code.
I'm going to review a few of the related tools for that I've had success with.&lt;/p&gt;
&lt;p&gt;To help demonstrate some of the concepts, I'm going to do some work on the &lt;a href="https://kata-log.rocks/gilded-rose-kata"&gt;gilded rose kata&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="python-version-management"&gt;Python Version Management&lt;a class="headerlink" href="#python-version-management" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Python projects destined for deployment should pin a specific version of Python, just like any other dependency, so that you get repeatable behavior in every environment, from local dev to production. 
At some point you're going to need multiple versions of Python on hand, either because you want to test a new version without getting rid of the old version, or because you work in multiple projects that require different versions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pyenv logo" src="https://ryanmckaytx.github.io/images/pyenv-logo.png" title="pyenv"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt; is a tool for installing and switching between different versions of Python.&lt;/p&gt;
&lt;h2 id="pyenv-installation"&gt;pyenv Installation&lt;a class="headerlink" href="#pyenv-installation" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;update
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pyenv
pyenv&lt;span class="w"&gt; &lt;/span&gt;init
&lt;span class="c1"&gt;# Load pyenv automatically by appending&lt;/span&gt;
&lt;span class="c1"&gt;# the following to&lt;/span&gt;
&lt;span class="c1"&gt;# ~/.zprofile (for login shells)&lt;/span&gt;
&lt;span class="c1"&gt;# and ~/.zshrc (for interactive shells) :&lt;/span&gt;

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PYENV_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PYENV_ROOT&lt;/span&gt;/bin&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PYENV_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Restart your shell for the changes to take effect.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="python-version-installation-and-use"&gt;Python version installation and use&lt;a class="headerlink" href="#python-version-installation-and-use" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# List available versions&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-l
...
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.0
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10-dev
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.1
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.2
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.3
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.4
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.5
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6
...

&lt;span class="c1"&gt;# Install version&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6
python-build:&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;homebrew
python-build:&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;readline&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;homebrew
Downloading&lt;span class="w"&gt; &lt;/span&gt;Python-3.10.6.tar.xz...
-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;https://www.python.org/ftp/python/3.10.6/Python-3.10.6.tar.xz
Installing&lt;span class="w"&gt; &lt;/span&gt;Python-3.10.6...
python-build:&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;readline&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;homebrew
python-build:&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;zlib&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;xcode&lt;span class="w"&gt; &lt;/span&gt;sdk
Installed&lt;span class="w"&gt; &lt;/span&gt;Python-3.10.6&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/.pyenv/versions/3.10.6

&lt;span class="c1"&gt;# Use version globally&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6

&lt;span class="c1"&gt;# Use version locally&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6

$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;version
&lt;span class="m"&gt;3&lt;/span&gt;.10.6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/gilded-rose-kata/.python-version&lt;span class="o"&gt;)&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;./.python-version
&lt;span class="m"&gt;3&lt;/span&gt;.10.6

$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;--version
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1 id="python-dependency-management"&gt;Python dependency management&lt;a class="headerlink" href="#python-dependency-management" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;For this I like to use &lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt;, for a few reasons:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integrated virtual environement support&lt;/li&gt;
&lt;li&gt;Dependency groups - Easy separation between production dependencies and dev/test/etc dependencies&lt;/li&gt;
&lt;li&gt;Lock file - specify desired versions with wildcards and inequalities, but still get repeatable builds with exactly the same versions&lt;/li&gt;
&lt;li&gt;IDE support - PyCharm and VScode&lt;/li&gt;
&lt;li&gt;Python version spec - constrain acceptable Python version just like any other dependency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="poetry logo" src="https://ryanmckaytx.github.io/images/poetry-logo-origami.png" title="poetry"&gt;&lt;/p&gt;
&lt;h2 id="poetry-installation"&gt;Poetry Installation&lt;a class="headerlink" href="#poetry-installation" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-sSL&lt;span class="w"&gt; &lt;/span&gt;https://install.python-poetry.org&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By default, poetry will create new virtual environments under {cache-dir}/virtualenvs, 
but I prefer to have the virtual env for a project stored in the project directory.
So after installation, I configure poetry to do that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;virtualenvs.in-project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="project-initialization"&gt;Project Initialization&lt;a class="headerlink" href="#project-initialization" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--src&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata
Created&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;gilded_rose_kata&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata
$&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata
gilded-rose-kata
├──&lt;span class="w"&gt; &lt;/span&gt;README.md
├──&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml
├──&lt;span class="w"&gt; &lt;/span&gt;src
│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;gilded_rose_kata
│&lt;span class="w"&gt;       &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
└──&lt;span class="w"&gt; &lt;/span&gt;tests
&lt;span class="w"&gt;    &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This created the basic project structure, including a &lt;code&gt;pyproject.toml&lt;/code&gt; file, 
which is where project dependencies are declared, and also where various tools (like pytest, pylint, etc) can be configured.&lt;/p&gt;
&lt;p&gt;We don't have any dependencies yet, but we can go ahead and create a virtual environment and a lockfile. 
The lockfile is good for making sure that every install gets exactly the same versions of dependencies.
Because of this property, we can use it for dependency caching in our CI job later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install
Creating&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/gilded-rose-kata/.venv
Updating&lt;span class="w"&gt; &lt;/span&gt;dependencies
Resolving&lt;span class="w"&gt; &lt;/span&gt;dependencies...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1s&lt;span class="o"&gt;)&lt;/span&gt;

Writing&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="w"&gt; &lt;/span&gt;file

Installing&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;current&lt;span class="w"&gt; &lt;/span&gt;project:&lt;span class="w"&gt; &lt;/span&gt;gilded-rose-kata&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can activate the virtual env with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;shell
Spawning&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;within&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/gilded-rose-kata/.venv
gilded-rose-kata&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/gilded-rose-kata/.venv/bin/activate

$&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;python
/Users/ryanmckay/projects/gilded-rose-kata/.venv/bin/python

$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;--version
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One feature of poetry that I really like is &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will synchronize the dependencies installed in the virtual env with the current state of the poetry.lock file (including removing/downgrading dependencies if necessary). 
This is very useful when you are jumping around between branches that might have different dependencies specified.&lt;/p&gt;
&lt;h1 id="testing"&gt;Testing&lt;a class="headerlink" href="#testing" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The two main testing libraries for Python are unittest (built in to Python) and pytest. I like pytest for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Informative assertion failures&lt;/li&gt;
&lt;li&gt;Easy, robust fixture management&lt;/li&gt;
&lt;li&gt;Lots of useful plugins&lt;/li&gt;
&lt;li&gt;Low explicit dependence on test framework&lt;ul&gt;
&lt;li&gt;No inheriting from Testcase&lt;/li&gt;
&lt;li&gt;No self.assertEqual, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Supports existing UnitTest-based tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="pytest logo" src="https://ryanmckaytx.github.io/images/pytest-200.png" title="pytest"&gt;&lt;/p&gt;
&lt;h2 id="pytest-plugins-and-built-ins"&gt;Pytest Plugins and Built-ins&lt;a class="headerlink" href="#pytest-plugins-and-built-ins" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are almost 1500 &lt;a href="https://docs.pytest.org/en/stable/reference/plugin_list.html"&gt;registered plugins&lt;/a&gt; for pytest. 
Here are a few plugins (and built-ins) I have found useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/example/simple.html#profiling-test-duration"&gt;--durations&lt;/a&gt; - pytest has built-in support for measuring and reporting test durations. 
I use this to identify the slowest-running tests, in order to target them for speeding up.
&lt;a href="https://pypi.org/project/pytest-durations/"&gt;pytest-durations&lt;/a&gt; is a plugin that gives more detailed information broken down into fixture creation, setup, run, and teardown.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/how-to/logging.html#caplog-fixture"&gt;caplog&lt;/a&gt;/&lt;a href="https://pypi.org/project/pytest-loguru/"&gt;pytest-loguru&lt;/a&gt; - caplog is useful for making assertions about logging activity in the code under test.
I like to use &lt;a href="https://loguru.readthedocs.io/en/stable/"&gt;loguru&lt;/a&gt; for logging with context, and the pytest-loguru plugin makes those logs available to caplog.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytest-asyncio/"&gt;pytest-asyncio&lt;/a&gt; - convenient for testing async code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytest-cov/"&gt;pytest-cov&lt;/a&gt; - capture coverage report from test run. 
This can be configured to fail if coverage is under a specified %.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytest-datadir/"&gt;pytest-datadir&lt;/a&gt; - pytest plugin for test data directories and files. 
I mostly use datadir to hold input files or expected output files I want to compare against. 
pytest-datadir creates a pristine copy of the datadir for each test run, so you can write output files to it for post-hoc analysis, without interfering with other tests.&lt;br&gt;
It cleans up these copies automatically after a while.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytest-rerunfailures/"&gt;pytest-rerunfailures&lt;/a&gt; - mark specific tests as flaky and specify a retry policy. Obviously it is better to root cause and remove the flakiness, but sometimes you need a quick fix.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytest-timeout/"&gt;pytest-timeout&lt;/a&gt; - use this to kill tests that take too long.  The threshold can be set globally or on specific tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;pytest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Poetry lets you group dependencies.  This is handy for keeping your dev dependencies out of your production deployable.&lt;/p&gt;
&lt;h2 id="test"&gt;Test&lt;a class="headerlink" href="#test" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Starting with the existing &lt;a href="https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/python/gilded_rose.py"&gt;gilded rose implementation&lt;/a&gt;
(with some minor modifications to show off pytest fixtures), let's add a quick red test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;gilded_rose_kata.gilded_rose&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GildedRose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;

&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rose&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GildedRose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_update_quality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rose&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# GIVEN&lt;/span&gt;
    &lt;span class="n"&gt;initial_quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Conjured Mana Cake&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sell_in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;initial_quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# EXPECT quality should have degraded by 2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;rose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_quality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice how easy it is to set up reusable test fixtures. 
If any test wants to use the &lt;code&gt;rose&lt;/code&gt; fixture, it just has to add the &lt;code&gt;rose&lt;/code&gt; parameter.
Test output looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pytest&lt;/span&gt;
&lt;span class="o"&gt;==================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=================================&lt;/span&gt;
platform&lt;span class="w"&gt; &lt;/span&gt;darwin&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.6,&lt;span class="w"&gt; &lt;/span&gt;pytest-8.3.3,&lt;span class="w"&gt; &lt;/span&gt;pluggy-1.5.0
rootdir:&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/gilded-rose-kata
configfile:&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml
collected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;item

tests/test_gilded_rose.py&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;                                                      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;=======================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAILURES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=======================================&lt;/span&gt;
__________________________________&lt;span class="w"&gt; &lt;/span&gt;test_update_quality&lt;span class="w"&gt; &lt;/span&gt;_________________________________

&lt;span class="nv"&gt;rose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gilded_rose_kata.gilded_rose.GildedRose&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x105655f00&amp;gt;

&lt;span class="w"&gt;    &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;test_update_quality&lt;span class="o"&gt;(&lt;/span&gt;rose&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# GIVEN&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;initial_quality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Item&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Conjured Mana Cake&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sell_in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;initial_quality&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# EXPECT quality should have degraded by 2&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;rose.update_quality&lt;span class="o"&gt;(&lt;/span&gt;item&lt;span class="o"&gt;)&lt;/span&gt;.quality&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
E&lt;span class="w"&gt;       &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Conjured&lt;span class="w"&gt; &lt;/span&gt;Mana&lt;span class="w"&gt; &lt;/span&gt;Cake,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.quality
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;    &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;Conjured&lt;span class="w"&gt; &lt;/span&gt;Mana&lt;span class="w"&gt; &lt;/span&gt;Cake,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;update_quality&lt;span class="o"&gt;(&lt;/span&gt;Conjured&lt;span class="w"&gt; &lt;/span&gt;Mana&lt;span class="w"&gt; &lt;/span&gt;Cake,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
E&lt;span class="w"&gt;        &lt;/span&gt;+&lt;span class="w"&gt;      &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;update_quality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gilded_rose_kata.gilded_rose.GildedRose&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x105655f00&amp;gt;.update_quality

tests/test_gilded_rose.py:15:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AssertionError&lt;/span&gt;
&lt;span class="o"&gt;================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===============================&lt;/span&gt;
FAILED&lt;span class="w"&gt; &lt;/span&gt;tests/test_gilded_rose.py::test_update_quality&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;assert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;
&lt;span class="o"&gt;===================================&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.01s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I really like this detailed assertion failure output. 
I spent most of my career working with Java, where you needed something like &lt;a href="https://ryanmckaytx.github.io/docker-java-example-part-2-spring-web.html"&gt;Spock&lt;/a&gt; to get output like this.&lt;/p&gt;
&lt;h1 id="pre-commit"&gt;Pre-commit&lt;a class="headerlink" href="#pre-commit" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://pre-commit.com/"&gt;Pre-commit&lt;/a&gt; is a tool for running a variety of static checks as a git precommit hook. 
Two of my favorites for Python are &lt;a href="https://github.com/psf/black"&gt;black&lt;/a&gt; and &lt;a href="https://github.com/pylint-dev/pylint"&gt;pylint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pre-commit logo" src="https://ryanmckaytx.github.io/images/pre-commit-logo.png" title="pre-commit"&gt;&lt;/p&gt;
&lt;h2 id="installation_1"&gt;Installation&lt;a class="headerlink" href="#installation_1" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;with &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; configured as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://github.com/psf/black&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;22.6.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://github.com/pylint-dev/pylint&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;v2.15.0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Replace with the latest version of pylint&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pylint&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;--disable=C0114,C0116&amp;#39;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Add any pylint disables you want here&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then install&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;pre-commit
$&lt;span class="w"&gt; &lt;/span&gt;pre-commit&lt;span class="w"&gt; &lt;/span&gt;install-hooks
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initializing&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://github.com/psf/black.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Initializing&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://github.com/pylint-dev/pylint.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Installing&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://github.com/psf/black.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Once&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;reused.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;This&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;take&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;few&lt;span class="w"&gt; &lt;/span&gt;minutes...
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Installing&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://github.com/pylint-dev/pylint.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Once&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;reused.
&lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;This&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;take&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;few&lt;span class="w"&gt; &lt;/span&gt;minutes...
$&lt;span class="w"&gt; &lt;/span&gt;pre-commit&lt;span class="w"&gt; &lt;/span&gt;install
pre-commit&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;.git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="run"&gt;Run&lt;a class="headerlink" href="#run" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After installing the git hook, pre-commit will automatically run on staged files every time you &lt;code&gt;git commit&lt;/code&gt;.
We can also run the hooks against all files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pre-commit&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--all-files
black....................................................................Failed
-&lt;span class="w"&gt; &lt;/span&gt;hook&lt;span class="w"&gt; &lt;/span&gt;id:&lt;span class="w"&gt; &lt;/span&gt;black
-&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;were&lt;span class="w"&gt; &lt;/span&gt;modified&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;hook

reformatted&lt;span class="w"&gt; &lt;/span&gt;tests/test_gilded_rose.py
reformatted&lt;span class="w"&gt; &lt;/span&gt;src/gilded_rose_kata/gilded_rose.py

All&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;✨&lt;span class="w"&gt; &lt;/span&gt;🍰&lt;span class="w"&gt; &lt;/span&gt;✨
&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;reformatted,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;left&lt;span class="w"&gt; &lt;/span&gt;unchanged.

pylint...................................................................Failed
-&lt;span class="w"&gt; &lt;/span&gt;hook&lt;span class="w"&gt; &lt;/span&gt;id:&lt;span class="w"&gt; &lt;/span&gt;pylint
-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;

*************&lt;span class="w"&gt; &lt;/span&gt;Module&lt;span class="w"&gt; &lt;/span&gt;gilded_rose_kata.gilded_rose
src/gilded_rose_kata/gilded_rose.py:4:0:&lt;span class="w"&gt; &lt;/span&gt;C0115:&lt;span class="w"&gt; &lt;/span&gt;Missing&lt;span class="w"&gt; &lt;/span&gt;class&lt;span class="w"&gt; &lt;/span&gt;docstring&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;missing-class-docstring&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:11:15:&lt;span class="w"&gt; &lt;/span&gt;C0209:&lt;span class="w"&gt; &lt;/span&gt;Formatting&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;regular&lt;span class="w"&gt; &lt;/span&gt;string&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;could&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;f-string&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;consider-using-f-string&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:4:0:&lt;span class="w"&gt; &lt;/span&gt;R0903:&lt;span class="w"&gt; &lt;/span&gt;Too&lt;span class="w"&gt; &lt;/span&gt;few&lt;span class="w"&gt; &lt;/span&gt;public&lt;span class="w"&gt; &lt;/span&gt;methods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/2&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;too-few-public-methods&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:14:0:&lt;span class="w"&gt; &lt;/span&gt;C0115:&lt;span class="w"&gt; &lt;/span&gt;Missing&lt;span class="w"&gt; &lt;/span&gt;class&lt;span class="w"&gt; &lt;/span&gt;docstring&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;missing-class-docstring&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:14:0:&lt;span class="w"&gt; &lt;/span&gt;R0205:&lt;span class="w"&gt; &lt;/span&gt;Class&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GildedRose&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inherits&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;object,&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;safely&lt;span class="w"&gt; &lt;/span&gt;removed&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;bases&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;useless-object-inheritance&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:19:12:&lt;span class="w"&gt; &lt;/span&gt;R1714:&lt;span class="w"&gt; &lt;/span&gt;Consider&lt;span class="w"&gt; &lt;/span&gt;merging&lt;span class="w"&gt; &lt;/span&gt;these&lt;span class="w"&gt; &lt;/span&gt;comparisons&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;updated_item.name not in (&amp;#39;&lt;/span&gt;Aged&lt;span class="w"&gt; &lt;/span&gt;Brie&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;Backstage&lt;span class="w"&gt; &lt;/span&gt;passes&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;TAFKAL80ETC&lt;span class="w"&gt; &lt;/span&gt;concert&lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;instead&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;elements&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;hashable.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;consider-using-in&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:15:4:&lt;span class="w"&gt; &lt;/span&gt;R0912:&lt;span class="w"&gt; &lt;/span&gt;Too&lt;span class="w"&gt; &lt;/span&gt;many&lt;span class="w"&gt; &lt;/span&gt;branches&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;/12&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;too-many-branches&lt;span class="o"&gt;)&lt;/span&gt;
src/gilded_rose_kata/gilded_rose.py:14:0:&lt;span class="w"&gt; &lt;/span&gt;R0903:&lt;span class="w"&gt; &lt;/span&gt;Too&lt;span class="w"&gt; &lt;/span&gt;few&lt;span class="w"&gt; &lt;/span&gt;public&lt;span class="w"&gt; &lt;/span&gt;methods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/2&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;too-few-public-methods&lt;span class="o"&gt;)&lt;/span&gt;
*************&lt;span class="w"&gt; &lt;/span&gt;Module&lt;span class="w"&gt; &lt;/span&gt;tests.test_gilded_rose
tests/test_gilded_rose.py:1:0:&lt;span class="w"&gt; &lt;/span&gt;E0401:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pytest&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;import-error&lt;span class="o"&gt;)&lt;/span&gt;
tests/test_gilded_rose.py:10:24:&lt;span class="w"&gt; &lt;/span&gt;W0621:&lt;span class="w"&gt; &lt;/span&gt;Redefining&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rose&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;outer&lt;span class="w"&gt; &lt;/span&gt;scope&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;redefined-outer-name&lt;span class="o"&gt;)&lt;/span&gt;

------------------------------------------------------------------
Your&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;rated&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.74/10&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;previous&lt;span class="w"&gt; &lt;/span&gt;run:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.74/10,&lt;span class="w"&gt; &lt;/span&gt;+0.00&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both hooks failed.  If that had happened during &lt;code&gt;git commit&lt;/code&gt;, it would have aborted the commit.
The &lt;code&gt;black&lt;/code&gt; hook fixes python formatting, so all we need to do is commit the modified files.
I like this approach for running black, because you can see what it did.&lt;/p&gt;
&lt;p&gt;A pylint score of &lt;code&gt;6.74&lt;/code&gt; is pretty low.  By default it fails with anything less than 10/10.
You can set the failing score threshold with &lt;code&gt;--fail-under&lt;/code&gt;, 
which is convenient for setting a linting floor when you are adding linting to an existing project.&lt;/p&gt;</content><category term="misc"></category><category term="python"></category></entry><entry><title>Essential Reading/Watching for Software Engineers</title><link href="https://ryanmckaytx.github.io/essential-reading-watching-for-software-engineers.html" rel="alternate"></link><published>2018-04-15T10:30:00-05:00</published><updated>2018-04-15T10:30:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2018-04-15:/essential-reading-watching-for-software-engineers.html</id><summary type="html">&lt;p&gt;I've been really fortunate through the years to be surrounded by people who are dedicated to continuous learning and improvement.  A few years ago, I put together a reading list as part of an engineering department reboot.  I updated it late last year.  I have rough reading/discussion times for some of it.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#notes"&gt;Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#agile"&gt;Agile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#clean-code"&gt;Clean Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#refactoring"&gt;Refactoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#effective-java-2nd-edition"&gt;Effective Java (2nd edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#domain-driven-design"&gt;Domain Driven Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#microservices"&gt;Microservices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing"&gt;Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#restapi-design"&gt;REST/API design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#event-sourcingcqrs"&gt;Event Sourcing/CQRS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#continuous-delivery"&gt;Continuous Delivery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#working-effectively-with-legacy-code"&gt;Working Effectively with Legacy Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#further-reading"&gt;Further Reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;I've been really fortunate through the years to be surrounded by people who are dedicated to continuous learning and improvement.  A few years ago, I put together a reading list as part of an engineering department reboot.  I updated it late last year.  I have rough reading/discussion times for some of it.  What am I missing?  &lt;/p&gt;
&lt;h1 id="notes"&gt;Notes&lt;a class="headerlink" href="#notes" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Video time slots are roughly 80% watching, 20% discussion&lt;/li&gt;
&lt;li&gt;Reading time slots are roughly 66% reading, 33% discussion&lt;/li&gt;
&lt;li&gt;Long chapters are broken up into 2 sessions&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="agile"&gt;Agile&lt;a class="headerlink" href="#agile" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Extreme Programming Explained Ch. 3-7 - 2 hours&lt;ul&gt;
&lt;li&gt;Chapter 3 - Values, Principles, and Practices&lt;/li&gt;
&lt;li&gt;Chapter 4 - Values&lt;/li&gt;
&lt;li&gt;Chapter 5 - Principles&lt;/li&gt;
&lt;li&gt;Chapter 6 - Practices&lt;/li&gt;
&lt;li&gt;Chapter 7 - Primary Practices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://theagileadmin.com/2010/10/15/a-devops-manifesto"&gt;A DevOps Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.ingineering.it/post/39385342347/21st-century-it-manifesto"&gt;21st Century IT Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.jamesshore.com/Blog/Red-Green-Refactor.html"&gt;Red-Green-Refactor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/"&gt;Best Practices for Code Review&lt;/a&gt; - 15 minutes&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.ingineering.it/post/23086269936/user-centered-it-and-t-shaped-people&amp;quot;"&gt;T-shaped people&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="" title="http://lisacrispin.com/2011/04/26/the-whole-team-approach-in-practice/"&gt;The Whole Team Approach in Practice&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="clean-code"&gt;Clean Code&lt;a class="headerlink" href="#clean-code" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Video: Clean Code Episode 1: Clean Code - 1.25 hours&lt;ul&gt;
&lt;li&gt;Covers Clean Code Book Ch.1: Clean Code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 2: Names - 1 hour&lt;ul&gt;
&lt;li&gt;Covers Clean Code Book Ch.2: Meaningful Names&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 3: Functions - 1.25 hours&lt;ul&gt;
&lt;li&gt;Covers Clean Code Book Ch.3: Functions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 4: Function Structure - 2 hours&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 5: Form - 1.5 hours&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 6: Testing part 1 - 1.25 hours&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 6: Testing part 2 - 1.5 hours&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vimeo.com/43734265"&gt;TDD excerpt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 7: Architecture - 1.75 hours&lt;/li&gt;
&lt;li&gt;Video: Clean Code Episode 8: Foundations of the SOLID principles - 1.25 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="refactoring"&gt;Refactoring&lt;a class="headerlink" href="#refactoring" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Chapter 1 - Refactoring, a First Example - 45 minutes&lt;/li&gt;
&lt;li&gt;Chapter 2 - Principles in Refactoring - 1.5 hours&lt;/li&gt;
&lt;li&gt;Chapter 3 - Bad Smells in Code - 45 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="effective-java-2nd-edition"&gt;Effective Java (2nd edition)&lt;a class="headerlink" href="#effective-java-2nd-edition" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Chapter 4 - Classes and Interfaces - 1.5 hours (est)&lt;/li&gt;
&lt;li&gt;Chapter 7 - Methods - 45 minutes (est)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="domain-driven-design"&gt;Domain Driven Design&lt;a class="headerlink" href="#domain-driven-design" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://drive.google.com/open?id=1yhsCaWzQv93bjkpmUYJGELcVOnAEGjpxI5J9P5hXAgg&amp;quot;"&gt;DDD Core Elements&lt;/a&gt; - 30 min&lt;/li&gt;
&lt;li&gt;&lt;a href="https://herbertograca.com/2017/09/07/domain-driven-design/"&gt;Domain Driven Design&lt;/a&gt; - 30 minutes&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.infoq.com/presentations/Value-Objects-Dan-Bergh-Johnsson"&gt;Value Objects&lt;/a&gt; - 1.25 hours&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/resource/minibooks/domain-driven-design-quickly/en/pdf/DomainDrivenDesignQuicklyOnline.pdf"&gt;Domain Driven Design Quickly&lt;/a&gt; - 2.5 hours (est)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="microservices"&gt;Microservices&lt;a class="headerlink" href="#microservices" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vimeo.com/99531595"&gt;Video: The Practical Implications of Microservices&lt;/a&gt; - by Sam Newman, the author of “Building Microservices” - 1.25 hours&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vimeo.com/100930174"&gt;Video: Deploying and Testing Microservices&lt;/a&gt; - also Sam Newman - 1.5 hours&lt;/li&gt;
&lt;li&gt;Building Microservices book&lt;ul&gt;
&lt;li&gt;Chapter 1 - Microservices - 45 minutes&lt;/li&gt;
&lt;li&gt;Chapter 3 - How to Model Services - 45 minutes&lt;/li&gt;
&lt;li&gt;Chapter 4 - Integration&lt;ul&gt;
&lt;li&gt;Part 1: Beginning of chapter through "Downsides to REST over HTTP" - 2 hours&lt;/li&gt;
&lt;li&gt;Part 2: "Implementing Asynchronous Event-Based Collaboration" through end of chapter - 2 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Chapter 5 - Splitting the Monolith&lt;ul&gt;
&lt;li&gt;Part 1: Beginning of chapter thru "So What to Do?" - 1.5 hours&lt;/li&gt;
&lt;li&gt;Part 2: "Reporting" thru end of chapter - 1.5 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Chapter 7 - Testing  &lt;ul&gt;
&lt;li&gt;Part 1: Beginning of chapter through "The Metaversion" - 1.5 hours&lt;/li&gt;
&lt;li&gt;Part 2: "Test Journeys, Not Stories" through end of chapter - 1.5 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="http://12factor.net/"&gt;The Twelve-Factor App&lt;/a&gt; - 1.5 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="testing"&gt;Testing&lt;a class="headerlink" href="#testing" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.infoq.com/presentations/tdd-original"&gt;Video: TDD, Where did it all go wrong&lt;/a&gt; - 1.5 hours&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="restapi-design"&gt;REST/API design&lt;a class="headerlink" href="#restapi-design" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://drive.google.com/open?id=1ZJ-k6Aeqx0ISewdbg6ANTXk9ppky4Pn9Kf1BYtpRi2Y&amp;quot;"&gt;REST Intro&lt;/a&gt; - 1 hour&lt;ul&gt;
&lt;li&gt;Summary of &lt;a href="http://shop.oreilly.com/product/9780596805838.do"&gt;Rest In Practice&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apigee.com/api-management/#/ebook/768"&gt;Apigee Web API Design ebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tenderware.blogspot.com/2011/05/magnanimous-writer.html"&gt;MagnanimousWriter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="event-sourcingcqrs"&gt;Event Sourcing/CQRS&lt;a class="headerlink" href="#event-sourcingcqrs" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=fU9hR3kiOK0"&gt;Video: Turning the database inside out with Apache Samza&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Not essential but really interesting: &lt;a href="https://www.youtube.com/watch?v=B1-gS0oEtYc"&gt;Video: Commander: Better Distributed Applications through CQRS and Event Sourcing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="" title="https://www.youtube.com/watch?v=LDW0QWie21s"&gt;Video: Greg Young — A Decade of DDD, CQRS, Event Sourcing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="continuous-delivery"&gt;Continuous Delivery&lt;a class="headerlink" href="#continuous-delivery" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Chapter 1 - The Problem of Delivering Software&lt;/li&gt;
&lt;li&gt;Chapter 2 - Configuration Management&lt;/li&gt;
&lt;li&gt;Chapter 7 - The Commit Stage&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="working-effectively-with-legacy-code"&gt;Working Effectively with Legacy Code&lt;a class="headerlink" href="#working-effectively-with-legacy-code" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Chapter 4 - The Seam Model&lt;/li&gt;
&lt;li&gt;Chapter 9 - I can't get this class into a test harness&lt;/li&gt;
&lt;li&gt;Chapter 13 - I need to make a change, but I don't know what tests to write&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="further-reading"&gt;Further Reading&lt;a class="headerlink" href="#further-reading" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Agile Software Development (If you don't have access to this book, see &lt;a href="http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod"&gt;Uncle Bob's blog about SOLID principles&lt;/a&gt;)&lt;ul&gt;
&lt;li&gt;Chapter 7 - What is Agile Design?&lt;/li&gt;
&lt;li&gt;Chapter 8 - SRP: The Single-Responsibility Principle&lt;/li&gt;
&lt;li&gt;Chapter 9 - OCP: The Open-Close Principle&lt;/li&gt;
&lt;li&gt;Chapter 10 - LSP: The Liskov Substitution Principle&lt;/li&gt;
&lt;li&gt;Chapter 11 - DIP: The Dependency-Inversion Principle&lt;/li&gt;
&lt;li&gt;Chapter 12 - ISP: The Interface-Segregation Principle&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implementing Domain Driven Design&lt;ul&gt;
&lt;li&gt;Ch. 4: Architecture&lt;/li&gt;
&lt;li&gt;Ch. 5: Entities&lt;/li&gt;
&lt;li&gt;Ch. 6: Value Objects&lt;/li&gt;
&lt;li&gt;Ch. 7: Services&lt;/li&gt;
&lt;li&gt;Ch. 8: Domain Events&lt;/li&gt;
&lt;li&gt;Ch. 9: Modules&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"></category></entry><entry><title>Docker Java Example Part 5: Kubernetes</title><link href="https://ryanmckaytx.github.io/docker-java-example-part-5-kubernetes.html" rel="alternate"></link><published>2017-09-14T17:28:00-05:00</published><updated>2017-09-14T17:28:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-09-14:/docker-java-example-part-5-kubernetes.html</id><summary type="html">&lt;p&gt;Now that I've got my project getting packaged up in a docker image, the next step in this POC is to look at platforms for running docker. The only PaaS that I am familiar with now is Pivotal Cloud Foundry, which we used at my last job to deploy Spring Boot executable jars. PCF was working on a docker story, not sure how far that got. It looks like they are pretty &lt;a href="https://pivotal.io/pks"&gt;bought into Kubernetes&lt;/a&gt; these days.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;div class="toctitle"&gt;Docker Java Example Series&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-1-initializing.html"&gt;Initializing a new Spring Boot Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-2-spring-web.html"&gt;Spring Web MVC Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part3-transmode-gradle-plugin.html"&gt;Transmode Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html"&gt;Bmuschko and Nebula Gradle Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-5-kubernetes.html"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;Now that I've got my project getting packaged up in a docker image, the next step in this POC is to look at platforms for running docker. The only PaaS that I am familiar with now is Pivotal Cloud Foundry, which we used at my last job to deploy Spring Boot executable jars. PCF was working on a docker story, not sure how far that got. It looks like they are pretty &lt;a href="https://pivotal.io/pks"&gt;bought into Kubernetes&lt;/a&gt; these days. In fact it seems like the whole cloud world is moving in that direction, with the likes of Pivotal, VMware, Amazon, Microsoft, Dell, Alibaba, and Mesosphere joining the &lt;a href="https://www.cncf.io/"&gt;Cloud Native Computing Foundation&lt;/a&gt;. So, I set out to learn more about Kubernetes.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Kubernetes logo" src="https://ryanmckaytx.github.io/images/kubernetes.png" title="Kubernetes"&gt;&lt;/p&gt;
&lt;p&gt;I started out by following the excellent &lt;a href="https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/"&gt;Hello Minikube&lt;/a&gt; tutorial provided in the kubernetes docs. It steps you through installing local kubernetes (a.k.a. minikube), creating a docker image, deploying it to kubernetes, making it accessible outside the cluster, and live updating the running image. I followed the tutorial as written first, then applied it to my demo java project. Of course, I ran into some issues.&lt;/p&gt;
&lt;h1 id="making-minikube-aware-of-your-docker-image"&gt;Making Minikube Aware of your Docker Image&lt;a class="headerlink" href="#making-minikube-aware-of-your-docker-image" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Minikube runs its own Docker daemon. As outlined &lt;a href="https://blog.hasura.io/sharing-a-local-registry-for-minikube-37c7240d0615"&gt;here&lt;/a&gt;, you have a few options for getting your docker images into minikube. Part of the hello minikube tutorial is to point your local docker client at the minikube docker daemon, and build your image there:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;docker-env&lt;span class="k"&gt;)&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;DOCKER
&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://192.168.64.2:2376
&lt;span class="nv"&gt;DOCKER_API_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.23
&lt;span class="nv"&gt;DOCKER_TLS_VERIFY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_CERT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/ryanmckay/.minikube/certs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That works fine in the tutorial, because they are using the docker cli tool, which respects those env variables. Unfortunately, the &lt;a href="https://github.com/bmuschko/gradle-docker-plugin"&gt;bmuschko gradle docker plugin&lt;/a&gt; does not. But it can be &lt;a href="https://github.com/bmuschko/gradle-docker-plugin#extension-properties"&gt;configured&lt;/a&gt; to relatively easily. &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.5.1"&gt;java-docker-example v0.5.1&lt;/a&gt; adds:&lt;/p&gt;
&lt;script src="https://gist.github.com/ryanmckaytx/8dec1f69b1b1539f50b2c3a0c1dcad5e.js"&gt;&lt;/script&gt;

&lt;p&gt;So now you can build the docker image into kubernetes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./gradlew&lt;span class="w"&gt; &lt;/span&gt;buildImage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And you can stop pointing at kubernetes' docker instance with:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;docker-env&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'm not sure this is the long term strategy for local dev, but at least it makes gradle and docker cli work the same way, which seems appropriate.&lt;/p&gt;
&lt;h1 id="kubernetes-concepts"&gt;Kubernetes Concepts&lt;a class="headerlink" href="#kubernetes-concepts" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;It's worth looking over the &lt;a href="https://kubernetes.io/docs/concepts/"&gt;kubernetes concepts docs&lt;/a&gt; to understand the domain language.  A &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"&gt;deployment&lt;/a&gt; is a declaration of how you want your container deployed.  It specifies things like which image to deploy, how many instances it should have, ports to expose, etc.  A deployment is mutable. The configuration of a live deployment can be modified to, e.g. target a new docker image, change number of replicas, etc.  &lt;/p&gt;
&lt;p&gt;A deployment manages one or more &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/"&gt;replica sets&lt;/a&gt;.  Each replica set corresponds to a distinct configuration of the deployment.  So if the docker image config is changed on the deployment, a new replica set representing the new config is created.  The deployment remembers the mapping from configuration to replica set, so if it sees the same configuration again, it will reuse an existing replica set. Replica sets managed by deployments should not be modified directly, even though the api allows it.&lt;/p&gt;
&lt;p&gt;A replica set manages one or more &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/"&gt;pods&lt;/a&gt;, depending on the number of desired replicas.  In most cases, a pod runs a single container, though it can be configured to run &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/#how-pods-manage-multiple-containers"&gt;multiple containers&lt;/a&gt; that need to be colocated on the same cluster node.&lt;/p&gt;
&lt;h1 id="create-a-deployment"&gt;Create a Deployment&lt;a class="headerlink" href="#create-a-deployment" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A complete deployment spec is a lengthy document, but kubernetes provides a quick and easy way to create one with minimal input:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;java-docker-example&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="o"&gt;=&lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
deployment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;java-docker-example” created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then you can look at the deployment on the cli with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;java-docker-example
NAME&lt;span class="w"&gt;                  &lt;/span&gt;DESIRED&lt;span class="w"&gt;   &lt;/span&gt;CURRENT&lt;span class="w"&gt;   &lt;/span&gt;UP-TO-DATE&lt;span class="w"&gt;   &lt;/span&gt;AVAILABLE&lt;span class="w"&gt;   &lt;/span&gt;AGE
java-docker-example&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;1d

$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;java-docker-example
Name:&lt;span class="w"&gt;            &lt;/span&gt;java-docker-example
Namespace:&lt;span class="w"&gt;        &lt;/span&gt;default
CreationTimestamp:&lt;span class="w"&gt;    &lt;/span&gt;Thu,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;07&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2017&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00:37&lt;span class="w"&gt; &lt;/span&gt;-0500
Labels:&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Annotations:&lt;span class="w"&gt;        &lt;/span&gt;deployment.kubernetes.io/revision&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
Selector:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Replicas:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;desired&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="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;updated&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="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;total&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="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;available&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;unavailable
StrategyType:&lt;span class="w"&gt;        &lt;/span&gt;RollingUpdate
MinReadySeconds:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
RollingUpdateStrategy:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;max&lt;span class="w"&gt; &lt;/span&gt;unavailable,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;max&lt;span class="w"&gt; &lt;/span&gt;surge
Pod&lt;span class="w"&gt; &lt;/span&gt;Template&amp;lt;:
&lt;span class="w"&gt;  &lt;/span&gt;Labels:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
&lt;span class="w"&gt;  &lt;/span&gt;Containers:
&lt;span class="w"&gt;   &lt;/span&gt;java-docker-example:
&lt;span class="w"&gt;    &lt;/span&gt;Image:&lt;span class="w"&gt;        &lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT
&lt;span class="w"&gt;    &lt;/span&gt;Port:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;/TCP
&lt;span class="w"&gt;    &lt;/span&gt;Environment:&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;none&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;Mounts:&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;none&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;Volumes:&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;none&amp;gt;
Conditions:
&lt;span class="w"&gt;  &lt;/span&gt;Type&lt;span class="w"&gt;        &lt;/span&gt;Status&lt;span class="w"&gt;    &lt;/span&gt;Reason
&lt;span class="w"&gt;  &lt;/span&gt;----&lt;span class="w"&gt;        &lt;/span&gt;------&lt;span class="w"&gt;    &lt;/span&gt;------
&lt;span class="w"&gt;  &lt;/span&gt;Available&lt;span class="w"&gt;     &lt;/span&gt;True&lt;span class="w"&gt;    &lt;/span&gt;MinimumReplicasAvailable
OldReplicaSets:&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;none&amp;gt;
NewReplicaSet:&lt;span class="w"&gt;    &lt;/span&gt;java-docker-example-3948992014&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/1&lt;span class="w"&gt; &lt;/span&gt;replicas&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="o"&gt;)&lt;/span&gt;
Events:
&lt;span class="w"&gt;  &lt;/span&gt;FirstSeen&lt;span class="w"&gt;    &lt;/span&gt;LastSeen&lt;span class="w"&gt;    &lt;/span&gt;Count&lt;span class="w"&gt;    &lt;/span&gt;From&lt;span class="w"&gt;            &lt;/span&gt;SubObjectPath&lt;span class="w"&gt;    &lt;/span&gt;Type&lt;span class="w"&gt;        &lt;/span&gt;Reason&lt;span class="w"&gt;            &lt;/span&gt;Message
&lt;span class="w"&gt;  &lt;/span&gt;---------&lt;span class="w"&gt;    &lt;/span&gt;--------&lt;span class="w"&gt;    &lt;/span&gt;-----&lt;span class="w"&gt;    &lt;/span&gt;----&lt;span class="w"&gt;            &lt;/span&gt;-------------&lt;span class="w"&gt;    &lt;/span&gt;--------&lt;span class="w"&gt;    &lt;/span&gt;------&lt;span class="w"&gt;            &lt;/span&gt;-------
&lt;span class="w"&gt;  &lt;/span&gt;1d&lt;span class="w"&gt;        &lt;/span&gt;1d&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;deployment-controller&lt;span class="w"&gt;            &lt;/span&gt;Normal&lt;span class="w"&gt;        &lt;/span&gt;ScalingReplicaSet&lt;span class="w"&gt;    &lt;/span&gt;Scaled&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;replica&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;java-docker-example-3948992014&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice the "Pod Template" section that describes the type of pod that will be managed by this deployment (through a replica set). At any given time, a deployment may be managing multiple active replica sets, which may in turn be managing multiple pods. In this example, there is only one replica set, and it is only managing one pod. But if you configured higher replication and rolling update, then during a change to the deployment spec, it will be managing spinning down the old replica set while spinning up the new replica set, at a minimum. If the spec changes faster than kubernetes can apply it, it could be more than that.  &lt;/p&gt;
&lt;p&gt;The ownership relationship can be traversed at the command line. You can see the new and old replica set in the deployment description above. Replica set details can be obtained in similar fashion:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;replicaset&lt;span class="w"&gt; &lt;/span&gt;java-docker-example-3948992014
Name:&lt;span class="w"&gt;  &lt;/span&gt;java-docker-example-3948992014
Namespace:&lt;span class="w"&gt; &lt;/span&gt;default
Selector:&lt;span class="w"&gt; &lt;/span&gt;pod-template-hash&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3948992014&lt;/span&gt;,run&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Labels:&lt;span class="w"&gt;  &lt;/span&gt;pod-template-hash&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3948992014&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Annotations:&lt;span class="w"&gt; &lt;/span&gt;deployment.kubernetes.io/desired-replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;deployment.kubernetes.io/max-replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;deployment.kubernetes.io/revision&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
Controlled&lt;span class="w"&gt; &lt;/span&gt;By:&lt;span class="w"&gt; &lt;/span&gt;Deployment/java-docker-example
Replicas:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;current&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;desired
Pods&lt;span class="w"&gt; &lt;/span&gt;Status:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Waiting&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Succeeded&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Failed
Pod&lt;span class="w"&gt; &lt;/span&gt;Template:
&lt;span class="w"&gt;  &lt;/span&gt;Labels:&lt;span class="w"&gt; &lt;/span&gt;pod-template-hash&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3948992014&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
&lt;span class="w"&gt;  &lt;/span&gt;Containers:
&lt;span class="w"&gt;   &lt;/span&gt;java-docker-example:
&lt;span class="w"&gt;    &lt;/span&gt;Image:&lt;span class="w"&gt;  &lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT
&lt;span class="w"&gt;    &lt;/span&gt;Port:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;/TCP
&lt;span class="w"&gt;    &lt;/span&gt;Environment:&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;none&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;Mounts:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;none&amp;gt;
&lt;span class="w"&gt;  &lt;/span&gt;Volumes:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;none&amp;gt;
Events:
&lt;span class="w"&gt;  &lt;/span&gt;FirstSeen&lt;span class="w"&gt; &lt;/span&gt;LastSeen&lt;span class="w"&gt; &lt;/span&gt;Count&lt;span class="w"&gt; &lt;/span&gt;From&lt;span class="w"&gt;   &lt;/span&gt;SubObjectPath&lt;span class="w"&gt; &lt;/span&gt;Type&lt;span class="w"&gt;  &lt;/span&gt;Reason&lt;span class="w"&gt;   &lt;/span&gt;Message
&lt;span class="w"&gt;  &lt;/span&gt;---------&lt;span class="w"&gt; &lt;/span&gt;--------&lt;span class="w"&gt; &lt;/span&gt;-----&lt;span class="w"&gt; &lt;/span&gt;----&lt;span class="w"&gt;   &lt;/span&gt;-------------&lt;span class="w"&gt; &lt;/span&gt;--------&lt;span class="w"&gt; &lt;/span&gt;------&lt;span class="w"&gt;   &lt;/span&gt;-------
&lt;span class="w"&gt;  &lt;/span&gt;24m&lt;span class="w"&gt;  &lt;/span&gt;24m&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;replicaset-controller&lt;span class="w"&gt;   &lt;/span&gt;Normal&lt;span class="w"&gt;  &lt;/span&gt;SuccessfulCreate&lt;span class="w"&gt; &lt;/span&gt;Created&lt;span class="w"&gt; &lt;/span&gt;pod:&lt;span class="w"&gt; &lt;/span&gt;java-docker-example-3948992014-h1c0l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see the created pods in the replica set's Events log. It is worth noting that the "kubectl describe" command output is intended for human consumption. To get details in a machine readable format, use "kubectl get -o json".&lt;/p&gt;
&lt;h1 id="minikube-dashboard"&gt;Minikube Dashboard&lt;a class="headerlink" href="#minikube-dashboard" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Its good to know the cli, but there is also the very nice&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;dashboard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will launch your browser pointed at the minikube dashboard app. The information we saw at the cli is available and hyperlinked.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ryanmckaytx.github.io/images/minikube-dashboard-deployment.png"&gt;&lt;img alt="Dashboard Deployment" src="https://ryanmckaytx.github.io/images/minikube-dashboard-deployment-thumb.jpg" title="Dashboard Deployment"&gt;&lt;/a&gt;
&lt;a href="https://ryanmckaytx.github.io/images/minikube-dashboard-replicaset.png"&gt;&lt;img alt="Dashboard Replicaset" src="https://ryanmckaytx.github.io/images/minikube-dashboard-replicaset-thumb.jpg" title="Dashboard Replicaset"&gt;&lt;/a&gt;
&lt;a href="https://ryanmckaytx.github.io/images/minikube-dashboard-pod.png"&gt;&lt;img alt="Dashboard Pod" src="https://ryanmckaytx.github.io/images/minikube-dashboard-pod-thumb.jpg" title="Dashboard Pod"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="internal-access-to-container" style="clear: both"&gt;Internal Access to Container&lt;a class="headerlink" href="#internal-access-to-container" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;At this point, the deployed container is running, and you can see logs with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;deployment/java-docker-example
$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;java-docker-example-3948992014-h1c0l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can access it from within the cluster. Note the pod's IP address from the Pod image above. The following will start another pod running busybox.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;--tty&lt;span class="w"&gt; &lt;/span&gt;busybox&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="o"&gt;=&lt;/span&gt;busybox&lt;span class="w"&gt; &lt;/span&gt;--restart&lt;span class="o"&gt;=&lt;/span&gt;Never&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;span class="c1"&gt;# telnet 172.17.0.4:8080&lt;/span&gt;
GET&lt;span class="w"&gt; &lt;/span&gt;/greeting
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;:4,&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are some issues here. We had to know the IP address of the pod. Also, if we were running more replicas, we wouldn't want to be reaching out to one specific instance.  The way to expose pods in kubernetes is through a &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/"&gt;service&lt;/a&gt;. First, note the busybox pod's environment:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# env | sort&lt;/span&gt;
&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox
&lt;span class="nv"&gt;KUBERNETES_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.1:443
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.1:443
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.1
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp
&lt;span class="nv"&gt;KUBERNETES_SERVICE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.1
&lt;span class="nv"&gt;KUBERNETES_SERVICE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;KUBERNETES_SERVICE_PORT_HTTPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/
&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we launch a service for our deployment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;expose&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;java-docker-exampleservice&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;java-docker-example&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;exposed

$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;java-docker-example
Name:&lt;span class="w"&gt;   &lt;/span&gt;java-docker-example
Namespace:&lt;span class="w"&gt;  &lt;/span&gt;default
Labels:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Annotations:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;none&amp;gt;
Selector:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Type:&lt;span class="w"&gt;   &lt;/span&gt;ClusterIP
IP:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.32
Port:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;unset&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;/TCP
Endpoints:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.17.0.4:8080
Session&lt;span class="w"&gt; &lt;/span&gt;Affinity:&lt;span class="w"&gt; &lt;/span&gt;None
Events:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now if we restart our busybox pod, we will have some new env variables related to the new service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# env | sort&lt;/span&gt;
&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.32:8080
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_PORT_8080_TCP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.32:8080
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_PORT_8080_TCP_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.32
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_PORT_8080_TCP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_PORT_8080_TCP_PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_SERVICE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.32
&lt;span class="nv"&gt;JAVA_DOCKER_EXAMPLE_SERVICE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="nv"&gt;KUBERNETES_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.1:443
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp://10.0.0.1:443
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.1
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;KUBERNETES_PORT_443_TCP_PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp
&lt;span class="nv"&gt;KUBERNETES_SERVICE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.1
&lt;span class="nv"&gt;KUBERNETES_SERVICE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;KUBERNETES_SERVICE_PORT_HTTPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/
&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm

&lt;span class="c1"&gt;# telnet $JAVA_DOCKER_EXAMPLE_SERVICE_HOST:$JAVA_DOCKER_EXAMPLE_SERVICE_PORT&lt;/span&gt;
GET&lt;span class="w"&gt; &lt;/span&gt;/greeting
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;:5,&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are a couple of points to make here.  First, if you plan for pods within the cluster to use a service, and you want to use the env variables for discovery, the service needs to be created &lt;em&gt;before&lt;/em&gt; those consuming pods. Second, there are several different service types.  Since we didn't specify a type, we got the default, ClusterIP. This exposes the service only within the cluster.  &lt;/p&gt;
&lt;h1 id="external-access-to-container"&gt;External Access to Container&lt;a class="headerlink" href="#external-access-to-container" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;At some point you're going to want to expose your containers outside the cluster.  The service types build on each other.  &lt;/p&gt;
&lt;h2 id="nodeport-service-type"&gt;NodePort Service Type&lt;a class="headerlink" href="#nodeport-service-type" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;NodePort exposes the service externally on each node's IP at a static port.  This supports managing your own load balancer in front of the nodes. Notice that it also set up a ClusterIP at 10.0.0.131.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;expose&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;java-docker-example&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="o"&gt;=&lt;/span&gt;NodePortservice&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;java-docker-example&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;exposed

$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;java-docker-example
Name:&lt;span class="w"&gt;   &lt;/span&gt;java-docker-example
Namespace:&lt;span class="w"&gt;  &lt;/span&gt;default
Labels:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Annotations:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;none&amp;gt;
Selector:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Type:&lt;span class="w"&gt;   &lt;/span&gt;NodePort
IP:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.131
Port:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;unset&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;/TCP
NodePort:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;unset&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32478&lt;/span&gt;/TCP
Endpoints:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.17.0.4:8080
Session&lt;span class="w"&gt; &lt;/span&gt;Affinity:&lt;span class="w"&gt; &lt;/span&gt;None
Events:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;none&amp;gt;

$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.status.addresses[].address}&amp;#39;&lt;/span&gt;
&lt;span class="m"&gt;192&lt;/span&gt;.168.99.100

$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.99.100:32478/greeting
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;:6,&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="loadbalancer-service-type"&gt;LoadBalancer Service Type&lt;a class="headerlink" href="#loadbalancer-service-type" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This type will configure a cloud-based load balancer for you.  I need to learn more about this, as I did all these exercises on minikube only. Even on minikube though, LoadBalancer type makes your life easier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;expose&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;java-docker-example&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancerservice&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;java-docker-example&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;exposed

$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;java-docker-example
Name:&lt;span class="w"&gt;   &lt;/span&gt;java-docker-example
Namespace:&lt;span class="w"&gt;  &lt;/span&gt;default
Labels:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Annotations:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;none&amp;gt;
Selector:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;java-docker-example
Type:&lt;span class="w"&gt;   &lt;/span&gt;LoadBalancer
IP:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.193
Port:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;unset&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;/TCP
NodePort:&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;unset&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32535&lt;/span&gt;/TCP
Endpoints:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.17.0.4:8080
Session&lt;span class="w"&gt; &lt;/span&gt;Affinity:&lt;span class="w"&gt; &lt;/span&gt;None
Events:&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;none&amp;gt;

$&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;java-docker-exampleOpening&lt;span class="w"&gt; &lt;/span&gt;kubernetes&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;default/java-docker-example&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;browser...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This saves you from having to track down and piece together the node ip and port.&lt;/p&gt;</content><category term="misc"></category><category term="docker"></category><category term="kubernetes"></category></entry><entry><title>Docker Java Example Part 4: Bmuschko and Nebula Gradle Docker Plugins</title><link href="https://ryanmckaytx.github.io/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html" rel="alternate"></link><published>2017-09-01T13:31:00-05:00</published><updated>2017-09-01T13:31:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-09-01:/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html</id><summary type="html">&lt;p&gt;Converting from the &lt;a href="https://github.com/Transmode/gradle-docker"&gt;transmode&lt;/a&gt; gradle plugin to the &lt;a href="https://github.com/bmuschko/gradle-docker-plugin#remote-api-plugin"&gt;bmuschko remote api gradle plugin&lt;/a&gt; was pretty straightforward. Other than importing and applying the plugin, the code to get local docker image creation working is as follows&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;div class="toctitle"&gt;Docker Java Example Series&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-1-initializing.html"&gt;Initializing a new Spring Boot Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-2-spring-web.html"&gt;Spring Web MVC Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part3-transmode-gradle-plugin.html"&gt;Transmode Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html"&gt;Bmuschko and Nebula Gradle Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-5-kubernetes.html"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;Converting from the &lt;a href="https://github.com/Transmode/gradle-docker"&gt;transmode&lt;/a&gt; gradle plugin to the &lt;a href="https://github.com/bmuschko/gradle-docker-plugin#remote-api-plugin"&gt;bmuschko remote api gradle plugin&lt;/a&gt; was pretty straightforward. Other than importing and applying the plugin, the code to get local docker image creation working is as follows:&lt;/p&gt;
&lt;div style="clear: both; text-align: center;"&gt; &lt;/div&gt;

&lt;script src="https://gist.github.com/ryanmckaytx/79440cb30c30e2040800b31af51e0c34.js"&gt;&lt;/script&gt;

&lt;p&gt;Note that bmuschko does support multiple image tags, and I took advantage of that to get the versioned tag as well as the "latest" tag.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;REPOSITORY&lt;span class="w"&gt;                      &lt;/span&gt;TAG&lt;span class="w"&gt;                 &lt;/span&gt;IMAGE&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt;            &lt;/span&gt;CREATED&lt;span class="w"&gt;             &lt;/span&gt;SIZE
ryanmckay/java-docker-example&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.1-SNAPSHOT&lt;span class="w"&gt;      &lt;/span&gt;7fd01d5b247f&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;       &lt;/span&gt;115MB
ryanmckay/java-docker-example&lt;span class="w"&gt;   &lt;/span&gt;latest&lt;span class="w"&gt;              &lt;/span&gt;7fd01d5b247f&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;       &lt;/span&gt;115MB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I tagged the code repo at this point &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.4.1"&gt;v0.4.1&lt;/a&gt;  &lt;/p&gt;
&lt;h2 id="java-application-plugin"&gt;Java Application plugin&lt;a class="headerlink" href="#java-application-plugin" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to the low-level remote api plugin, bmuschko offers an opinionated &lt;a href="https://github.com/bmuschko/gradle-docker-plugin#java-application-plugin"&gt;docker-java-application&lt;/a&gt; plugin based on the &lt;a href="http://www.gradle.org/docs/current/userguide/application_plugin.html"&gt;application gradle plugin&lt;/a&gt;. Using the opinionated plugin cuts down dramatically on the boilerplate in the build.gradle:&lt;/p&gt;
&lt;script src="https://gist.github.com/ryanmckaytx/d287ac6e8131aa1355629910fc19fd4e.js"&gt;&lt;/script&gt;

&lt;p&gt;Unfortunately, this task only supports one tag. By default, you get the versioned one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;REPOSITORY&lt;span class="w"&gt;                      &lt;/span&gt;TAG&lt;span class="w"&gt;                 &lt;/span&gt;IMAGE&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt;            &lt;/span&gt;CREATED&lt;span class="w"&gt;             &lt;/span&gt;SIZE
ryanmckay/java-docker-example&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.1-snapshot&lt;span class="w"&gt;      &lt;/span&gt;415a9e4b201d&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;       &lt;/span&gt;115MB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The generated Dockerfile looks like this:  &lt;/p&gt;
&lt;script src="https://gist.github.com/ryanmckaytx/a0b7ba41cdca53869a71c2f8c0e713e4.js"&gt;&lt;/script&gt;

&lt;p&gt;As an interesting side note, the &lt;a href="https://docs.docker.com/engine/reference/builder/#add"&gt;ADD&lt;/a&gt; Dockerfile directive has special behavior when the file being added is a tar file. In that case, it unpacks it to the destination.  &lt;/p&gt;
&lt;p&gt;The application gradle plugin is a more generic method of packaging up a java application than that offered by the spring boot plugin. It creates a tar file containing the application jar and all the dependency jars. It also contains a shell script for launching the application, which has OS detection and some OS-specific config.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;tf&lt;span class="w"&gt; &lt;/span&gt;build/distributions/java-docker-example-0.0.1-SNAPSHOT.tar&lt;span class="w"&gt; &lt;/span&gt;
java-docker-example-0.0.1-SNAPSHOT/
java-docker-example-0.0.1-SNAPSHOT/lib/
java-docker-example-0.0.1-SNAPSHOT/lib/java-docker-example-0.0.1-SNAPSHOT.jar
java-docker-example-0.0.1-SNAPSHOT/lib/spring-boot-starter-1.5.4.RELEASE.jar
java-docker-example-0.0.1-SNAPSHOT/lib/spring-boot-starter-web-1.5.4.RELEASE.jar
...
java-docker-example-0.0.1-SNAPSHOT/bin/
java-docker-example-0.0.1-SNAPSHOT/bin/java-docker-example
java-docker-example-0.0.1-SNAPSHOT/bin/java-docker-example.bat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I started using gradle about the same time I started using spring boot (which has its own &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-gradle-plugin.html"&gt;gradle plugin&lt;/a&gt; with &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html"&gt;executable jar packaging&lt;/a&gt;), so wasn't familiar with the application plugin. It makes sense that bmuschko would base the opinionated plugin on that, so it can support all types of java applications, not just spring boot.  However, since I plan to exclusively use spring boot for the foreseeable future, and can completely specify the execution environment in Docker (so don't need the OS-related functionality provided by the application plugin), I want to stick with Spring Boot application packaging and running.  &lt;/p&gt;
&lt;p&gt;I left the modifications in a branch tagged as &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.4.2"&gt;v0.4.2&lt;/a&gt;  &lt;/p&gt;
&lt;h2 id="nebula-docker-gradle-plugin"&gt;Nebula docker gradle plugin&lt;a class="headerlink" href="#nebula-docker-gradle-plugin" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Netflix publishes a set of plugins for gradle called &lt;a href="https://github.com/nebula-plugins"&gt;Nebula&lt;/a&gt;. The &lt;a href="https://github.com/nebula-plugins/nebula-docker-plugin"&gt;nebula-docker-plugin&lt;/a&gt; is another opinionated plugin built on top of the bmuschko and application plugins.  It doesn't seem to add a lot beyond the bmuschko application plugin, other than the concept of separate test and production repositories for publishing docker images.  I'm going to look into docker deployment models next so it might come into play there.&lt;/p&gt;</content><category term="misc"></category><category term="gradle"></category><category term="docker"></category></entry><entry><title>Docker Java Example Part 3: Transmode Gradle plugin</title><link href="https://ryanmckaytx.github.io/docker-java-example-part3-transmode-gradle-plugin.html" rel="alternate"></link><published>2017-08-29T15:23:00-05:00</published><updated>2017-08-29T15:23:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-08-29:/docker-java-example-part3-transmode-gradle-plugin.html</id><summary type="html">&lt;p&gt;At last we get to some Docker in this Docker example.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;div class="toctitle"&gt;Docker Java Example Series&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-1-initializing.html"&gt;Initializing a new Spring Boot Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-2-spring-web.html"&gt;Spring Web MVC Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part3-transmode-gradle-plugin.html"&gt;Transmode Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html"&gt;Bmuschko and Nebula Gradle Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-5-kubernetes.html"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;At last we get to some Docker in this Docker example.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Docker logo" src="https://ryanmckaytx.github.io/images/docker-logo.png" title="Docker"&gt;&lt;/p&gt;
&lt;h2 id="gradle-docker-plugin"&gt;Gradle Docker Plugin&lt;a class="headerlink" href="#gradle-docker-plugin" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few prominent docker plugins for gradle: &lt;a href="https://github.com/Transmode/gradle-docker"&gt;transmode&lt;/a&gt;, &lt;a href="https://github.com/bmuschko/gradle-docker-plugin"&gt;bmuschko&lt;/a&gt;, and Netflix &lt;a href="https://github.com/nebula-plugins/nebula-docker-plugin"&gt;nebula&lt;/a&gt;. First, I used transmode, as recommended in the spring boot guide &lt;a href="https://spring.io/guides/gs/spring-boot-docker/"&gt;Spring Boot with Docker&lt;/a&gt;. After adding the Dockerfile:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;openjdk:8-jdk-alpine
VOLUME&lt;span class="w"&gt; &lt;/span&gt;/tmp
ADD&lt;span class="w"&gt; &lt;/span&gt;target/java-docker-example-0.0.1-SNAPSHOT.jar&lt;span class="w"&gt; &lt;/span&gt;app.jar
ENV&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;JAVA_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
ENTRYPOINT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sh&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;java &lt;/span&gt;&lt;span class="nv"&gt;$JAVA_OPTS&lt;/span&gt;&lt;span class="s2"&gt; -Djava.security.egd=file:/dev/./urandom -jar /app.jar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;, and updating build.gradle as described in the guide, I was able to build a docker image for my application:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;gw&lt;span class="w"&gt; &lt;/span&gt;clean&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;buildDocker&lt;span class="w"&gt; &lt;/span&gt;--info

Setting&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;staging&lt;span class="w"&gt; &lt;/span&gt;directory.
Creating&lt;span class="w"&gt; &lt;/span&gt;Dockerfile&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/java-docker-example/java-docker-example/src/main/docker/Dockerfile.
Determining&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;tag:&lt;span class="w"&gt; &lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT
Using&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;native&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;binary.
Sending&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;daemon&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.43MB
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/5&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;openjdk:8-jdk-alpine
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;478bf389b75b
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;/5&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;VOLUME&lt;span class="w"&gt; &lt;/span&gt;/tmp
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Using&lt;span class="w"&gt; &lt;/span&gt;cache
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;136f2d4e58dc
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;/5&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ADD&lt;span class="w"&gt; &lt;/span&gt;target/java-docker-example-0.0.1-SNAPSHOT.jar&lt;span class="w"&gt; &lt;/span&gt;app.jar
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;b3b47b89bbf1
Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;92f637bc67e0
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;/5&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENV&lt;span class="w"&gt; &lt;/span&gt;JAVA_OPTS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;e90c9a3557eb
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;1d3f6526e8e5
Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;e90c9a3557eb
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;/5&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENTRYPOINT&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;java&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$JAVA_OPTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-Djava.security.egd&lt;span class="o"&gt;=&lt;/span&gt;file:/dev/./urandom&lt;span class="w"&gt; &lt;/span&gt;-jar&lt;span class="w"&gt; &lt;/span&gt;/app.jar
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;2fbfb52f836d
---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;f001bdddc80b
Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;2fbfb52f836d
Successfully&lt;span class="w"&gt; &lt;/span&gt;built&lt;span class="w"&gt; &lt;/span&gt;f001bdddc80b
Successfully&lt;span class="w"&gt; &lt;/span&gt;tagged&lt;span class="w"&gt; &lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;images
REPOSITORY&lt;span class="w"&gt;                       &lt;/span&gt;TAG&lt;span class="w"&gt;               &lt;/span&gt;IMAGE&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt;        &lt;/span&gt;CREATED&lt;span class="w"&gt;          &lt;/span&gt;SIZE
ryanmckay/java-docker-example&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.1-SNAPSHOT&lt;span class="w"&gt;    &lt;/span&gt;f001bdddc80b&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minutes&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;    &lt;/span&gt;115MB

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;:8080&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ryanmckay/java-docker-example:0.0.1-SNAPSHOT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="automatically-tracking-application-version"&gt;Automatically Tracking Application Version&lt;a class="headerlink" href="#automatically-tracking-application-version" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Note that the &lt;a href="https://github.com/ryanmckaytx/java-docker-example/blob/991edc8a2d668e3418aae9063b362b06315f6305/src/main/docker/Dockerfile"&gt;Dockerfile&lt;/a&gt; at this point has the application version hard-coded in it. This duplication must not stand. The transmode gradle plugin also supports a dsl for specifying the Dockerfile in build.gradle. Then as part of the build process, it produces the actual Dockerfile.  &lt;/p&gt;
&lt;p&gt;I set about moving line by line of the Dockerfile into the dsl. With one exception it went smoothly. You can see the result in &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.3"&gt;v0.3&lt;/a&gt; of the app. The relevant portion of build.gradle is listed here. You can see its pretty much a line for line translation of the Dockerfile. And since we have access to the jar filename in the build script, nothing needs to be hard coded for docker.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// for docker&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ryanmckay&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;baseImage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;openjdk:8-jdk-alpine&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buildDocker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Docker&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;dependsOn:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;applicationName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseName&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;addFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;archivePath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;app.jar&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setEnvironment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;JAVA_OPTS&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryPoint&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sh&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="env-is-used-at-build-time-and-at-run-time"&gt;ENV is used at build time And at run time&lt;a class="headerlink" href="#env-is-used-at-build-time-and-at-run-time" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One little gotcha in the previous section lead to an interesting learning. My initial attempt to set the JAVA_OPTS env variable looked like this:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;setEnvironment&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;JAVA_OPTS&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but that produced an illegal line in the Dockerfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ENV&lt;span class="w"&gt; &lt;/span&gt;JAVA_OPTS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That led me to read about the &lt;a href="https://docs.docker.com/engine/reference/builder/#env"&gt;ENV&lt;/a&gt; directive in the Dockerfile reference docs.  I was confused about whether ENV directives are used at build time, run time, or both.  Turns out, the answer is both, as I was able to prove to myself with the following. The ENV THE_FILE is used at build time to decide which file to add to the image, and at run time as an environment variable, which can be overriden at the command line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;somefile
somefile&lt;span class="w"&gt; &lt;/span&gt;contents

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;Dockerfile
FROM&lt;span class="w"&gt; &lt;/span&gt;alpine:latest
ENV&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;THE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somefile&amp;quot;&lt;/span&gt;
ADD&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$THE_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;containerizedfile
ENTRYPOINT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sh&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cat containerizedfile &amp;amp;&amp;amp; echo &amp;#39;-----&amp;#39; &amp;amp;&amp;amp; env | sort&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;envtest&lt;span class="w"&gt; &lt;/span&gt;.
Sending&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;daemon&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.072kB
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/4&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;alpine:latest
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;7328f6f8b418
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;/4&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENV&lt;span class="w"&gt; &lt;/span&gt;THE_FILE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somefile&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Using&lt;span class="w"&gt; &lt;/span&gt;cache
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;148a4236ce19
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;/4&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ADD&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$THE_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;containerizedfile
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Using&lt;span class="w"&gt; &lt;/span&gt;cache
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;d44f9e242685
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;/4&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENTRYPOINT&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;containerizedfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-----&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;70de8ceac5ef
&lt;span class="w"&gt; &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;5d875712904a
Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;70de8ceac5ef
Successfully&lt;span class="w"&gt; &lt;/span&gt;built&lt;span class="w"&gt; &lt;/span&gt;5d875712904a
Successfully&lt;span class="w"&gt; &lt;/span&gt;tagged&lt;span class="w"&gt; &lt;/span&gt;envtest:latest

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;envtest
somefile&lt;span class="w"&gt; &lt;/span&gt;contents
-----
&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;36b3233697df
&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/
&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nv"&gt;THE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;somefile
&lt;span class="nv"&gt;no_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;*.local,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;169&lt;/span&gt;.254/16

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;THE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;blah&lt;span class="w"&gt; &lt;/span&gt;envtest
somefile&lt;span class="w"&gt; &lt;/span&gt;contents
-----
&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6a0f8c183a18
&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/
&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nv"&gt;THE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;blah
&lt;span class="nv"&gt;no_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;*.local,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;169&lt;/span&gt;.254/16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="version-tag-and-latest-tag"&gt;Version Tag and Latest Tag&lt;a class="headerlink" href="#version-tag-and-latest-tag" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So that all worked fine for creating and publishing a versioned docker image locally.  But I really want that image tagged with the semantic version and also the &lt;strong&gt;latest&lt;/strong&gt; tag.  The transmode plugin currently does not support multiple tags for the produced docker image.  I'm not the only one who &lt;a href="https://github.com/Transmode/gradle-docker/issues/98"&gt;wants this feature&lt;/a&gt;.  I took a look at the source code, and it wouldn't be a minor change.  At this point, I'm only publishing locally, so given the choice between version tag and latest tag, I'm going to go for latest for now.  This is a simple matter of &lt;a href="https://github.com/ryanmckaytx/java-docker-example/commit/4f62d9aa5a5a3cf51160dd0cb230c19896bc6da2?diff=unified"&gt;adding&lt;/a&gt; tagVersion = 'latest' to the buildDocker task.  &lt;/p&gt;
&lt;p&gt;I tagged the &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.3.2"&gt;code repo at v0.3.2&lt;/a&gt; at this point.  &lt;/p&gt;
&lt;p&gt;I'm going to move on to evaluating the &lt;a href="https://github.com/bmuschko/gradle-docker-plugin"&gt;bmuschko&lt;/a&gt; and Netflix &lt;a href="https://github.com/nebula-plugins/nebula-docker-plugin"&gt;Nebula Docker&lt;/a&gt; Gradle plugins next.&lt;/p&gt;</content><category term="misc"></category><category term="gradle"></category><category term="docker"></category></entry><entry><title>Docker Java Example Part 2: Spring Web MVC Testing</title><link href="https://ryanmckaytx.github.io/docker-java-example-part-2-spring-web.html" rel="alternate"></link><published>2017-08-23T12:47:00-05:00</published><updated>2017-08-23T12:47:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-08-23:/docker-java-example-part-2-spring-web.html</id><summary type="html">&lt;p&gt;The next step was to add some tests. The tests that came with the demo controller used a Spring feature I was not familiar with, MockMvc. The Spring Guide "&lt;a href="https://spring.io/guides/gs/testing-web/"&gt;Testing the Web Layer&lt;/a&gt;" provides a good discussion of various levels of testing, focusing on how much of the Spring context to load.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;div class="toctitle"&gt;Docker Java Example Series&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-1-initializing.html"&gt;Initializing a new Spring Boot Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-2-spring-web.html"&gt;Spring Web MVC Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part3-transmode-gradle-plugin.html"&gt;Transmode Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html"&gt;Bmuschko and Nebula Gradle Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-5-kubernetes.html"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;The next step was to add some tests. The tests that came with the demo controller used a Spring feature I was not familiar with, MockMvc. The Spring Guide "&lt;a href="https://spring.io/guides/gs/testing-web/"&gt;Testing the Web Layer&lt;/a&gt;" provides a good discussion of various levels of testing, focusing on how much of the Spring context to load. There are 3 main levels: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;start the full Tomcat server with full Spring context,&lt;/li&gt;
&lt;li&gt;full Spring context without server, and &lt;/li&gt;
&lt;li&gt;narrower MVC-focused context without server. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I wanted to compare all three, plus add in variation in testing framework and assertion framework. &lt;/p&gt;
&lt;p&gt;Specifically I wanted to add &lt;a href="http://spockframework.org/spock/docs/1.0/spock_primer.html"&gt;Spock&lt;/a&gt; with groovy power assert.  The aspects I wanted to compare were: test speed, readability of test code, readability of test output.  I intentionally made one of the tests fail in each approach to compare output.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Spring logo" src="https://ryanmckaytx.github.io/images/spring-300x293.png" title="Spring"&gt;&lt;/p&gt;
&lt;h2 id="spock-with-full-tomcat-server"&gt;Spock with Full Tomcat Server&lt;a class="headerlink" href="#spock-with-full-tomcat-server" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the approach I am most familiar with.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerSpec.groovy"&gt;GreetingControllerSpec.groovy&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="timing"&gt;Timing&lt;a class="headerlink" href="#timing" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I ran and timed the test in isolation with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./gradlew&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*GreetingControllerSpec&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--profile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Total 'test' task time (reported by gradle profile output): 13.734s&lt;br&gt;
Total test run time (reported by junit test output): 12.690s&lt;br&gt;
Time to start GreetingControllerSpec (load full context and start tomcat): 12.157s&lt;br&gt;
So, not fast. Maybe one of the other approaches can do better.&lt;/p&gt;
&lt;h3 id="test-code-readability"&gt;Test Code Readability&lt;a class="headerlink" href="#test-code-readability" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;&amp;quot;no Param greeting should return default message&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;when:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Greeting&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responseGreeting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restTemplate&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getForEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://localhost:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/greeting&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Greeting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;then:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;responseGreeting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;responseGreeting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blah&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I really like Spock. I like the plain English test names. I like the separate sections for given, when, then, etc. I think it reads well and makes it obvious what is under test.&lt;/p&gt;
&lt;h3 id="test-output-readability"&gt;Test Output Readability&lt;a class="headerlink" href="#test-output-readability" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a test fails, you want to see why, right?  In this aspect, &lt;a href="http://groovy-lang.org/testing.html#_power_assertions"&gt;groovy power assertions&lt;/a&gt; are simply unparalleled.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Condition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;satisfied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;responseGreeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;blah&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;differences&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;He&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ah&lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&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;Greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="n"&gt;application/json;charset=UTF-8&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="n"&gt;chunked&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="n"&gt;Tue, 22 Aug 2017 22:06:33 GMT&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ryanmckay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GreetingControllerSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GreetingControllerSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;groovy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the nice output for responseGreeting itself comes from ResponseEntity.toString(), and from Greeting.toString(), which is provided by &lt;a href="https://projectlombok.org/"&gt;Lombok&lt;/a&gt;.  &lt;/p&gt;
&lt;h2 id="spock-with-mockmvc"&gt;Spock with MockMvc&lt;a class="headerlink" href="#spock-with-mockmvc" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By adding @AutoConfigureMockMvc to your test class, you can inject a MockMvc instance, which facilitates making calls directly to Springs HTTP request handling layer.  This allows you to skip starting up a Tomcat server, so should save some time and/or memory.  On the other hand, you are testing less of the round trip, so the time savings would need to be significant to justify this approach.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerMockMvcSpec.groovy"&gt;GreetingControllerMockMvcSpec.groovy&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="timing_1"&gt;Timing&lt;a class="headerlink" href="#timing_1" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This approach was about 500ms faster than with tomcat.  Not significant enough to justify for me, considering the overall time scale.  &lt;/p&gt;
&lt;p&gt;Total 'test' task time (reported by gradle profile output): 13.263s&lt;br&gt;
Total test run time (reported by junit test output): 12.281s &lt;br&gt;
Time to start GreetingControllerSpec (load full context, no tomcat): 11.804s&lt;/p&gt;
&lt;h3 id="test-code-readability_1"&gt;Test Code Readability&lt;a class="headerlink" href="#test-code-readability_1" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;no Param greeting should return default message&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;when&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;resultActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mockMvc&lt;/span&gt;.&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/greeting&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;))&lt;/span&gt;.&lt;span class="nv"&gt;andDo&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;())&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;resultActions&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;.&lt;span class="nv"&gt;andExpect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;.&lt;span class="nv"&gt;isOk&lt;/span&gt;&lt;span class="ss"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This reads reasonably well.  Capturing the resultActions in the &lt;em&gt;when&lt;/em&gt; block to use later in the &lt;em&gt;then&lt;/em&gt; block is a little awkward, but not too bad.  Being able to express arbitrary JSON path expectations is convenient.  I didn't see an obvious way to get a ResponseEntity as was done in the full Tomcat example.&lt;/p&gt;
&lt;h3 id="test-output-readability_1"&gt;Test Output Readability&lt;a class="headerlink" href="#test-output-readability_1" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;resultActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;isOk&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="n"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&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="s"&gt;&amp;quot;blah&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JsonPathResultMatchers$2&lt;/span&gt;&lt;span class="mf"&gt;@1f&lt;/span&gt;&lt;span class="mi"&gt;977413&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JsonPathResultMatchers&lt;/span&gt;&lt;span class="mi"&gt;@6&lt;/span&gt;&lt;span class="n"&gt;cd50e89&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssertionError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$.content&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;blah&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="o"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="o"&gt;!&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusResultMatchers$10&lt;/span&gt;&lt;span class="mi"&gt;@660&lt;/span&gt;&lt;span class="n"&gt;dd332&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusResultMatchers&lt;/span&gt;&lt;span class="mf"&gt;@251379e8&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MockMvc$1&lt;/span&gt;&lt;span class="mi"&gt;@68837646&lt;/span&gt;
&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servlet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MockMvc$1&lt;/span&gt;&lt;span class="mi"&gt;@68837646&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ryanmckay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GreetingControllerMockMvcSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GreetingControllerMockMvcSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Caused&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssertionError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$.content&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;blah&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="o"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="o"&gt;!&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This test output does not read well at all. Spock and the Spring MockMvc library are both tripping over each other trying to provide verbose output.  I think you need choose either Spock or MockMvc, but not both.  &lt;/p&gt;
&lt;h2 id="junit-with-webmvctest-and-mockmvc"&gt;JUnit with WebMvcTest and MockMvc&lt;a class="headerlink" href="#junit-with-webmvctest-and-mockmvc" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This configuration is on the far other end of the spectrum from full service Spock.  With @WebMvcTest, not only does it not start a Tomcat server, it doesn't even load a full context.  In the current state of the project this doesn't make much of a difference because the GreetingController has no injected dependencies.  If it did, I would have to mock those out.  Again, because of the differences from "real" configuration, time savings would need to be significant.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerTests.java"&gt;GreetingControllerTests.java&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="timing_2"&gt;Timing&lt;a class="headerlink" href="#timing_2" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This approach was also about 500ms faster overall than full context with Tomcat.  &lt;/p&gt;
&lt;p&gt;Total 'test' task time (reported by gradle profile output): 13.275s&lt;br&gt;
Total test run time (reported by junit test output): 0.269s &lt;br&gt;
Time to start GreetingControllerSpec (load narrow context, no tomcat): 11.88s&lt;/p&gt;
&lt;h3 id="test-code-readability_2"&gt;Test Code Readability&lt;a class="headerlink" href="#test-code-readability_2" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;noParamGreetingShouldReturnDefaultMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockMvc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/greeting&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;andDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isOk&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="na"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$.content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blah&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the least readable for me.  Again, I like separating the call under test from the assertions.  &lt;/p&gt;
&lt;h3 id="test-output-readability_2"&gt;Test Output Readability&lt;a class="headerlink" href="#test-output-readability_2" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The failure message for MockMvc-based assertion failures isn't as informative as Spock in this case.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$.content&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;blah&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;World&lt;/span&gt;&lt;span class="p"&gt;!&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; type=&amp;quot;&lt;/span&gt;&lt;span class="nx"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AssertionError&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;gt;java.lang.AssertionError: JSON path &amp;quot;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;blah&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;World&lt;/span&gt;&lt;span class="p"&gt;!&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Because the test called .andDo(print()), some additional information is available in the standard out of the test, including the full response status code and body.  &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm as convinced as ever that Spock is the premier Java testing framework.  I'm reserving judgment on the Spring annotations that let you avoid starting a Tomcat server or load the full context.  If the project gets more complicated, those could potentially provide a nice speedup.  &lt;/p&gt;
&lt;p&gt;I tagged the &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.2"&gt;code repo at v0.2&lt;/a&gt; at this point.&lt;/p&gt;</content><category term="misc"></category><category term="testing"></category><category term="spring"></category></entry><entry><title>Docker Java Example Part 1: Initializing a new Spring Boot Project</title><link href="https://ryanmckaytx.github.io/docker-java-example-part-1-initializing.html" rel="alternate"></link><published>2017-07-23T21:12:00-05:00</published><updated>2017-07-23T21:12:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-07-23:/docker-java-example-part-1-initializing.html</id><summary type="html">&lt;p&gt;I've been wanting to learn more about Docker for a while.  I'm almost done with this udemy course &lt;a href="https://www.udemy.com/docker-for-java-developers/learn/v4/overview"&gt;Docker for Java Developers&lt;/a&gt;.  It's a good course, and the concepts are straightforward.  To help me commit it to memory, I wanted to do my own project to apply what I'm learning.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;div class="toctitle"&gt;Docker Java Example Series&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-1-initializing.html"&gt;Initializing a new Spring Boot Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-2-spring-web.html"&gt;Spring Web MVC Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part3-transmode-gradle-plugin.html"&gt;Transmode Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-4-bmuschko-nebula-gradle-docker-plugins.html"&gt;Bmuschko and Nebula Gradle Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker-java-example-part-5-kubernetes.html"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;I've been wanting to learn more about Docker for a while.  I'm almost done with this udemy course &lt;a href="https://www.udemy.com/docker-for-java-developers/learn/v4/overview"&gt;Docker for Java Developers&lt;/a&gt;.  Its a good course, and the concepts are straightforward.  To help me commit it to memory, I wanted to do my own project to apply what I'm learning.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanmckaytx/java-docker-example"&gt;https://github.com/ryanmckaytx/java-docker-example&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;In this part, I'm just going to initialize a new project.  &lt;a href="http://againstentropy.blogspot.com/2017/08/docker-java-example-part-2-spring-web.html"&gt;Part 2&lt;/a&gt; covers Spring Web MVC testing.  &lt;/p&gt;
&lt;h2 id="set-up-your-dev-machine"&gt;Set up your dev machine&lt;a class="headerlink" href="#set-up-your-dev-machine" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every once in a while, like when you switch jobs and get a new laptop, you need to set up a machine for java development.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="SDKMan logo" src="https://ryanmckaytx.github.io/images/sdk-man-logo.png" title="SDKMan"&gt;&lt;/p&gt;
&lt;p&gt;For this, I like to use &lt;a href="http://sdkman.io/"&gt;sdkman&lt;/a&gt;.  It helps install the tools you need to do java development.  It also helps switch between multiple versions of those tools.  I've installed java, groovy, gradle, and maven.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sdk&lt;span class="w"&gt; &lt;/span&gt;current

Using:

gradle:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.0
groovy:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.4.11
java:&lt;span class="w"&gt; &lt;/span&gt;8u131-zulu
maven:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.5.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="create-a-new-project"&gt;Create a new project&lt;a class="headerlink" href="#create-a-new-project" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few good ways to do this.  The typical way I do this is to copy another project.  Within an organization, or at least within a team, there is typically some amount of infrastructure and institutional knowledge built into existing projects that you want in a new project.  But for this project, I wanted to practice starting completely from scratch.  There are a couple of good options.  &lt;/p&gt;
&lt;h3 id="gradle-init"&gt;Gradle init&lt;a class="headerlink" href="#gradle-init" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I like gradle as a build tool, and gradle has a built in &lt;a href="https://docs.gradle.org/current/userguide/build_init_plugin.html"&gt;project initializer&lt;/a&gt;. 
It supports a few project archtypes, including pom (converting a maven project to gradle), java library, and java application.  It even explicitly supports spock testing framework.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;gradle&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;java-application&lt;span class="w"&gt; &lt;/span&gt;--test-framework&lt;span class="w"&gt; &lt;/span&gt;spock

BUILD&lt;span class="w"&gt; &lt;/span&gt;SUCCESSFUL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0s
&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;actionable&lt;span class="w"&gt; &lt;/span&gt;tasks:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;executed
$&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;.
&lt;span class="p"&gt;|&lt;/span&gt;____build.gradle
&lt;span class="p"&gt;|&lt;/span&gt;____gradle
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____wrapper
&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="p"&gt;|&lt;/span&gt;____gradle-wrapper.jar
&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="p"&gt;|&lt;/span&gt;____gradle-wrapper.properties
&lt;span class="p"&gt;|&lt;/span&gt;____gradlew
&lt;span class="p"&gt;|&lt;/span&gt;____gradlew.bat
&lt;span class="p"&gt;|&lt;/span&gt;____settings.gradle
&lt;span class="p"&gt;|&lt;/span&gt;____src
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____main
&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="p"&gt;|&lt;/span&gt;____java
&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="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____App.java
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____test
&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="p"&gt;|&lt;/span&gt;____groovy
&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="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____AppTest.groovy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see it also generates a demo App and AppTest.&lt;/p&gt;
&lt;h3 id="spring-boot-initializr"&gt;Spring Boot Initializr&lt;a class="headerlink" href="#spring-boot-initializr" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For Spring Boot apps, Spring provides the &lt;a href="https://start.spring.io/"&gt;Spring Boot Initializr&lt;/a&gt;.  This lets you choose from a curated  (but extensive) set of options and dependencies, and then generates a project in a zip file for download.  Similarly to gradle init, it includes a default basic app and test.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Spring Boot Initializr logo" src="https://ryanmckaytx.github.io/images/SpringBootInitializr.png" title="Spring Boot Initializr"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;unzip&lt;span class="w"&gt; &lt;/span&gt;demo.zip
Archive:&lt;span class="w"&gt;  &lt;/span&gt;demo.zip
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradlew
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradle/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradle/wrapper/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/java/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/java/net/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/java/net/ryanmckay/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/java/net/ryanmckay/demo/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/resources/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/resources/static/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/resources/templates/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/java/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/java/net/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/java/net/ryanmckay/
&lt;span class="w"&gt;   &lt;/span&gt;creating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/java/net/ryanmckay/demo/
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/.gitignore
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/build.gradle
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradle/wrapper/gradle-wrapper.jar
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradle/wrapper/gradle-wrapper.properties
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/gradlew.bat
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/java/net/ryanmckay/demo/DemoApplication.java
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/main/resources/application.properties
&lt;span class="w"&gt;  &lt;/span&gt;inflating:&lt;span class="w"&gt; &lt;/span&gt;demo/src/test/java/net/ryanmckay/demo/DemoApplicationTests.java
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pretty much the only thing I don't like here is that the test isn't spock and there doesn't seem to be a way to choose it. Not a big deal, its easy to change afterward.  I went with initializr for this project.&lt;/p&gt;
&lt;h3 id="jhipster"&gt;JHipster&lt;a class="headerlink" href="#jhipster" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://jhipster.github.io/"&gt;JHipster&lt;/a&gt; is an opinionated full-stack project generator for Spring Boot + Angular apps.  It has a lot of features that I want to explore later, so for now I stuck with Spring Boot Initializr.  &lt;/p&gt;
&lt;h2 id="add-a-rest-controller"&gt;Add a Rest Controller&lt;a class="headerlink" href="#add-a-rest-controller" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I could have gone straight to CI at this point, but I wanted to do a little extra work that I wish the Initializr could have done for me.  I copied in a basic hello world spring rest controller (and its tests) from the &lt;a href="http://spring.io/guides/gs/rest-service/"&gt;Spring Restful Web Service Guide&lt;/a&gt;.  The repo is now at this point &lt;a href="https://github.com/ryanmckaytx/java-docker-example/tree/v0.1"&gt;https://github.com/ryanmckaytx/java-docker-example/tree/v0.1&lt;/a&gt;&lt;/p&gt;</content><category term="misc"></category><category term="gradle"></category><category term="spring"></category></entry><entry><title>Interview Prep/Tips</title><link href="https://ryanmckaytx.github.io/interview-prep-tips.html" rel="alternate"></link><published>2017-06-24T18:54:00-05:00</published><updated>2017-06-24T18:54:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-06-24:/interview-prep-tips.html</id><summary type="html">&lt;p&gt;I recently interviewed for a new job, which led me to review some notes I made for myself after some interviews a couple years ago.  I thought I would share them in case someone else might find something useful here.  The point of preparing is not to pretend to be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently interviewed for a new job, which led me to review some notes I made for myself after some interviews a couple years ago.  I thought I would share them in case someone else might find something useful here.  The point of preparing is not to pretend to be anything you aren't or to know anything you don't, the point is just to have everything you do know on the tip of your tongue.  You only have probably an hour per panel, so you need to be on point.  &lt;/p&gt;
&lt;p&gt;Make a list of professional things that are important to you.  Out of those, pick the most important and write a few sentences.  Those are kind of your mission statement.  I put those sentences right at the top of my resume.  Here is my list:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Working with smart, motivated people&lt;/li&gt;
&lt;li&gt;Agile&lt;/li&gt;
&lt;li&gt;Mentoring&lt;/li&gt;
&lt;li&gt;Being Mentored&lt;/li&gt;
&lt;li&gt;DevOps&lt;/li&gt;
&lt;li&gt;Physical fitness&lt;/li&gt;
&lt;li&gt;Feeling like I'm contributing to company's success&lt;/li&gt;
&lt;li&gt;Company mission&lt;/li&gt;
&lt;li&gt;Competence of other departments&lt;/li&gt;
&lt;li&gt;Trust in leadership&lt;/li&gt;
&lt;li&gt;Trust from leadership&lt;/li&gt;
&lt;li&gt;Advanced software architecture&lt;/li&gt;
&lt;li&gt;Advanced technology&lt;/li&gt;
&lt;li&gt;Work/Life balance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make a list of the major lessons learned during your time at your current job.  These can include accomplishments, things you wish you had done better, or just experience gained.  &lt;/p&gt;
&lt;p&gt;Make a list of what areas you want to learn more/grow more in.  These should be things that really excite you.  It can be stuff you already do in your current job or not.  Its helpful if you've shown some initiative and done some learning on your own outside of work.&lt;/p&gt;
&lt;p&gt;Make a list of recent videos watched, books read, conferences attended, and one or two sentences about each.  Try to tie the things you learned in these back to the items in the other lists. You want to show that you are passionate about your craft and always learning and improving.  &lt;/p&gt;
&lt;p&gt;Learn about the prospective company, their products, their market, their competition. Make a list of questions you have about the prospective company.  Some of these should tie back to what you are passionate about.  For me it was a lot of questions about how they do agile.  What the dev teams look like.  How they interact with other departments like product and sys ops.  Questions show you are interested, passionate, and evens out the power structure of the interview a bit, which makes everybody feel more comfortable.  Yes, they are interviewing you, but you are also interviewing them.&lt;/p&gt;
&lt;p&gt;For design problems, stay calm and focus on fundamentals.  Treat the interviewer as a subject matter expert/product owner.  Don’t be afraid to ask questions!  Your job is to:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Capture the &lt;a href="https://martinfowler.com/bliki/UbiquitousLanguage.html"&gt;ubiquitous language&lt;/a&gt; of the domain.  Make sure you understand what the pieces are and what they do. &lt;/li&gt;
&lt;li&gt;Capture the functional requirements of the application.  I like to focus on user stories/use cases.  Just solve one at a time, and evolve the design to handle additional ones.&lt;/li&gt;
&lt;li&gt;Nouns you hear are good candidates for objects/resources.  Verbs are good candidates for methods.&lt;/li&gt;
&lt;li&gt;Keep it lean.  Start simple and try to deliver a minimal top to bottom slice that does something.  Then move on to more complicated scenarios iteratively.&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"></category></entry><entry><title>Stop Over-Engineering</title><link href="https://ryanmckaytx.github.io/stop-over-engineering.html" rel="alternate"></link><published>2017-05-24T09:09:00-05:00</published><updated>2017-05-24T09:09:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2017-05-24:/stop-over-engineering.html</id><summary type="html">&lt;p&gt;I just watched &lt;a href="https://www.youtube.com/watch?v=GRr4xeMn1uU"&gt;Greg Young's Build Stuff 2016 Keynote - Stop Over-Engineering&lt;/a&gt;, and had a lot of good takeaways.  &lt;/p&gt;
&lt;p&gt;Your software is only part of an overall business process system.  You don't have to solve every problem using software.  Definitely not early on, and typically not ever.  Why wouldn't you try …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I just watched &lt;a href="https://www.youtube.com/watch?v=GRr4xeMn1uU"&gt;Greg Young's Build Stuff 2016 Keynote - Stop Over-Engineering&lt;/a&gt;, and had a lot of good takeaways.  &lt;/p&gt;
&lt;p&gt;Your software is only part of an overall business process system.  You don't have to solve every problem using software.  Definitely not early on, and typically not ever.  Why wouldn't you try to solve all the problems?  Because the cost benefit tradeoff doesn't make sense.  Many tasks are less expensive for humans to do than to try to automate.  Instead of trying to handle every edge case, regardless of how infrequently it might be encountered, focus on handling the happy path, and detecting when the user has gone off the happy path.  When that happens, hand it off to humans.  &lt;/p&gt;
&lt;p&gt;How do you know what is the happy path?  He used the analogy, "Stop watering the weeds in your life and start watering the flowers".  How do you know where the flowers are? &lt;strong&gt;Data&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;This is where a distinction was made between brown-field projects and green-field projects.  Brown-field projects have the advantage of usage data.  You can see which features are used the most and how they are used.  So it is easer to make data-driven decisions about where to invest effort.  &lt;/p&gt;
&lt;p&gt;He gave an example of an invoicing app.  If you try to capture all the requirements for an invoicing system, you will be in endless meetings.  This struck a chord with me, because in a past job, I worked on a brownfield project in the consumer financial sector, and I had exactly this same experience.  In Greg's case, after 2 weeks of meetings, they decided to stop capturing exhaustive requirements, and instead, look at the past year's worth of actual invoices.  Then the domain experts were simply asked to classify the invoices wrt whether they were on the happy path or not, and how to detect getting outside the happy path.  In one day, they were able to implement a solution that solved 60-70% of the previous year's invoices.  Then they looked at the next most common case (15%) and solved that the next day.  And the next (6%).  After 2 weeks they got to 99%, and then they stopped.  They had reached the point of diminishing returns on investment.  That project had been budgeted to take 9 months.  &lt;strong&gt;Don't spend 4 days of modeling to automate a task that takes a human 5 minutes of work once a year!&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;The problem with green-field projects is that there is no usage data.  He asks the question, how many of us have built features that have never been used?  How many of us have build whole products that have never been used?  So how do we know where the flowers are?  We need to get data.  How do we get data?  Greg described two complementary approaches: Throw sh*t at the wall (and see what sticks), and human concierge service.  The first approach is to build small, unpolished pieces of functionality and see what people actually like.  Taken to the extreme, you get the &lt;a href="https://www.industriallogic.com/blog/fast-frugal-learning-with-a-feature-fake/"&gt;Feature Fake&lt;/a&gt;, where you make it look like you have added a new feature to your application, and see who shows interest in it.  &lt;/p&gt;
&lt;p&gt;The human concierge service actually has no automation at all.  Humans do all the work for a while, so that you can gather usage data, and find the happy path to automate first.  Human concierge is one of several &lt;a href="http://www.movestheneedle.com/blog/enterprise-lean-startup-experiment-examples/"&gt;lean experimentation techniques&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Some other bullet points:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most things in software aren't worth building&lt;/li&gt;
&lt;li&gt;Cost Benefit Analyze everything!  Where is your break even point?  If you can't figure it out - stop.  And figure it out.&lt;/li&gt;
&lt;li&gt;Every developer should hire another developer to build something, to see the CBA from the other side.&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"></category><category term="Readings+Videos"></category></entry><entry><title>Agile Peer Review</title><link href="https://ryanmckaytx.github.io/agile-peer-review.html" rel="alternate"></link><published>2015-08-01T06:40:00-05:00</published><updated>2015-08-01T06:40:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2015-08-01:/agile-peer-review.html</id><summary type="html">&lt;p&gt;What follows is primarily the result of nearly 2 years of intentional refinement by an experienced, high-performing agile team. Dev team members, architects, product owner, and even members of other dev teams participated and helped shape our thoughts on review. I also incorporated ideas from several other experienced peers. But …&lt;/p&gt;</summary><content type="html">&lt;p&gt;What follows is primarily the result of nearly 2 years of intentional refinement by an experienced, high-performing agile team. Dev team members, architects, product owner, and even members of other dev teams participated and helped shape our thoughts on review. I also incorporated ideas from several other experienced peers. But like anything agile, it's a work in progress.  &lt;/p&gt;
&lt;p&gt;Don't let preconceptions about the term "review" color your reading - most of the reviews called for are only 5-15 minutes long.&lt;/p&gt;
&lt;h1 id="overview"&gt;Overview&lt;a class="headerlink" href="#overview" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Functional defects and design problems get exponentially more expensive the longer they are allowed to exist. The earlier those problems can be found and fixed, the better. Peer reviews provide an inexpensive, effective method for doing that.  &lt;/p&gt;
&lt;p&gt;There are several major work products in the lifecycle of a user story that warrant review (these are discussed in detail below):&lt;br&gt;
* Domain Model - The team’s shared, fundamental, documented understanding of the domain.
* Demo script - How the dev team will demonstrate to stakeholders that the story is functionally complete.
* Design - Focused on the API and its test cases, but also covering important implementation details. 
* Pull Request - A single coherent change to the shared codebase, in service of a story. 
* Code Complete - The full set of changes to the codebase that were made to complete the story.&lt;/p&gt;
&lt;h1 id="benefits"&gt;Benefits&lt;a class="headerlink" href="#benefits" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h3 id="these-reviews-deliver-tangible-value-to-the-business"&gt;These reviews deliver tangible value to the business&lt;a class="headerlink" href="#these-reviews-deliver-tangible-value-to-the-business" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Higher-quality code &lt;/li&gt;
&lt;li&gt;Higher, more consistent velocity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="and-additional-value-to-the-dev-team"&gt;And additional value to the dev team&lt;a class="headerlink" href="#and-additional-value-to-the-dev-team" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pride in work &lt;/li&gt;
&lt;li&gt;Self-improvement&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="as-a-result-of-the-direct-effects-of-the-reviews"&gt;As a result of the direct effects of the reviews&lt;a class="headerlink" href="#as-a-result-of-the-direct-effects-of-the-reviews" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Getting a better product, sooner &lt;/li&gt;
&lt;li&gt;Bugs identified, eliminated earlier in the process &lt;/li&gt;
&lt;li&gt;More maintainable code&lt;/li&gt;
&lt;li&gt;Better tested&lt;/li&gt;
&lt;li&gt;More adherent to team/organizational standards&lt;/li&gt;
&lt;li&gt;Knowledge sharing&lt;/li&gt;
&lt;li&gt;Team members sharing the same frame of mind&lt;/li&gt;
&lt;li&gt;Better understanding of the domain&lt;/li&gt;
&lt;li&gt;Better understanding of how system components interact&lt;/li&gt;
&lt;li&gt;Improving our craftsmanship every day by learning from each others strengths&lt;/li&gt;
&lt;li&gt;Better understanding of our tools and technical stack&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="what-to-review"&gt;What to Review&lt;a class="headerlink" href="#what-to-review" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="domain-model"&gt;Domain Model&lt;a class="headerlink" href="#domain-model" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The fundamental, documented understanding of the domain shared among the whole team including the Product Owner. A critical part of this model is the Ubiquitous Language, which unambiguously expresses domain concepts, enabling effective communication. Any change in that understanding or expression needs to be reviewed by the rest of the team, to keep everyone on the same page, and to consider potential impact to the software design. Not every story changes the Domain Model.&lt;/p&gt;
&lt;h2 id="demo-script"&gt;Demo Script&lt;a class="headerlink" href="#demo-script" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;How the dev team will demonstrate to stakeholders that the story is functionally complete. This would involve the dev team and Product Owner. Depending on team/org structure, more or less of this might be provided to the dev team by PO, or might even be developed collaboratively, in which case a separate review would be unnecessary. &lt;/p&gt;
&lt;p&gt;There is not a lot of detail here, just a handful of user-facing scenarios. If it is more than a handful, the story is probably too big.. It shouldn't take much time at all to produce or review. If it does take a long time, again it's probably too big, or there is a lack of understanding that needs to be addressed before proceeding. For example:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an event&lt;ul&gt;
&lt;li&gt;Go to the Event list page&lt;/li&gt;
&lt;li&gt;Click "New Event" button&lt;/li&gt;
&lt;li&gt;Fill in information in Event form, click save&lt;/li&gt;
&lt;li&gt;Should show Event overview page with saved info&lt;/li&gt;
&lt;li&gt;Go back to Event list page, new event should be there&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Register for an Event&lt;ul&gt;
&lt;li&gt;Registrant Role&lt;ul&gt;
&lt;li&gt;Go to Event Registration page&lt;/li&gt;
&lt;li&gt;Fill in form, submit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Admin Role&lt;ul&gt;
&lt;li&gt;Go to Event overview page, see new Registration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="design"&gt;Design&lt;a class="headerlink" href="#design" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a microservice, this would focus on the API and its test cases, but would also cover important implementation details. Generally should be developed collaboratively with the major client(s) of the API, e.g. UI. In which case, the review with the rest of the team shouldn't take long.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resource endpoints&lt;/li&gt;
&lt;li&gt;Methods on those endpoints&lt;/li&gt;
&lt;li&gt;API objects&lt;/li&gt;
&lt;li&gt;API test cases&lt;/li&gt;
&lt;li&gt;API versioning&lt;/li&gt;
&lt;li&gt;New runtime dependencies, e.g. now we're going to have to use the Org service&lt;/li&gt;
&lt;li&gt;Major changes to infrastructure, e.g. using a new database, new message queue&lt;/li&gt;
&lt;li&gt;Data migration&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pair-programming"&gt;Pair Programming&lt;a class="headerlink" href="#pair-programming" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is effectively continuous code review between two team members while the code is being written. I wrote up most of my thoughts on this practice &lt;a href="http://againstentropy.blogspot.com/2014/06/pair-programming-benefits.html"&gt;here&lt;/a&gt;. Because of the normalizing and quality control forces exerted here, the pull request and code complete reviews go faster.&lt;/p&gt;
&lt;h2 id="pull-request"&gt;Pull Request&lt;a class="headerlink" href="#pull-request" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Code gets merged into the shared repository by issueing a pull request. This should occur frequently, typically multiple times per day per developer. It should be coherent and correct, but not necessarily complete. Each pull request gets reviewed by one or more other team members. In my experience, the types of things caught here are team standards violations, code/test readability (e.g. method and variable names), and code factoring.&lt;/p&gt;
&lt;h2 id="code-complete"&gt;Code Complete&lt;a class="headerlink" href="#code-complete" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When the dev(s) working on a story consider it code complete, the code and its tests should be reviewed by the rest of the dev team, typically plus an architect.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The person(s) who did the coding walk everyone else through it&lt;ul&gt;
&lt;li&gt;Generally start with brief review of the story&lt;/li&gt;
&lt;li&gt;Then walk through the important parts of the code&lt;ul&gt;
&lt;li&gt;And the tests!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;While those persons are presenting, someone else is taking action item notes&lt;ul&gt;
&lt;li&gt;Somewhere public, or just email out after&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The story is not considered dev-complete until those action items are addressed.&lt;ul&gt;
&lt;li&gt;Creating a tech debt story to do it later is a valid option but should not be abused &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This is at a level similar to Design review, focusing on API classes and tests, and any deviations from the original design.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="how-to-review"&gt;How to Review&lt;a class="headerlink" href="#how-to-review" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="when-to-review"&gt;When to review?&lt;a class="headerlink" href="#when-to-review" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each work product should be reviewed as early as feasible, because course correction is cheaper the earlier you do it. Also, the less time elapses between creation and review, the more of the context the creator(s) will still have loaded. Finally, by spreading the reviews out over the lifetime of the story (as opposed to reviewing everything at the end), you avoid the tendency to get mentally overwhelmed by the volume of work to be reviewed and just rubber-stamp it. &lt;/p&gt;
&lt;p&gt;On my team, we had one-week sprints, Monday to Friday. We took Monday morning to commit to a set of stories for the sprint, produce and review demo plans for all the committed stories, and produce and review design and test plan for the first one or two stories we were starting work on. All other reviews were performed as needed at the end of each day.&lt;/p&gt;
&lt;h2 id="why-not-just-create-together-instead-of-reviewing-later"&gt;Why not just create together instead of reviewing later?&lt;a class="headerlink" href="#why-not-just-create-together-instead-of-reviewing-later" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is a tradeoff to be made between direct collaboration and ex post facto review. In my experience, you typically want to keep creative activities for a given story limited to 2-3 people, 4 at the most. Otherwise the cost/benefit starts to deteriorate.&lt;/p&gt;
&lt;h2 id="how-much-time-to-invest"&gt;How much time to invest?&lt;a class="headerlink" href="#how-much-time-to-invest" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a team that has been together a while, the amount of time to target for each review is ~30 minutes for code complete review, and 5-15 minutes for the others. Expect more review time early on in a team's formation. However, team norming will drive the time down significantly, and performing these reviews accelerates team norming. You don't keep finding the same things in review, because you incorporate the feedback and get better.&lt;/p&gt;
&lt;h2 id="who-does-the-reviewing"&gt;Who does the reviewing?&lt;a class="headerlink" href="#who-does-the-reviewing" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The composition of the reviewing group varies based on what is being reviewed, and larger review groups will tend to use more person-hours, because:&lt;br&gt;
* There are more people spending the time
* More people talk more
* The members of large groups will be less familiar with what is being reviewed&lt;/p&gt;
&lt;h1 id="putting-it-all-together-a-sprint-in-the-life-of-a-story"&gt;Putting it all Together: A Sprint in the Life of a Story&lt;a class="headerlink" href="#putting-it-all-together-a-sprint-in-the-life-of-a-story" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="monday-morning"&gt;Monday morning&lt;a class="headerlink" href="#monday-morning" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dev team meets to commit to stories for sprint, and devise demo script for those stories. Right after that meeting, they meet with Product Owner to review the demo script, adjust as necessary.&lt;/p&gt;
&lt;h2 id="monday-afternoon"&gt;Monday afternoon&lt;a class="headerlink" href="#monday-afternoon" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One pair of developers start on a medium-sized story. They come up with a design together. During the design, they realize that their Domain Model and Ubiquitous Language will need to change. Previously, Constituents could only register for Events. Now a Constituent can be designated as the "Event Organizer". They ask the rest of the team and an architect for a design review, scheduled for 30 minutes later. In the mean time, they check the proposed new terminology with the Product Owner, and add the term to the project's wiki. Then they go ahead and start implementing the design.&lt;/p&gt;
&lt;p&gt;In the design review, an overlooked test case is identified and added, and the format of a timestamp field is changed to suit the UI. Minimal rework in the codebase is required. They also take that opportunity to inform the rest of the dev team and the architect of the new Ubiquitous Language term.&lt;/p&gt;
&lt;p&gt;Monday end of day - The pair issues a pull request. One other developer is still in the office, spends 5 minutes reviewing it. Catches a few issues, which he comments on, but nothing serious, so he merges the pull request.&lt;/p&gt;
&lt;h2 id="tuesday-wednesday"&gt;Tuesday, Wednesday&lt;a class="headerlink" href="#tuesday-wednesday" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Several more pull requests issued. Since the rest of the team is there, they both review both pull requests. The architect notices the pull requests going by and adds a few comments as well. Late Wednesday afternoon, the pair working on the story determines that it is code complete, and schedules a team code review for end of day.&lt;/p&gt;
&lt;h2 id="wednesday-end-of-day"&gt;Wednesday end of day&lt;a class="headerlink" href="#wednesday-end-of-day" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dev team + Architect code review for the story. One of the dev pair refreshes everyone's memory about the story, then walks through the code and tests. The other member of the dev pair is taking notes. A few minor issues are caught, and one potentially serious scalability issue. The team decides to consult historical metrics to see if it will be a problem short- or long-term. Once they determine that it would take about a year of usage at current rates to become a problem, they decide to add monitoring for this story, and add a tech debt story to address the scaling longer-term.&lt;/p&gt;
&lt;h2 id="thursday-morning"&gt;Thursday morning&lt;a class="headerlink" href="#thursday-morning" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dev pair demos the story to the Product Owner according to the demo plan.&lt;/p&gt;
&lt;h1 id="further-reading"&gt;Further Reading&lt;a class="headerlink" href="#further-reading" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.codinghorror.com/code-reviews-just-do-it/"&gt;Code Reviews: Just Do It&lt;/a&gt; - Quotes some great case study numbers on the efficacy of code review&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.fogcreek.com/effective-code-reviews-9-tips-from-a-converted-skeptic/"&gt;Effective Code Reviews: 9 Tips from a Converted Skeptic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Smart Bear: &lt;a href="https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/"&gt;Best Practices for Code Reviews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Atlassian: &lt;a href="http://blogs.atlassian.com/2011/07/creating_optimal_reviews/"&gt;Creating Optimal Reviews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://techbus.safaribooksonline.com/book/project-management/0321125215/communication-and-the-use-of-language/ch02lev1sec1"&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software, Ch. 2, Section 1: Ubiquitous Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.jamesshore.com/Agile-Book/ubiquitous_language.html"&gt;The Art of Agile Development: Ubiquitous Language&lt;/a&gt; - Shorter version of above - definition and benefits of Ubiquitous Language&lt;/li&gt;
&lt;li&gt;&lt;a href="/pair-programming-benefits.html"&gt;Pair Programming Benefits&lt;/a&gt; - My experience with Pair Programming&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.summa-tech.com/blog/2013/05/16/pair-programming-benefits-part-1-the-good"&gt;Pair Programming Benefits&lt;/a&gt; - Another blog on the pluses and minuses of Pair Programming&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"></category></entry><entry><title>More Vagrant Learnings</title><link href="https://ryanmckaytx.github.io/more-vagrant-learnings.html" rel="alternate"></link><published>2015-03-29T19:59:00-05:00</published><updated>2015-03-29T19:59:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2015-03-29:/more-vagrant-learnings.html</id><summary type="html">&lt;p&gt;Last time I wrote about &lt;a href="http://againstentropy.blogspot.com/2015/01/vagrant-for-local-development.html"&gt;how we use Vagrant to manage our local development environment&lt;/a&gt; at work. Since then I've dug into Vagrant a little more deeply and found a few nuggets I wanted to share.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last time I wrote about &lt;a href="http://againstentropy.blogspot.com/2015/01/vagrant-for-local-development.html"&gt;how we use Vagrant to manage our local development environment&lt;/a&gt; at work. Since then I've dug into Vagrant a little more deeply and found a few nuggets I wanted to share.  &lt;/p&gt;
&lt;h2 id="package-your-own-vagrant-box"&gt;Package your own Vagrant Box&lt;a class="headerlink" href="#package-your-own-vagrant-box" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are some great base boxes (images) available for vagrant. It will even install them for you. If you want to tweak the box, you can use one or more of the many available provisioning providers. However, sometimes you might want to just bake the tweak into a new base image, for example, if the tweak is something you want to apply to all your instances, or if it is time consuming. Vagrant makes it very easy to do this.&lt;/p&gt;
&lt;h3 id="initialize-start-vm"&gt;Initialize, start VM&lt;a class="headerlink" href="#initialize-start-vm" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;hashicorp/precise32
$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="install-new-stuff-in-vm"&gt;Install new stuff in VM&lt;a class="headerlink" href="#install-new-stuff-in-vm" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh
vagrant@precise32:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;set editing-mode vi&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;~/.inputrc
vagrant@precise32:~$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update
vagrant@precise32:~$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;vim
vagrant@precise32:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="package-up-vm-image-as-a-new-vagrant-box"&gt;Package up VM image as a new Vagrant box&lt;a class="headerlink" href="#package-up-vm-image-as-a-new-vagrant-box" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Attempting&lt;span class="w"&gt; &lt;/span&gt;graceful&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;VM...
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Clearing&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;previously&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;forwarded&lt;span class="w"&gt; &lt;/span&gt;ports...
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Exporting&lt;span class="w"&gt; &lt;/span&gt;VM...
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Compressing&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;to:&lt;span class="w"&gt; &lt;/span&gt;/Users/ryanmckay/projects/vagrant/package.box

$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l
total&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;801784&lt;/span&gt;
-rw-r--r--&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ryanmckay&lt;span class="w"&gt;  &lt;/span&gt;staff&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;3001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Mar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;:13&lt;span class="w"&gt; &lt;/span&gt;Vagrantfile
-rw-r--r--&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ryanmckay&lt;span class="w"&gt;  &lt;/span&gt;staff&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;410509096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Mar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:32&lt;span class="w"&gt; &lt;/span&gt;package.box

$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;list
hashicorp/precise32&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0.0&lt;span class="o"&gt;)&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ryanmckay/withvim&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;package.box&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;box:&lt;span class="w"&gt; &lt;/span&gt;Adding&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ryanmckay/withvim&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;v0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;provider:&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;box:&lt;span class="w"&gt; &lt;/span&gt;Downloading:&lt;span class="w"&gt; &lt;/span&gt;file:///Users/ryanmckay/projects/vagrant/package.box
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;box:&lt;span class="w"&gt; &lt;/span&gt;Successfully&lt;span class="w"&gt; &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ryanmckay/withvim&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;v0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;virtualbox&amp;#39;&lt;/span&gt;!

$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;list
hashicorp/precise32&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0.0&lt;span class="o"&gt;)&lt;/span&gt;
ryanmckay/withvim&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="provisioning"&gt;Provisioning&lt;a class="headerlink" href="#provisioning" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Vagrant provides a boatload of provisioning providers, e.g. puppet, chef, ansible, and even shell script. Normally configured provisioners are run when bringing up the VM for the first time. However, you can also run the provisioners with vagrant provision. For example, if you have the following:&lt;/p&gt;
&lt;h3 id="provision_countersh-shell-script"&gt;provision_counter.sh shell script&lt;a class="headerlink" href="#provision_countersh-shell-script" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;provisioning&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/etc/provision_count&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/etc/provision_count&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/provision_count
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;provisioned: &lt;/span&gt;&lt;span class="nv"&gt;$counter&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="configured-in-your-vagrantfile"&gt;Configured in your Vagrantfile&lt;a class="headerlink" href="#configured-in-your-vagrantfile" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config.vm.provision &amp;quot;shell&amp;quot;, path: &amp;quot;provision_counter.sh&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="when-you-vagrant-up-provisioners-are-run"&gt;When you vagrant up, provisioners are run&lt;a class="headerlink" href="#when-you-vagrant-up-provisioners-are-run" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up
...
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;provisioner:&lt;span class="w"&gt; &lt;/span&gt;shell...
&lt;span class="w"&gt;    &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Running:&lt;span class="w"&gt; &lt;/span&gt;/var/folders/k5/6mc1_m_n1675572ts12qr7588mtjh5/T/vagrant-shell20150329-86419-qkjn7x.sh
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;stdin:&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tty&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;provisioning&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;provisioned:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="and-you-can-run-provisioners-explicitly"&gt;And you can run provisioners explicitly&lt;a class="headerlink" href="#and-you-can-run-provisioners-explicitly" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;provision&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;provisioner:&lt;span class="w"&gt; &lt;/span&gt;shell...
&lt;span class="w"&gt;    &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Running:&lt;span class="w"&gt; &lt;/span&gt;/var/folders/k5/6mc1_m_n1675572ts12qr7588mtjh5/T/vagrant-shell20150316-31070-d35qp6.sh
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;stdin:&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tty&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;provisioning&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;provisioned:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can even add/modify provisioning lines in the Vagrantfile of a running VM, and when you run 'vagrant provision', the new lines will be used.&lt;/p&gt;
&lt;h2 id="reloading-vagrantfile"&gt;Reloading Vagrantfile&lt;a class="headerlink" href="#reloading-vagrantfile" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Vagrantfile is the main config file for your vagrant VM. It specifies all the stuff that needs to be set up before the OS is running, as well as the hooks for provisioning once it has booted. The Vagrantfile can be reloaded by 'vagrant reload', which is basically 'halt' and then 'up'. It does not re-run provisioners unless you tell it to. Some examples of why you would want to do this are to modify your network setup or your shared folders. For example, if you want to:  &lt;/p&gt;
&lt;h3 id="add-a-network-port-forward-and-a-shared-folder"&gt;Add a network port forward and a shared folder&lt;a class="headerlink" href="#add-a-network-port-forward-and-a-shared-folder" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config.vm.network &amp;quot;forwarded_port&amp;quot;, guest: 80, host: 8080config.vm.synced_folder &amp;quot;src/&amp;quot;, &amp;quot;/srv/website&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then 'vagrant reload' would restart your VM with the new configuration without having to reprovision.&lt;/p&gt;
&lt;h2 id="vagrant-ssh"&gt;Vagrant SSH&lt;a class="headerlink" href="#vagrant-ssh" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;'vagrant ssh' is how you ssh into an interactive session on your VM. You can also use 'vagrant ssh -c command' to run a command in the VM instead of an interactive shell, similar to 'ssh host command'.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hostname&amp;#39;&lt;/span&gt;
precise32
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In order to connect to your VM with regular ssh (or anything that depends on ssh, e.g. scp or rsync over ssh), you need to&lt;/p&gt;
&lt;h3 id="export-vagrant-ssh-config"&gt;Export vagrant ssh-config&lt;a class="headerlink" href="#export-vagrant-ssh-config" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh-config
Host&lt;span class="w"&gt; &lt;/span&gt;default
&lt;span class="w"&gt;  &lt;/span&gt;HostName&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1
&lt;span class="w"&gt;  &lt;/span&gt;User&lt;span class="w"&gt; &lt;/span&gt;vagrant
&lt;span class="w"&gt;  &lt;/span&gt;Port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2222&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;UserKnownHostsFile&lt;span class="w"&gt; &lt;/span&gt;/dev/null
&lt;span class="w"&gt;  &lt;/span&gt;StrictHostKeyChecking&lt;span class="w"&gt; &lt;/span&gt;no
&lt;span class="w"&gt;  &lt;/span&gt;PasswordAuthentication&lt;span class="w"&gt; &lt;/span&gt;no
&lt;span class="w"&gt;  &lt;/span&gt;IdentityFile&lt;span class="w"&gt; &lt;/span&gt;/Users/ryan.mckay/.vagrant.d/insecure_private_key
&lt;span class="w"&gt;  &lt;/span&gt;IdentitiesOnly&lt;span class="w"&gt; &lt;/span&gt;yes
&lt;span class="w"&gt;  &lt;/span&gt;LogLevel&lt;span class="w"&gt; &lt;/span&gt;FATAL

$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh-config&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;ssh-config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="then-provide-ssh-config-to-ssh-and-friends"&gt;Then provide ssh-config to ssh and friends&lt;a class="headerlink" href="#then-provide-ssh-config-to-ssh-and-friends" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;ssh-config&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hostname&amp;#39;&lt;/span&gt;
precise32

$&lt;span class="w"&gt; &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;scp_me
$&lt;span class="w"&gt; &lt;/span&gt;scp&lt;span class="w"&gt; &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;ssh-config&lt;span class="w"&gt; &lt;/span&gt;scp_me&lt;span class="w"&gt; &lt;/span&gt;default:/tmp
scp_me&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0KB/s&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00&lt;span class="w"&gt;    &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;ssh-config&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ls /tmp&amp;#39;&lt;/span&gt;
scp_me

$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rsync_test
$&lt;span class="w"&gt; &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;rsync_test/rsync_me
$&lt;span class="w"&gt; &lt;/span&gt;rsync&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ssh -F ssh-config&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rsync_test&lt;span class="w"&gt; &lt;/span&gt;default:/tmp/
$&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;ssh-config&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;find /tmp&amp;#39;&lt;/span&gt;
/tmp
/tmp/rsync_test
/tmp/rsync_test/rsync_me
/tmp/scp_me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"></category></entry><entry><title>Vagrant for Local Development</title><link href="https://ryanmckaytx.github.io/vagrant-for-local-development.html" rel="alternate"></link><published>2015-01-19T15:46:00-06:00</published><updated>2015-01-19T15:46:00-06:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2015-01-19:/vagrant-for-local-development.html</id><summary type="html">&lt;p&gt;When I was hired at my current company about a year and a half ago, it was immediately obvious that this company took dev-ops much more seriously than any other place I had worked before. In fact, a lot of the dev-ops culture is pushed by the ops team, which …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I was hired at my current company about a year and a half ago, it was immediately obvious that this company took dev-ops much more seriously than any other place I had worked before. In fact, a lot of the dev-ops culture is pushed by the ops team, which is a dramatic, and very welcome, departure from my previous experience. This team follows a lot of the principles espoused in the Continuous Delivery book. In this post, I'm going to focus on the principle of making all pre-prod environments as production-like as possible, and in particular, what has been done in the local development environment.  &lt;/p&gt;
&lt;p&gt;The Ops team uses puppet for server provisioning in all environments. This facilitates the separation of the vast majority of configuration that is the same across environments and servers within an environment from the relatively small proportion that that needs to be different.  &lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our project currently comprises 7 microservices and one CLI app, all based on Spring Boot. Most of the services are RESTful; a couple are message driven. They all expose a management port, primarily for doing health checks and gathering metrics. Deployable artifacts are built by Jenkins and stored in Nexus. Deployment is handled by a standardized shell script which is placed on the target servers by Puppet during provisioning.&lt;/p&gt;
&lt;p&gt;We also use several 3rd-party applications, including MySQL, Mongo, Rabbit MQ, Splunk, and a CLI ETL application. These are also provisioned by Puppet.&lt;/p&gt;
&lt;p&gt;The provisioning and deployment infrastructure is almost identical in production and the non-local pre-prod environments. However, until several months ago, provisioning and deploying in the local development environment (i.e. developers' laptops) was still a manual affair. This was achieved by following meticulously crafted documentation about how to set up a new workstation. Invariably, differences crept in, e.g. different versions of 3rd party apps, different configuration, even different package managers. Mostly these differences were benign, but sometimes they did cause problems. And that type of problem is much more difficult to diagnose and fix than a bug in the application layer.&lt;/p&gt;
&lt;h2 id="enter-vagrant"&gt;Enter Vagrant&lt;a class="headerlink" href="#enter-vagrant" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; enables you to "Create and configure lightweight, reproducible, and portable development environments." Vagrant VM images are called "boxes". You can produce your own box, or find one at &lt;a href="https://atlas.hashicorp.com/boxes/search"&gt;https://atlas.hashicorp.com/boxes/search&lt;/a&gt;. Once you find one (e.g. hashicorp/precise32), getting it up and running is simple:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;hashicorp/precise32vagrant&lt;span class="w"&gt; &lt;/span&gt;up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then you can ssh to it with:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And to stop it with various levels of severity:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;suspend/halt/destroy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you run init, vagrant creates a basic Vagrantfile in your current directory which has a little bit of configuration in it, and a lot of commented out stuff so you can see how to do common things.  &lt;/p&gt;
&lt;h2 id="how-we-use-vagrant"&gt;How we use Vagrant&lt;a class="headerlink" href="#how-we-use-vagrant" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our gradle build keeps updated a vagrant.config file which contains a simple ruby structure with information about our deployable services and cli apps:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;PROJECTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;core-foo&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;:version&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.211&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;                &lt;/span&gt;:project_root&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/Users/ryan.mckay/projects/shared-services/core-foo&amp;#39;&lt;/span&gt;,
&lt;span class="w"&gt;                &lt;/span&gt;:use_puppet_config&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;                &lt;/span&gt;:use_puppet_deploy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;                &lt;/span&gt;:im_just_a_jar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;false,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="our-vagrantfile"&gt;Our Vagrantfile&lt;a class="headerlink" href="#our-vagrantfile" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Load configuration about our deployables&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;load&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vagrant.config&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Configure some vm settings like memory and number of cpus&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config.vm.provider&lt;span class="w"&gt; &lt;/span&gt;:virtualbox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;vb&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Use VBoxManage to customize the VM. For example to change memory:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;vb.customize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;modifyvm&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;:id,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--memory&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;5120&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;vb.customize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;modifyvm&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;:id,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--cpus&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
end&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Run external provisioning script&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config.vm.provision&lt;span class="w"&gt; &lt;/span&gt;:shell,&lt;span class="w"&gt; &lt;/span&gt;:path&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../scripts/05-vagrant.sh&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Deployment (calls deployment script landed by puppet for each deployable)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;PROJECTS.each&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;artifact_name,&lt;span class="w"&gt; &lt;/span&gt;artifact_config&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;artifact_name,
&lt;span class="w"&gt;      &lt;/span&gt;artifact_config&lt;span class="o"&gt;[&lt;/span&gt;:version&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;artifact_config&lt;span class="o"&gt;[&lt;/span&gt;:im_just_a_jar&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jar&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;service&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;config.vm.provision&lt;span class="w"&gt; &lt;/span&gt;:shell,&lt;span class="w"&gt; &lt;/span&gt;:path&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../scripts/10-deploy.sh&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;:args&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;args
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Make sure everything came up&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config.vm.provision&lt;span class="w"&gt; &lt;/span&gt;:shell,&lt;span class="w"&gt; &lt;/span&gt;:path&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../scripts/99-runtests.sh&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;We use a private custom base image&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# The url from where the &amp;#39;config.vm.box&amp;#39; box will be fetched if it&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# doesn&amp;#39;t already exist on the user&amp;#39;s system.&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;config.vm.box_url&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://foo.com/images/debian7-base.box&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Forward application ports to host system with 10,000 offset&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Application ports&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8000&lt;/span&gt;..9999&lt;span class="o"&gt;)&lt;/span&gt;.step&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.each&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;port&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;config.vm.network&lt;span class="w"&gt; &lt;/span&gt;:forwarded_port,&lt;span class="w"&gt; &lt;/span&gt;:host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;port,&lt;span class="w"&gt; &lt;/span&gt;:guest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;port
&lt;span class="w"&gt;    &lt;/span&gt;config.vm.network&lt;span class="w"&gt; &lt;/span&gt;:forwarded_port,&lt;span class="w"&gt; &lt;/span&gt;:host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;port,&lt;span class="w"&gt; &lt;/span&gt;:guest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;port
&lt;span class="w"&gt;  &lt;/span&gt;end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3rd party service port forwarding&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  # default intellij debugging port
  config.vm.network :forwarded_port, :host =&amp;gt; 5005, :guest =&amp;gt; 5005

  # mysql port
  config.vm.network :forwarded_port, :host =&amp;gt; 3306, :guest =&amp;gt; 3306

  # mongodb port
  config.vm.network :forwarded_port, :host =&amp;gt; 27017, :guest =&amp;gt; 27017

  #rabbitmq 
  config.vm.network :forwarded_port, :host =&amp;gt; 5672, :guest =&amp;gt; 5672
  config.vm.network :forwarded_port, :host =&amp;gt; 15672, :guest =&amp;gt; 15672

  #splunk mangagement port
  config.vm.network :forwarded_port, :host =&amp;gt; 18089, :guest =&amp;gt; 8089
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Host/VM synced folder&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  # create sync folder for integration test data
  local_sync_folder = &amp;quot;/tmp/foo_integration&amp;quot;
  FileUtils.mkdir_p local_sync_folder
  File.chmod(0775, local_sync_folder)
  config.vm.synced_folder local_sync_folder, &amp;quot;/mnt/filer/foo/foodev&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Deploy locally built artifacts if available&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PROJECTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;:use_puppet_config&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;host_config_dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;:project_root&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;/src/config&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host_config_dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synced_folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;host_config_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;/var/bv/conf/#{service_name}/local&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;ln -sf /var/bv/conf/#{service_name}/local/dev-local.properties /var/bv/conf/#{service_name}/properties&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;ln -sf /var/bv/conf/#{service_name}/local/dev-migrate.properties /var/bv/conf/#{service_name}/migrate.properties&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Project root found for #{service_name}, but src/config not detected. Local config directory was not mounted&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;:use_puppet_deploy&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;guest_folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;/var/bv/apps/#{service_name}/local&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;local_artifact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localArtifactNameFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;host_lib_dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;:project_root&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;/build/libs&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host_lib_dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synced_folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;host_lib_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;guest_folder&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;command_to_run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;ln -sf #{guest_folder}/#{local_artifact} /var/bv/apps/#{service_name}/current.jar&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;service_config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;:im_just_a_jar&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="n"&gt;command_to_run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot; &amp;amp;&amp;amp; /etc/init.d/#{service_name} stop &amp;amp;&amp;amp; /etc/init.d/#{service_name} start&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command_to_run&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Project root found for #{service_name}, but build/libs not detected. Local build directory was not mounted.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localArtifactNameFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PROJECTS&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="o"&gt;][&lt;/span&gt;&lt;span class="n"&gt;:version&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;major_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;#{service_name}-#{major_version}.99999.jar&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have started using Vagrant, spinning up a new developer workstation takes a matter of minutes, and you know you got it exactly right. We can run integration and manual tests, and be confident in the result. And we are regularly exercising a good portion of the provisioning, deployment, and monitoring infrastructure that is used in production.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Pair Programming Benefits</title><link href="https://ryanmckaytx.github.io/pair-programming-benefits.html" rel="alternate"></link><published>2014-06-20T20:21:00-05:00</published><updated>2014-06-20T20:21:00-05:00</updated><author><name>Ryan McKay</name></author><id>tag:ryanmckaytx.github.io,2014-06-20:/pair-programming-benefits.html</id><summary type="html">&lt;p&gt;For the last year and a bit, I've been pair programming about 90% of my dev time (i.e. outside of meetings).  I've seen several major benefits compared to the previous 15 years of solo programming.  But I've seen some issues as well.  I think they are addressable, but they …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the last year and a bit, I've been pair programming about 90% of my dev time (i.e. outside of meetings).  I've seen several major benefits compared to the previous 15 years of solo programming.  But I've seen some issues as well.  I think they are addressable, but they are issues to stay aware of.  I'm going to use this post to talk about the benefits, then follow up with another to discuss the issues I have identified.  &lt;/p&gt;
&lt;p&gt;For the most part, I've been practicing what I'm going to refer to as Long Form Pairing.  Other than meetings and lunch, pair all day long.  Not a lot of attention paid to taking breaks or role switching.  However, we have dabbled in &lt;a href="http://c2.com/cgi/wiki?PairProgrammingPingPongPattern"&gt;Ping-Pong Pairing&lt;/a&gt;, &lt;a href="http://agileworld.blogspot.com/2009/10/applying-pomodoro-technique-during-pair.html"&gt;Applying the Pomodoro Technique to Pairing&lt;/a&gt;, and even &lt;a href="http://adam.pohorecki.pl/blog/2013/07/15/ppppp-talk-at-krug/"&gt;Ping-Pong Pomodoro Pair-Programming, or PPPPP&lt;/a&gt;.  In fact, at our most recent hackathon, one of my team mates wrote an &lt;a href="http://plugins.jetbrains.com/plugin/7467?pr=idea"&gt;IntelliJ port&lt;/a&gt; of the &lt;a href="http://www.happyprog.com/pairhero/"&gt;Pair Hero&lt;/a&gt; PPPPP Eclipse plugin.  &lt;/p&gt;
&lt;p&gt;One person is doing the typing and mousing (sometimes referred to as the driver), while the other is watching and providing verbal input (the navigator).  We are typically working on a user story that has been pretty well broken down into tasks, so when code is being written, there is not a lot of discussion required about &lt;strong&gt;what&lt;/strong&gt; needs to be done.  The conversation at that point is about &lt;strong&gt;how&lt;/strong&gt; things should be done - things like how to factor functionality into classes and methods, which test cases we need, what to name things, third party libraries, etc.  &lt;/p&gt;
&lt;h1 id="increased-focus"&gt;Increased Focus&lt;a class="headerlink" href="#increased-focus" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;One of the biggest benefits that I have experienced from pair programming is increased focus on the task at hand.  When you are pairing, you don't have time to check email, do research, go off on tangents in the code, etc.  You are hyper-focused on getting the current task done and moving on to the next one.  Emails come in - I ignore them.  Conversations happen around me - I don't even hear them.  I'm definitely not taking this opportunity to take care of that big refactoring that's been nagging at the back of my mind.&lt;/p&gt;
&lt;h1 id="faster-context-reloading"&gt;Faster Context (Re)loading&lt;a class="headerlink" href="#faster-context-reloading" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Some interruptions are unavoidable, for example, meetings, lunch, nights and weekends :)  Sometimes the other person has the same interruption; sometimes they don't.  Either way, it is much easier and faster to reload a context shared with another person than one you had by yourself.  Similarly, if you are just coming into a context for the first time, someone who already has it loaded can spin you up much faster than you can on your own.  &lt;/p&gt;
&lt;h1 id="teachinglearning-opportunity"&gt;Teaching/Learning Opportunity&lt;a class="headerlink" href="#teachinglearning-opportunity" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;My current team is composed of all senior engineers (for better or worse), so there is not the same teaching opportunity I have had with junior developers in the past.  However, software engineering is a large field, and we all have varying experience in both quality and quantity, so there is still plenty of opportunity for learning from each other.  &lt;/p&gt;
&lt;p&gt;We have identified several major technical areas of interest in our project, too many for any individual to specialize in all of them at the same time.  Some examples are Angular JS (and associated ecosystem), MongoDB, Spring (boot, data, IOC, etc), REST, and Testing.  Team members have selected their top three areas to focus on for this release.  Next release, we will shuffle them up, and encourage pairing between the SMEs and members who are new to the area.  &lt;/p&gt;
&lt;h1 id="alternative-paths"&gt;Alternative Paths&lt;a class="headerlink" href="#alternative-paths" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Having another pair of eyes helps you see alternatives you wouldn't otherwise see.  Period.  Some of these can save you time right now, for example:
* Remembering where some particular configuration properties are
* Informing you of a library that does what you were about to implement
* Explaining how the hell that variable is getting a null value
* Containing scope creep
* Suggesting an easier way to do something, so you can get it done now instead of pulling it out of the story for tech debt&lt;/p&gt;
&lt;p&gt;Others can save you a lot of time later, for example:
* Identifying missing test cases
* Suggesting a more maintainable design&lt;/p&gt;
&lt;p&gt;This is the area where I see the strongest relation with the driver/navigator analogy.  The driver has a very tactical view of the problem.  When the pairs' skill levels are pretty evenly matched, if both partners are going full speed, the driver simply cannot consider the same breadth and depth of strategic concerns as the navigator can.  In fact, sometimes the driver will have to stop typing in order to catch up mentally.  &lt;/p&gt;
&lt;p&gt;Even in the highly unlikely case that neither of the partners learns a thing, they are producing better code, faster, right now.  Happily, both partners &lt;strong&gt;will&lt;/strong&gt; be learning and getting better.  &lt;/p&gt;
&lt;h1 id="adherence-to-team-norms"&gt;Adherence to Team Norms&lt;a class="headerlink" href="#adherence-to-team-norms" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Over the past year, several team norms have emerged.  Some of them are explicit, and recorded in Confluence.  For example, we have discussed and decided on several policies about how we test our software - what should be tested at various levels, how to set up tests, wording of test names, etc.  Others are more implicit, like patterns in the codebase for how we address certain design elements.  Individuals tend to deviate from these standards from time to time for various reasons.  Sometimes you simply forget or were not aware.  Other times, you're just feeling a bit lackadaisical.  And yes, sometimes its a standard you didn't agree with in the first place, and you don't feel like following it right now.  &lt;/p&gt;
&lt;p&gt;As an analogy, I've been doing a lot of swimming lately, trying to improve my technique.  For argument's sake, let's just say I know exactly what I need to do to have a really efficient body position and stroke.  But when I'm in the water actually doing it, trying to focus on all the different elements, and get enough oxygen, its really difficult.  I'll notice that my head position is a bit off, and shift my focus there for a while.  Then I'll notice myself slacking off on rotating my body with each stroke and try to address that.  And so on.  My point is, its easy to say, "Everyone should follow the standards 100% of the time", but even for mature, experienced software developers, that is not realistic.&lt;/p&gt;
&lt;p&gt;Pair programming increases adherence to team standards in a couple ways.  Sometimes your pairing partner will see you starting to go down a path that is out of line with the standards, and remind you.  But a lot of times, they don't have to say anything - just having another person there watching what you type applies pressure to do it "right".&lt;/p&gt;
&lt;h1 id="norming-between-pairing-partners"&gt;Norming between pairing partners&lt;a class="headerlink" href="#norming-between-pairing-partners" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Obviously, there isn't a team standard for every situation or even most situations.  There is plenty of room for individuality.  There are many facets to software engineering, and individual developers tend to be more dogmatic on some and more pragmatic on others.  I have a couple of favorites, like making domain objects immutable and always constructed by fluent builders, or replacing conditionals with polymorphism.  I also have a few pet peeves I specifically look out for, like overuse of generic interfaces.&lt;/p&gt;
&lt;p&gt;Pair programming tends to bend each developer's tendencies toward the pragmatic.  For example, if a domain object only has one or two data members, do you really need to use a builder?  Or if there are only one or two cases in the conditional, is it really worth using separate strategy implementations?  Do we really need to make every class with a single arg non-void return method implement the Handler interface?  No.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&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;&amp;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="n"&gt;R&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&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;h1 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;When a pair is really clicking, there is no doubt in my mind, they are going significantly faster, writing significantly better code, and learning more than an individual programmer.  I have experienced the difference first hand, and it is awesome.  But pairs don't always fire on all cylinders.  I'll talk about that in another post.&lt;/p&gt;</content><category term="misc"></category><category term="pairing"></category><category term="agile"></category><category term="pair-programming"></category></entry></feed>