<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jay R. Wren</title>
    <description>The latest articles on DEV Community by Jay R. Wren (@jrwren).</description>
    <link>https://dev.to/jrwren</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F85242%2F5d7870ef-8080-416a-96ec-43a16b3d89b0.jpeg</url>
      <title>DEV Community: Jay R. Wren</title>
      <link>https://dev.to/jrwren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jrwren"/>
    <language>en</language>
    <item>
      <title>Benchmark Testing Kubernetes Controller Runtime With Envtest</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Wed, 01 Mar 2023 22:11:45 +0000</pubDate>
      <link>https://dev.to/jrwren/benchmark-testing-kubernetes-controller-runtime-with-envtest-339k</link>
      <guid>https://dev.to/jrwren/benchmark-testing-kubernetes-controller-runtime-with-envtest-339k</guid>
      <description>&lt;p&gt;The envtest docs push you into Ginko, but I'm a huge proponent of idiomatic (add another t to this word and I still fit) Go and Ginko doesn't fit me.&lt;/p&gt;

&lt;p&gt;Thankfully you can use standard Go test benchmark functions without any extra testing packages and profile code to improve controllers.&lt;/p&gt;

&lt;p&gt;Here is how I did it...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/types"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/kubernetes/scheme"&lt;/span&gt;
    &lt;span class="n"&gt;controllerruntime&lt;/span&gt; &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/client"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/envtest"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/reconcile"&lt;/span&gt;

    &lt;span class="s"&gt;"something/mypkg"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkMy_Reconcile&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="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;testEnv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;envtest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CRDDirectoryPaths&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path/to/crds"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testEnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testEnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mypkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddToScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;k8sClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyController&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;k8sClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;controllerruntime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"controllers"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mypkg"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InitMetrics&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;rreq&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reconcile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"test"&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="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;rreq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyController.Reconcile() error = %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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="n"&gt;Logf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%#v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my mac, I use brew to install etcd and kubectl. I clone kubernetes and build kube-apiserver myself and copy it to &lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kubectl etcd
git clone git@github.com:kubernetes/kubernetes.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;pushd &lt;/span&gt;kubernetes &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
git checkout v1.26.1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
go &lt;span class="nb"&gt;install&lt;/span&gt; ./cmd/kube-apiserver &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; ~/go/bin/kube-apiserver /usr/local/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;popd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I run this test with &lt;code&gt;KUBEBUILDER_ASSETS=/usr/local/bin go test -benchmem -run=^$ -bench ^BenchmarkMy_Reconcile$ -memprofile mem.prof ./something&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The output is the standard Go test benchmark output and the mem.prof which I examine using &lt;code&gt;go tool pprof mem.prof&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I haven't been programming much lately and so I didn't have &lt;code&gt;dot&lt;/code&gt; installed on my Mac and I had to lookup where to get it. I had to &lt;code&gt;brew install graphviz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;top10&lt;/code&gt; to see the highest memory users and I use &lt;code&gt;web&lt;/code&gt; to generate an SVG of the memory creation call graph.&lt;/p&gt;

&lt;p&gt;In my case, the code base was over using &lt;code&gt;fmt.Sprint(f)&lt;/code&gt; and I was able to optimize these string allocations. I consider this a win.&lt;/p&gt;

</description>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>controllerruntime</category>
      <category>envtest</category>
    </item>
    <item>
      <title>How to write Go</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Fri, 17 Feb 2023 19:11:12 +0000</pubDate>
      <link>https://dev.to/jrwren/how-to-write-go-47nk</link>
      <guid>https://dev.to/jrwren/how-to-write-go-47nk</guid>
      <description>&lt;p&gt;I find myself on a team where where small tiny little mistakes are being made when writing Go. I'd like to collect a reference of how to write better Go. I am putting it here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/effective_go" rel="noopener noreferrer"&gt;https://go.dev/doc/effective_go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/faq" rel="noopener noreferrer"&gt;https://go.dev/doc/faq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/wiki/CodeReviewComments" rel="noopener noreferrer"&gt;https://github.com/golang/go/wiki/CodeReviewComments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go-proverbs.github.io" rel="noopener noreferrer"&gt;https://go-proverbs.github.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/golang-standards/project-layout/issues/10" rel="noopener noreferrer"&gt;https://github.com/golang-standards/project-layout/issues/10&lt;/a&gt; (pkg directory should not be recommended for us)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang-standards/project-layout/issues/117" rel="noopener noreferrer"&gt;https://github.com/golang-standards/project-layout/issues/117&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;If the last 2 don't convince you, then: &lt;a href="https://github.com/go-standard/project-layout" rel="noopener noreferrer"&gt;https://github.com/go-standard/project-layout&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Consider: &lt;a href="https://github.com/uber-go/guide/blob/master/style.md" rel="noopener noreferrer"&gt;https://github.com/uber-go/guide/blob/master/style.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;More: &lt;a href="https://rakyll.org/style-packages/" rel="noopener noreferrer"&gt;https://rakyll.org/style-packages/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Things I see newer Go programers do which should be avoided:&lt;/p&gt;

&lt;h2&gt;
  
  
  Too many packages
&lt;/h2&gt;

&lt;p&gt;Circular packages are not allowed in Go, unlike Java, C#, Javascript, Ruby, or Python. This means that you can structure yourself into a corner which requires great feats of moving things around.&lt;br&gt;
When should you create a new package? Only when you must!&lt;br&gt;
You'll know when you must.&lt;br&gt;
Common musts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Another executable and you want it smaller so you avoid including some larger packages.&lt;/li&gt;
&lt;li&gt;You are building libraries (packages) consumed by 3rd-parties and you want those 3rd parties to have a choice regarding sizing their resulting executables.
Let YAGNI and KISS guide you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, Go is a derivative of C more than any other language, not Python, Ruby, JavaScript, C#, or Java. The compilation artifact of those languages are different than C or Go. If you aren't very familiar with C, then your instinct in Go is probably misguiding.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining an unused interface
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/golang/go/wiki/CodeReviewComments#interfaces" rel="noopener noreferrer"&gt;https://github.com/golang/go/wiki/CodeReviewComments#interfaces&lt;/a&gt; says it best.&lt;/p&gt;

&lt;p&gt;Go's implicit interfaces allow consumers to create the interface they need far better than you can.&lt;/p&gt;
&lt;h2&gt;
  
  
  Common or util packages
&lt;/h2&gt;

&lt;p&gt;NEVER.&lt;br&gt;
&lt;a href="https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common" rel="noopener noreferrer"&gt;https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Don't store context
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://go.dev/blog/context" rel="noopener noreferrer"&gt;https://go.dev/blog/context&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice the RFC for "relax recommendation against storing context" &lt;a href="https://github.com/golang/go/issues/22602" rel="noopener noreferrer"&gt;https://github.com/golang/go/issues/22602&lt;/a&gt; is still open. Never expect this to be closed.&lt;/p&gt;

&lt;p&gt;In fact, if you read that open issue, you'll learn that it is not requesting to relax the recommendation, but to clarify it and the clarification is, "don't store context"&lt;/p&gt;
&lt;h2&gt;
  
  
  No unused parameters (unless you are implementing an interface)
&lt;/h2&gt;

&lt;p&gt;This really applies to ALL programming languages, but us Go programmers like it because we have so many "no unused..." rules already.&lt;/p&gt;
&lt;h2&gt;
  
  
  Clear is better than clever
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://go-proverbs.github.io" rel="noopener noreferrer"&gt;https://go-proverbs.github.io&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=14m35s" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=14m35s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Beware of reading too much
&lt;/h2&gt;

&lt;p&gt;This is basic defensive programming which is often ignored which Go stdlib helps make trivial.&lt;/p&gt;

&lt;p&gt;You've made your net/http Request and have a response:&lt;/p&gt;

&lt;p&gt;Do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LimitReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vary the 32768 to as large as you expect the response to be, but do limit it. Don't do unlimited reads from the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overuse of init()
&lt;/h2&gt;

&lt;p&gt;init() almost always implies globals. See next item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overuse of globals
&lt;/h2&gt;

&lt;p&gt;This really applies to all programming languages, but when you have a global, it is probably a sign that you've designed the structure of your application poorly.&lt;/p&gt;

&lt;p&gt;Early on in a project is the best time to spend time to avoid this.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>haproxy tuning for TLS termination</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Wed, 24 Aug 2022 18:45:43 +0000</pubDate>
      <link>https://dev.to/jrwren/haproxy-tuning-for-tls-termination-ilc</link>
      <guid>https://dev.to/jrwren/haproxy-tuning-for-tls-termination-ilc</guid>
      <description>&lt;p&gt;I'm working on a platform which has been a little neglects from a performance tuning point of view. We had some issues recently which effectively caused denial of service, even though we were doing it to ourselves. As a result, I'm tuning some things and I've learned some things which I didn't know.&lt;/p&gt;

&lt;p&gt;You can probably DoS your haproxy instance too, if you've not already tuned it. I'm using the wonderful &lt;a href="https://github.com/rakyll/hey" rel="noopener noreferrer"&gt;hey&lt;/a&gt; tool to create connections to my haproxy.&lt;/p&gt;

&lt;p&gt;My haproxy has multiple certificates with unique private keys associated with each. Some are 4096 bit keys. Most are 2048 bit keys. Which certificate and key is used depends on which hostname is used to connect to the server.&lt;/p&gt;

&lt;p&gt;I see huge differences in the performance based on 2k or 4k PK used for signing. I've learned that this is expected and that openssl ships with a speed command so that you know what to expect. In my case, haproxy is running on nodes with 8 cores (c5.2xlarge) and is configured to use them (cpu-map 1- 0-).&lt;/p&gt;

&lt;p&gt;I've seen the &lt;code&gt;openssl speed rsa&lt;/code&gt; command, but I had to hunt down the &lt;code&gt;rsa2048&lt;/code&gt;, &lt;code&gt;rsa4096&lt;/code&gt;, and &lt;code&gt;-multi&lt;/code&gt; commands and option.&lt;/p&gt;

&lt;p&gt;In my case, there isn't a great solution because I'm mixing 4k and 2k PK. Don't do that. I'll be advocating to my team to use 2k as much as possible.&lt;/p&gt;

&lt;p&gt;Here is example output from the commands and the resulting &lt;code&gt;sign/s&lt;/code&gt; number is pretty much the value I suggest using for the &lt;code&gt;maxsslrate&lt;/code&gt; setting in haproxy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# openssl speed  -multi 8 rsa2048
Forked child 0
Forked child 1
Forked child 2
Forked child 3
Forked child 4
Forked child 5
Forked child 6
Forked child 7
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+DTP:2048:private:rsa:10
+R1:8096:2048:10.00
+DTP:2048:public:rsa:10
+R1:8202:2048:10.00
+DTP:2048:public:rsa:10
+R1:8279:2048:10.00
+DTP:2048:public:rsa:10
+R1:7769:2048:10.00
+DTP:2048:public:rsa:10
+R1:8329:2048:10.00
+R1:7351:2048:10.00
+DTP:2048:public:rsa:10
+DTP:2048:public:rsa:10
+R1:7355:2048:10.00
+DTP:2048:public:rsa:10
+R1:7363:2048:10.00
+DTP:2048:public:rsa:10
+R2:258668:2048:10.00
+R2:280384:2048:10.00
+R2:274594:2048:10.00
Got: +F2:2:2048:809.600000:25866.800000 from 0
+R2:262465:2048:10.00
+R2:279210:2048:10.00
+R2:255086:2048:10.00
Got: +F2:2:2048:832.900000:27921.000000 from 1
Got: +F2:2:2048:735.100000:25508.600000 from 2
Got: +F2:2:2048:827.900000:27459.400000 from 3
Got: +F2:2:2048:776.900000:26246.500000 from 4
+R2:266538:2048:10.00
Got: +F2:2:2048:735.500000:26653.800000 from 5
Got: +F2:2:2048:820.200000:28038.400000 from 6
+R2:297361:2048:10.00
Got: +F2:2:2048:736.300000:29736.100000 from 7
OpenSSL 1.1.1  11 Sep 2018
built on: Mon Jul  4 11:25:51 2022 UTC
options:bn(64,64) rc4(16x,int) des(int) aes(partial) blowfish(ptr) 
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-wL7Fqk/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000159s 0.000005s   6274.4 217430.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# openssl speed  -multi 8 rsa4096
Forked child 0
Forked child 1
Forked child 2
Forked child 3
Forked child 4
Forked child 5
Forked child 6
Forked child 7
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+DTP:4096:private:rsa:10
+R1:1170:4096:10.00
+DTP:4096:public:rsa:10
+R1:1144:4096:10.00
+DTP:4096:public:rsa:10
+R1:1195:4096:10.00
+DTP:4096:public:rsa:10
+R1:1152:4096:10.00
+DTP:4096:public:rsa:10
+R1:1070:4096:10.00
+DTP:4096:public:rsa:10
+R1:1178:4096:10.01
+DTP:4096:public:rsa:10
+R1:1195:4096:10.00
+DTP:4096:public:rsa:10
+R1:1145:4096:10.02
+DTP:4096:public:rsa:10
+R2:75477:4096:10.00
+R2:71013:4096:10.00
+R2:82989:4096:10.00
+R2:84313:4096:10.00
Got: +F2:4:4096:115.200000:8431.300000 from 0
+R2:77447:4096:10.00
+R2:76991:4096:10.00
Got: +F2:4:4096:117.682318:7699.100000 from 1
Got: +F2:4:4096:119.500000:8298.900000 from 2
Got: +F2:4:4096:107.000000:7744.700000 from 3
Got: +F2:4:4096:117.000000:7547.700000 from 4
+R2:75476:4096:10.00
Got: +F2:4:4096:119.500000:7547.600000 from 5
+R2:78033:4096:10.00
Got: +F2:4:4096:114.271457:7803.300000 from 6
Got: +F2:4:4096:114.400000:7101.300000 from 7
OpenSSL 1.1.1  11 Sep 2018
built on: Mon Jul  4 11:25:51 2022 UTC
options:bn(64,64) rc4(16x,int) des(int) aes(partial) blowfish(ptr) 
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-wL7Fqk/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
                  sign    verify    sign/s verify/s
rsa 4096 bits 0.001082s 0.000016s    924.6  62173.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6200 is much larger than 920.&lt;/p&gt;

&lt;p&gt;2k keys and &lt;code&gt;maxsslrate 6200&lt;/code&gt; it is.&lt;/p&gt;

</description>
      <category>haproxy</category>
      <category>openssl</category>
    </item>
    <item>
      <title>Failing Digital Ocean Kubernetes Challenge</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Sat, 04 Dec 2021 17:13:47 +0000</pubDate>
      <link>https://dev.to/jrwren/rolling-harbor-with-digital-ocean-kubernetes-challenge-3nc</link>
      <guid>https://dev.to/jrwren/rolling-harbor-with-digital-ocean-kubernetes-challenge-3nc</guid>
      <description>&lt;p&gt;Goal: Vanilla k8s install&lt;/p&gt;

&lt;p&gt;The first thing is signing up with Digital Ocean. Apparently, I've never used their product, so I had to signup. When entering my credit card information, I'm a little disappointed that they don't support the autofill features of my browser. I was able to autofill the CC number, but I had to type my name and address.&lt;/p&gt;

&lt;p&gt;Apparently, this page attempts to verify identity using the CC and it does it before clicking submit. The "Save Payment Method" button is disabled if it cannot verify that the card details are correct, but it doesn't feedback about what is invalid. I use this CC everywhere on the internet but DOs form will not validate for me. I wonder how much money DO is leaving on the table by not accepting valid cards and identity. I know everything on this form is correct. I use the exact same details to buy things all over the internet. DO doesn't accept it.&lt;/p&gt;

&lt;p&gt;It took me a good couple of minutes of staring at the form to realize the CVC is required and not filled in. Terrible UX. DO should be ashamed. There was NO feedback. What should have happened: the field should have been red, there should have been a * next to the field. There should have been text which said, "CVC required". I'm starting to see why there are only 6 requests in the GH repo so far.&lt;/p&gt;

&lt;p&gt;Now that I see the welcome page and can access the control panel, I'm going to follow this to get a k8s cluster up. &lt;a href="https://docs.digitalocean.com/products/kubernetes/quickstart/" rel="noopener noreferrer"&gt;https://docs.digitalocean.com/products/kubernetes/quickstart/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm in Ann Arbor, MI and so I am not going to use the default Amsterdam region and instead I'll select NYC3.&lt;/p&gt;

&lt;p&gt;There is a warning by default that says &lt;code&gt;Important:&lt;br&gt;
You have reached the 1 Droplet limit on your account. Request Increase&lt;/code&gt; I click the request button and say I want 3 droplets. Since I'm new to DO and not 100% sure what a droplet is, I'm a little surprised, but I guess I'll assume that a droplet is not a container like I thought, but is a VM. I want 3 VMs because I know that I need at least that many to run a k8s cluster.&lt;/p&gt;

&lt;p&gt;I also change the node type since I won't be doing much with this cluster. The $10/mo nodes seem like plenty.&lt;/p&gt;

&lt;p&gt;The "Create Cluster" button is disabled. No feedback on the form again. I wonder if DO is using some web framework that doesn't support Safari. I thought the web was beyond this at this point, but either DO web devs aren't eating their own dog food, or they ignore users like me. 😢&lt;/p&gt;

&lt;p&gt;I try lowering the node count from 3 to 1. This pops up a "Recommended:&lt;br&gt;
A minimum of 2 nodes is required to prevent downtime during upgrades or maintenance. Request Increase" message. Ok, that makes sense, but... the plus button to increase the node count is disabled. I can't use that, but I can type 2 into the textbox and the message goes away.&lt;/p&gt;

&lt;p&gt;At this point, I do find it strange that AWS will let me rack up a giant bill as soon as they have my billing info, but DO is limiting me to 1 droplet OOTB, even though they have my billing info AND my account has a $60 credit on it. 🤔&lt;/p&gt;

&lt;p&gt;At this point I give up and leave this form and start exploring my account. Settings has the droplet listed too so I try to raise it there. Same thing, it looks like a human reviews these raise requests. I wonder what AWS limits are like by default and I search for it. I see something about 20 instances per region. Seems more reasonable.&lt;/p&gt;

&lt;p&gt;I can't move on, so I'll have to wait.&lt;/p&gt;

&lt;p&gt;About an hour later I received this email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thank you so much for reaching out to us for getting the Droplet limit increased, Unfortunately, your account does not qualify for the requested limit based on our internal assessment guidelines. 

We would be happy to consider your request for a higher Droplet limit after you have established additional billing history with us with no instances of abuse. 

We hope you would understand we have these guidelines in place to ensure the stability of our platform.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, I guess I won't be playing with k8s on DO.&lt;/p&gt;

</description>
      <category>failure</category>
      <category>digitalocean</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>haproxy and certbot on ubuntu</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Mon, 29 Nov 2021 23:12:38 +0000</pubDate>
      <link>https://dev.to/jrwren/haproxy-and-certbot-on-ubuntu-52n1</link>
      <guid>https://dev.to/jrwren/haproxy-and-certbot-on-ubuntu-52n1</guid>
      <description>&lt;p&gt;This is largely inspired from &lt;a href="https://phansch.net/posts/haproxy-letsencrypt-certbot/" rel="noopener noreferrer"&gt;https://phansch.net/posts/haproxy-letsencrypt-certbot/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently changed my setup such that &lt;code&gt;haproxy&lt;/code&gt; listens on &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; and proxies into various services. I like it MUCH better now. IMO, it is far easier to manage.&lt;/p&gt;

&lt;p&gt;One outstanding issue is certificate renewal. The &lt;code&gt;certbot&lt;/code&gt; package on ubuntu (and probably debian, but I'm on ubuntu) includes &lt;code&gt;/lib/systemd/system/certbot.service&lt;/code&gt; and &lt;code&gt;/lib/systemd/system/certbot.timer&lt;/code&gt; files and I would like to work with them. Systemd is awesome and allows me to do so using overrides. I don't even have to think about how to manage the overrides. I use &lt;code&gt;sudo systemctl edit certbot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I add this to the editor which starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Service]
ExecStart=
ExecStart=-/usr/bin/certbot -q renew --preferred-challenges http --http-01-port 9785
ExecStartPost=/etc/haproxy/certs/certbot-renew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an excellent post here which describes why the blank = and the -=. I shall not repeat it here. Go read the reply. It is great: &lt;a href="https://askubuntu.com/a/659268/1668" rel="noopener noreferrer"&gt;https://askubuntu.com/a/659268/1668&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To my &lt;code&gt;haproxy.cfg&lt;/code&gt; I added to both frontends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        acl letsencrypt-req path_beg /.well-known/acme-challenge/
        use_backend letsencrypt if letsencrypt-req
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that letsencrypt backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend letsencrypt
   server letsencrypt 127.0.0.1:9785
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, now when letsencrypt tries to verify on my system, that request for &lt;code&gt;/.well-known/acme-challenge/&lt;/code&gt; will be redirected to the &lt;code&gt;certbot&lt;/code&gt; process listening on port &lt;code&gt;9785&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also that &lt;code&gt;ExecStartPost=&lt;/code&gt; which I must explain.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;haproxy.cfg&lt;/code&gt; is setup with &lt;code&gt;crt-base /etc/haproxy/certs&lt;/code&gt; in the &lt;code&gt;global&lt;/code&gt; section and an HTTPS frontend which starts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend https-in
  bind :::443 v4v6 ssl crt one.example.com crt two.example.com crt e.example.net alpn h2,ht
tp/1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, I have a few different certificates from &lt;code&gt;letsencrypt&lt;/code&gt;. One with a dozen or so SNI names and the other two as wildcard certificates. I use the &lt;code&gt;haproxy&lt;/code&gt; features of reading all pem files in a directory to load them. I'd like any &lt;code&gt;certbot renew&lt;/code&gt; to automatically configure them for &lt;code&gt;haproxy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is done with that &lt;code&gt;ExecStartPost=/etc/haproxy/certs/certbot-renew&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e
cd /etc/haproxy/certs
LELIVE=/etc/letsencrypt/live
RELOAD=false
for d in $(find . -type d -not -name .) ; do
        if [[ $LELIVE/$d/fullchain.pem -nt $d/fullchain.pem ]] ; then
                RELOAD=true
                cp $LELIVE/$d/fullchain.pem $d/fullchain.pem
                cp $LELIVE/$d/privkey.pem $d/fullchain.pem.key
                chown haproxy:haproxy $d/fullchain.pem $d/fullchain.pem.key
        fi
done

if [[ $RELOAD == true ]] ; then
        systemctl reload haproxy
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script looks at each directory in &lt;code&gt;/etc/letsencrypt/live&lt;/code&gt; and if the &lt;code&gt;fullchain.pem&lt;/code&gt; in that directory is newer than the one in &lt;code&gt;/etc/haproxy/certs&lt;/code&gt; then it copies the pem file and keys into the haproxy directory with a filename which haproxy will read. If it did anything then it calls &lt;code&gt;systemctl&lt;/code&gt; to reload haproxy.&lt;/p&gt;

&lt;p&gt;It is possible that I won't have to deal with certificate renewal every again.&lt;/p&gt;

</description>
      <category>haproxy</category>
      <category>letsencrypt</category>
      <category>certbot</category>
      <category>linux</category>
    </item>
    <item>
      <title>nginx with brotli support on Centos 6</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Tue, 19 Oct 2021 16:21:39 +0000</pubDate>
      <link>https://dev.to/jrwren/nginx-with-brotli-support-on-centos-6-2ipj</link>
      <guid>https://dev.to/jrwren/nginx-with-brotli-support-on-centos-6-2ipj</guid>
      <description>&lt;p&gt;I started a new job 6 weeks ago and I'm still learning the environment. I got a new task yesterday and it was an issue which had been bounced around for a while. The issue seemed simple enough, enable brotli on a virtual host. This is the story of how simple may not be so simple.&lt;/p&gt;

&lt;h1&gt;
  
  
  The first issue: this is a Centos 6 host.
&lt;/h1&gt;

&lt;p&gt;Centos 6 is end of life as of November 2020. After a bit of discussion, the plan is to continue to investigate if we can do this as a stopgap until the entire solution can be reworked.&lt;/p&gt;

&lt;p&gt;So, how would one investigate how to do this? It turns out, it isn't so trivial, and even just starting an investigate can be tricky. My first thought was, start a docker instance so I can poke around and try various RPMs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker pull centos:6.7&lt;/code&gt;&lt;br&gt;
Great start!&lt;br&gt;
&lt;code&gt;docker run -ti centos:6.7&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Cool, I've got a prompt, now what?&lt;/p&gt;

&lt;p&gt;After searching the web, I see that nginx.org has rpm packages. I'll try one and see if it has brotli support.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -LO https://nginx.org/packages/mainline/centos/6/x86_64/RPMS/nginx-1.19.5-1.el6.ngx.x86_64.rpm&lt;br&gt;
curl: (35) SSL connect error&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;WAT? ok, maybe the certs on this 2yo docker image (I had checked docker hub) are out of date. &lt;code&gt;curl -k&lt;/code&gt; the same thing and get the same error. WTF? add the &lt;code&gt;-v&lt;/code&gt; to curl and see the libnss message &lt;code&gt;* warning: ignoring value of ssl.verifyhost&lt;/code&gt; Well ain't that something? At this point I mumble under my breath about how debian/ubuntu uses openssl linked curl by default and not libnss and I wonder if it would have behaved the same.&lt;/p&gt;

&lt;p&gt;As I write this, I realize that maybe I should have used a more recent Centos 6 docker image, 6.10 perhaps. Unfortunately I'm not as experienced with Centos as I should be. Part of this fun is diving in and learning. I used 6.7 because that is what this server under question says it is.&lt;/p&gt;

&lt;p&gt;Alright, so new problem...&lt;/p&gt;
&lt;h1&gt;
  
  
  How to update certificates on a 2yo Centos 6.7 docker image?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;yum update&lt;/code&gt; says It can't do its thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yum update
Loaded plugins: fastestmirror
Setting up Update Process
Loading mirror speeds from cached hostfile
YumRepo Error: All mirror URLs are not using ftp, http[s] or file.
 Eg. Invalid release/repo/arch combination/
removing mirrorlist with no valid mirrors: /var/cache/yum/x86_64/6/base/mirrorlist.txt
Error: Cannot find a valid baseurl for repo: base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, some searching tells me to use &lt;code&gt;baseurl&lt;/code&gt; instead of &lt;code&gt;mirrorlist&lt;/code&gt; and that it is now at &lt;code&gt;vault.centos.org&lt;/code&gt;. I try that and get a new error: &lt;code&gt;http://vault.centos.org/centos/6/os/x86_64/repodata/repomd.xml: [Errno 14] Peer cert cannot be verified or peer cert invalid&lt;/code&gt; Yes, that says http:, but port 80 just redirects to port 443 and then we get the cert error. We have a catch-22. We need to upgrade our package to get new certs but we need new certs to upgrade our package. We need to upgrade our package to get new certs but we need new certs to upgrade our package. We need to upgrade our package to get new certs but we need new certs to upgrade our package. We need to upgrade our package to get new certs but we need new certs to upgrade our package. &lt;em&gt;cough&lt;/em&gt; &lt;em&gt;ouch&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(Update: I'm doing this often enough that editing that file takes too long and so there is this: &lt;code&gt;sed -i 's/#baseurl=http:\/\/mirror/baseurl=http:\/\/vault/;s/mirrorlist/#mirrorlist/' /etc/yum.repos.d/CentOS-Base.repo&lt;/code&gt; )&lt;/p&gt;

&lt;p&gt;For a solution, I got lucky. I knew to try to get the ca-certificates rpm manually and install it without yum. Worse come to worse I could download it on another host and copy it with &lt;code&gt;docker cp&lt;/code&gt; or use netcat or python simple server or put it on a non-TLS webserver or any other method of moving data around. I was manually browsing the repo and got this URL &lt;code&gt;curl -LO https://archive.kernel.org/centos-vault/6.10/updates/x86_64/Packages/ca-certificates-2020.2.41-65.1.el6_10.noarch.rpm&lt;/code&gt; and it turns out that the cert serving archive.kernel.org was acceptable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rpm -U ./ca-cert*.rpm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And now I can &lt;code&gt;yum update ; yum upgrade&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Now we can look for nginx with brotli
&lt;/h1&gt;

&lt;p&gt;Search search search, search the web and you will find no clear answers. There are posts about compiling it yourself. There are posts about using a subscription repo. (&lt;a href="https://www.getpagespeed.com" rel="noopener noreferrer"&gt;https://www.getpagespeed.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Incidentally, that subscription repo also had blog post which helped confirm my findings above: &lt;a href="https://www.getpagespeed.com/server-setup/how-to-fix-yum-after-centos-6-went-eol" rel="noopener noreferrer"&gt;https://www.getpagespeed.com/server-setup/how-to-fix-yum-after-centos-6-went-eol&lt;/a&gt; along with &lt;a href="https://forums.centos.org/viewtopic.php?f=13&amp;amp;t=78238" rel="noopener noreferrer"&gt;https://forums.centos.org/viewtopic.php?f=13&amp;amp;t=78238&lt;/a&gt; which verified what I had guessed about updating certificates.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now my first thought was, nginx has this by default now, right? Use the newest package from nginx.org. Look at all of 'em here: &lt;a href="https://nginx.org/packages/centos/6/x86_64/RPMS/" rel="noopener noreferrer"&gt;https://nginx.org/packages/centos/6/x86_64/RPMS/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I &lt;code&gt;curl -LO&lt;/code&gt;ed (curllo is a verb ya see) that &lt;a href="https://nginx.org/packages/mainline/centos/6/x86_64/RPMS/nginx-1.19.5-1.el6.ngx.x86_64.rpm" rel="noopener noreferrer"&gt;https://nginx.org/packages/mainline/centos/6/x86_64/RPMS/nginx-1.19.5-1.el6.ngx.x86_64.rpm&lt;/a&gt; and installed it (&lt;code&gt;rpm -i ./nginx*rpm&lt;/code&gt;) and was disappointed that it had no brotli (&lt;code&gt;strings /usr/sbin/nginx | grep brot&lt;/code&gt; confirms).  &lt;em&gt;(That is after I &lt;code&gt;yum install initscripts&lt;/code&gt; as required by that package)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well bummer.&lt;/p&gt;

&lt;p&gt;Keep searching and reading...&lt;/p&gt;

&lt;p&gt;The next thing I tried were the packages from &lt;a href="https://repo.aerisnetwork.com" rel="noopener noreferrer"&gt;https://repo.aerisnetwork.com&lt;/a&gt; but, in short they didn't do what I wanted or had some drawback.&lt;/p&gt;

&lt;p&gt;Next, I tried poking at the packages from getpagespeed.com, I even browsed in my browser, got this URL: &lt;code&gt;https://extras.getpagespeed.com/redhat/6/mainline/x86_64/RPMS/nginx-1.21.3-1.el6.ngx.x86_64.rpm&lt;/code&gt; and tried to download and install it, but it didn't actually download. Registration is required and apparently they whitelist registered users by IP. Jumping through those hoops would not be sustainable for me in my work environment so I discarded getpagespeed.com.&lt;/p&gt;

&lt;p&gt;I have no idea why, but next I tried this &lt;code&gt;https://nginx.org/packages/centos/6/x86_64/RPMS/nginx-1.18.0-2.el6.ngx.x86_64.rpm&lt;/code&gt; Older than the pervious nginx.org package, but it isn't in the &lt;code&gt;mainline&lt;/code&gt; repo and I'm Centos n00b enough that I don't know the difference. Again, &lt;code&gt;strings /usr/sbin/nginx|grep brot&lt;/code&gt; showed nothing, so that got a quick &lt;code&gt;rpm -e nginx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, I tried &lt;code&gt;curl -LO https://repo.aerisnetwork.com/archive/nginx-more-1.13.6-1.el6.x86_64.rpm&lt;/code&gt; and &lt;code&gt;https://repo.aerisnetwork.com/archive/nginx-more-1.14.2-4.el6.x86_64.rpm&lt;/code&gt;. (No idea why I grabbed 1.13 and 1.14.)  There were a bunch of various nginx packages on &lt;code&gt;repo.aerisnetwork.com&lt;/code&gt; For this to work there were some requirements so I ran &lt;code&gt;yum install gd libxslt&lt;/code&gt; but there was still the case of &lt;code&gt;libmaxminddb.so.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, I tried &lt;code&gt;repo.codeit.guru&lt;/code&gt; packages. I don't recall exactly where I found this repo. I think it was multiple sources. One of them was &lt;code&gt;https://nixcp.com/brotli-compression-nginx/&lt;/code&gt;. I'd probably also seen &lt;code&gt;https://codeit.guru/en_US/2020/04/nginx-1-18-0-stable-with-brotli-support-tls-1-3-final-rfc-8446-built-against-openssl-1-1-1g-for-red-hat-enterprise-linux-and-centos/&lt;/code&gt;. I was desperate enough to copy and page the root repo URL and hope for a Centos 6 directory. &lt;strong&gt;There was one!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -LO https://repo.codeit.guru/packages/mainline/centos/6/x86_64/nginx-1.19.5-1.el6.codeit.x86_64.rpm
rpm -i ./nginx-1.19.5-1.el6.codeit.x86_64.rpm

warning: ./nginx-1.19.5-1.el6.codeit.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID 898b43f4: NOKEY
error: Failed dependencies:
    libbrotli = 1:1.0.7 is needed by nginx-1:1.19.5-1.el6.codeit.x86_64
    libmaxminddb.so.0()(64bit) is needed by nginx-1:1.19.5-1.el6.codeit.x86_64

curl -LO https://repo.codeit.guru/packages/mainline/centos/6/x86_64/libbrotli-1.0.7-1.codeit.el6.x86_64.rpm
rpm -i ./libbrotli-1.0.7-1.codeit.el6.x86_64.rpm 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is obviously brotli support but also the same &lt;code&gt;libmaxminddb.so.0&lt;/code&gt; dependency.&lt;/p&gt;

&lt;h1&gt;
  
  
  libmaxminddb on Centos 6
&lt;/h1&gt;

&lt;p&gt;Now I had to briefly forget nginx and focussing on where to find this dependency. Surely there is a package?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yum install libmaxmindddb
Loaded plugins: fastestmirror
Setting up Install Process
Loading mirror speeds from cached hostfile
No package libmaxmindddb available.
Error: Nothing to do
$ yum install libmaxmindddb-dev
...
No package libmaxmindddb-dev available.
$ yum install libmaxmindddb-devel
...
No package libmaxmindddb-devel available.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It required much searching. I wondered if it was in the nginx-module-geoip rpm, but no.&lt;/p&gt;

&lt;p&gt;Finally, I found some comments about epel. I didn't have epel repo enabled. It is not part of vault AFAICT, but I was able to web browse epel and find a package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -LO https://archives.fedoraproject.org/pub/archive/epel/6/x86_64/Packages/l/libmaxminddb-1.1.1-5.el6.x86_64.rpm
rpm -i ./libmaxminddb-1.1.1-5.el6.x86_64.rpm 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now nginx package from codeit.guru can be installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rpm -i ./nginx-1.19.5-1.el6.codeit.x86_64.rpm 
warning: ./nginx-1.19.5-1.el6.codeit.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID 898b43f4: NOKEY
----------------------------------------------------------------------

Thanks for using nginx!

Please find the official documentation for nginx here:
* http://nginx.org/en/docs/

Please subscribe to nginx-announce mailing list to get
the most important news about nginx:
* http://nginx.org/en/support.html

Commercial subscriptions for nginx are available on:
* http://nginx.com/products/

----------------------------------------------------------------------
$ rpm -ql nginx
/etc/logrotate.d/nginx
/etc/nginx
/etc/nginx/conf.d
/etc/nginx/conf.d/default-ssl.conf.example
/etc/nginx/conf.d/default.conf
/etc/nginx/fastcgi_params
/etc/nginx/koi-utf
/etc/nginx/koi-win
/etc/nginx/mime.types
/etc/nginx/modules
/etc/nginx/nginx.conf
/etc/nginx/php.inc
/etc/nginx/scgi_params
/etc/nginx/uwsgi_params
/etc/nginx/win-utf
/etc/rc.d/init.d/nginx
/etc/rc.d/init.d/nginx-debug
/etc/sysconfig/nginx
/etc/sysconfig/nginx-debug
/usr/lib64/nginx
/usr/lib64/nginx/modules
/usr/sbin/nginx
/usr/sbin/nginx-debug
/usr/share/doc/nginx-1.19.5
/usr/share/doc/nginx-1.19.5/COPYRIGHT
/usr/share/man/man8/nginx.8.gz
/usr/share/nginx
/usr/share/nginx/html
/usr/share/nginx/html/50x.html
/usr/share/nginx/html/index.html
/var/cache/nginx
/var/log/nginx
$ strings /usr/sbin/nginx | grep brot
ngx_http_brotli_static_module_ctx
ngx_http_brotli_filter_module
ngx_http_brotli_static_module
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at those beautiful symbols!&lt;/p&gt;

&lt;p&gt;Next task is to discuss with the team all of the reasons that we should &lt;strong&gt;NOT&lt;/strong&gt; integrate this into our environment and instead migrate off of Centos 6 instead.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>howto</category>
      <category>software</category>
      <category>tips</category>
    </item>
    <item>
      <title>Go Standards Project Layout</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Tue, 11 May 2021 15:48:21 +0000</pubDate>
      <link>https://dev.to/jrwren/go-standards-project-layout-4hfj</link>
      <guid>https://dev.to/jrwren/go-standards-project-layout-4hfj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;There are only two hard things in Computer Science: cache invalidation and naming things.&lt;/p&gt;

&lt;p&gt;-- Phil Karlton&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For new Go programmers a third hardest thing might be package layout. The truth is, it doesn't have to be hard at all.&lt;/p&gt;

&lt;p&gt;Alright, here is the secret to the hard party: forget everything you know from other languages, unless that other language is C.&lt;/p&gt;

&lt;p&gt;Strangely, this might be the most controversial issue in the Go community, including advice so bad that project contributors had to file a bug: &lt;a href="https://github.com/golang-standards/project-layout/issues/117" rel="noopener noreferrer"&gt;https://github.com/golang-standards/project-layout/issues/117&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honestly, &lt;a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1#.ds38va3pp" rel="noopener noreferrer"&gt;https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1#.ds38va3pp&lt;/a&gt; is probably the best advice, but don't feel that you need to follow all of it immediately. You probably won't have mocks at first. You probably don't need to decouple things at first. Remember, loose coupling is only a good thing if you need it. Otherwise you are paying a cost. Simplicity is far more valuable than future flexibility especially when YAGNI.&lt;/p&gt;

&lt;p&gt;There is a list of some great TODO and DO NOT items here:&lt;br&gt;
&lt;a href="https://rakyll.org/style-packages/" rel="noopener noreferrer"&gt;https://rakyll.org/style-packages/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Along with common and util, I believe &lt;code&gt;api&lt;/code&gt; is a package name which should be avoided. It should be obvious why: all packages have an API. &lt;code&gt;api&lt;/code&gt; certainly is a short name, but there is nothing clear about it. &lt;a href="https://blog.golang.org/package-names" rel="noopener noreferrer"&gt;https://blog.golang.org/package-names&lt;/a&gt; Package names should be short and clear. If you want to name a package &lt;code&gt;api&lt;/code&gt;, instead, ask yourself, &lt;code&gt;api for what?&lt;/code&gt; and name the package the answer to that question.&lt;/p&gt;

&lt;p&gt;Strangely, there is a ton of official guidance on that page which is completely ignored by other recommendations. I'll link it again: &lt;a href="https://blog.golang.org/package-names" rel="noopener noreferrer"&gt;https://blog.golang.org/package-names&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you read someone else's advice, such as: &lt;a href="https://peter.bourgon.org/go-best-practices-2016/#repository-structure" rel="noopener noreferrer"&gt;https://peter.bourgon.org/go-best-practices-2016/#repository-structure&lt;/a&gt; consider each point against the official advice of &lt;a href="https://blog.golang.org/package-names" rel="noopener noreferrer"&gt;https://blog.golang.org/package-names&lt;/a&gt; and if it doesn't fit, discard it. Also consider each point against YAGNI.&lt;/p&gt;

&lt;p&gt;If you read enough of these advice posts you'll being to notice some are not prescriptive. That is great. Prescriptive advice is harmful. No one can know your specific needs and circumstances. Following the wrong advice can be very expensive in the long run. Instead look at advice like &lt;a href="https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html" rel="noopener noreferrer"&gt;https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html&lt;/a&gt; Notice how it is about philosophy and usability. It is not about how the programmer writing the code feels or wants things to be.&lt;/p&gt;

&lt;p&gt;At any step, ask yourself, has this practice added value? If it hasn't, then don't do it. Undo it even. Value is also subjective. While I don't find a &lt;code&gt;pkg&lt;/code&gt; dir to have any value, another team might find it to be very valuable to put everything there, away from the non-Go code things (docs, scripts, etc) in their repo. That is fine. Recognize that there is no right on wrong here, there is only our different values.&lt;/p&gt;

&lt;p&gt;My favorite line of advice is from: &lt;a href="https://christine.website/blog/within-go-repo-layout-2020-09-07" rel="noopener noreferrer"&gt;https://christine.website/blog/within-go-repo-layout-2020-09-07&lt;/a&gt; which reasons about the advice to say: &lt;code&gt;"...Leaves the development team a lot more agency to decide how to name things"&lt;/code&gt; Take your agency. Ignore prescription.&lt;/p&gt;

&lt;p&gt;All that said, how would I &lt;a href="https://www.reddit.com/r/golang/comments/n9tgjk/how_to_divide_mvc_codes_into_package/" rel="noopener noreferrer"&gt;divide MVC into packages&lt;/a&gt; I wouldn't... until I need to.&lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Limit the Number of Connections to an Endpoint In Go</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Fri, 07 May 2021 17:42:06 +0000</pubDate>
      <link>https://dev.to/jrwren/limit-the-number-of-connections-to-an-endpoint-in-go-3a14</link>
      <guid>https://dev.to/jrwren/limit-the-number-of-connections-to-an-endpoint-in-go-3a14</guid>
      <description>&lt;p&gt;I found myself implementing this pattern twice in recent times. As a result, I thought I'd share it.&lt;/p&gt;

&lt;p&gt;First, some background. I wrote a web crawler for reasons. The crawler would find links in a webpage and create a go routine to follow the link and download what was at that endpoint, and recurse. Quickly, this caused the system running the crawler to run out of open file handles. Goroutines are so powerful that we had the opposite problem that we have in a serial downloader. Rather than going to slowly because of a one at a time situation, we did so much at once that we ran out of free file handles. I just wrote another crawler type program which downloads links discovered via a JSON API and needed the same solution.&lt;/p&gt;

&lt;p&gt;Browsers are examples of good behavior to the point that they don't open very many connections to remote hosts.  There is a great stack overflow post &lt;a href="https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser&lt;/a&gt; which describes how web browsers limit connections. It is very easy to make my Go programs behave in a similar way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;httpSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentFileName&lt;/span&gt;&lt;span class="p"&gt;)&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="c"&gt;// httpSave saves u to file named name.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;httpSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Queue errors?&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0666&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Queue errors?&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wrote %d bytes to %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above httpSave function is real, and the main is partially omitted code for how it might be used.&lt;/p&gt;

&lt;p&gt;There are many ways one might limit the number of connections, but a simple to understand way IMO, rather than using locks and a counter is to use a channel of length N where N is the number of concurrent things. I'm still going to use a lock, but that is for synchronizing access to the map which maps the host we are limiting to the channel.&lt;/p&gt;

&lt;p&gt;I'm using globals, but for a larger app I would add the map and lock to the struct that holds data around my app state for this operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;domainLimit&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;domainListMutex&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;domainLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getDomainToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;domainListMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;domainListMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;domainLimit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Six connections per host.&lt;/span&gt;
        &lt;span class="n"&gt;domainLimit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;f&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;To use this, I add 1 line to the top of the httpSave function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;httpSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;getDomainToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I had other functions doing http operations I'd need to add that same one line to the top of them. In my case, I don't.&lt;/p&gt;

&lt;p&gt;I like it because it is simple and it works.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>How to make a nice high dev salary into not as much, frugally</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Fri, 07 May 2021 17:34:35 +0000</pubDate>
      <link>https://dev.to/jrwren/how-to-make-a-nice-high-dev-salary-into-not-as-much-frugally-5a25</link>
      <guid>https://dev.to/jrwren/how-to-make-a-nice-high-dev-salary-into-not-as-much-frugally-5a25</guid>
      <description>&lt;p&gt;Let's say you are senior and have a lot of experience and make well above the average salary and you aren't located coastal where everything is in a bubble. Let's take a nice round number, $150,000 salary.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max out your 401k. -&amp;gt; 150 - 19 = 131&lt;/li&gt;
&lt;li&gt;Max out your IRA &lt;em&gt;AND&lt;/em&gt; your spouse's IRA. -&amp;gt; 131 - 12 = 119&lt;/li&gt;
&lt;li&gt;Max out family HSA. -&amp;gt; 119 - 7 = 112&lt;/li&gt;
&lt;li&gt;Max out ESPP @ 10% of your salary. -&amp;gt; 112 - 15 = 97&lt;/li&gt;
&lt;li&gt;Save $3600/yr for child's college. -&amp;gt; 97,000 - 3600 = 93,400&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wow, 37% ($56,600) of income is going to saving for the future and 68% ($38,000) of that is before taxes.&lt;/p&gt;

&lt;p&gt;I was reading this post by Troy Hunt &lt;a href="https://www.troyhunt.com/10-personal-finance-lessons-for-technology-professionals/" rel="noopener noreferrer"&gt;https://www.troyhunt.com/10-personal-finance-lessons-for-technology-professionals/&lt;/a&gt; and I realized I wanted to know my own numbers and so I ran these myself (These are not exactly my numbers).&lt;/p&gt;

</description>
      <category>money</category>
      <category>industry</category>
    </item>
    <item>
      <title>Concurrent Thinking Might Lead to Better Code</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Fri, 07 May 2021 17:33:18 +0000</pubDate>
      <link>https://dev.to/jrwren/concurrent-thinking-might-lead-to-better-code-29pd</link>
      <guid>https://dev.to/jrwren/concurrent-thinking-might-lead-to-better-code-29pd</guid>
      <description>&lt;p&gt;For whatever reason I was listening to &lt;a href="https://www.youtube.com/watch?v=oV9rvDllKEg" rel="noopener noreferrer"&gt;Rob Pike talk about Concurrency is not Parallelism&lt;/a&gt; yet again.  I had also been using Kafka for the first time recently.  It triggered an epiphany.&lt;/p&gt;

&lt;p&gt;I first heard about Kafka around six or seven years ago.  I asked what it was and I was told of its wonders and how it would solve all my problems and make everything better and how any app not written around Kafka was inferior and it is the future and blah blah blah.  I immediately disregarded it all and ignored it.&lt;/p&gt;

&lt;p&gt;Having now used it, I can say for certain that I don't know what the fuss is about.  I'd never choose to use it again.  I'd never design an app around it.  I would, instead, strive for simplicity and solve the types of problems which Kafka solves a different way, preferably by avoiding them altogether.&lt;/p&gt;

&lt;p&gt;Now, while listening to Rob Pike talk about concurrency it occurred to me one possibility why Kafka was so beloved by those who espouse it.  It is a JVM project and far more heavily used in that circle than outside of it.  JVM folk are very likely used to a certain way of doing things and building around Kafka entirely breaks those ways.  It shakes things up, possibly in a good way.  Kafka creates a communication boundary that wouldn't have otherwise existed in a monolithic JVM app.  Kafka creates the need for multiple processes doing smaller tasks utilizing varying Kafka features, rather than a single monolith.  The requirement of these structures could be a huge benefit and explains the sentiment which I heard all of those years ago.&lt;/p&gt;

&lt;p&gt;I think I've learned a bit thinking about these things...&lt;/p&gt;

&lt;p&gt;... or I'm completely full of shit.&lt;/p&gt;

</description>
      <category>go</category>
      <category>kafka</category>
      <category>concurrency</category>
      <category>programming</category>
    </item>
    <item>
      <title>Consider Not Using 3rd Party CDNs</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Sun, 11 Oct 2020 19:41:35 +0000</pubDate>
      <link>https://dev.to/jrwren/consider-not-using-3rd-party-cdns-1o1n</link>
      <guid>https://dev.to/jrwren/consider-not-using-3rd-party-cdns-1o1n</guid>
      <description>&lt;p&gt;I just read &lt;a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/" rel="noopener noreferrer"&gt;https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/&lt;/a&gt; and I feel the need to expound on the points.&lt;/p&gt;

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

&lt;p&gt;Great questions from &lt;a class="mentioned-user" href="https://dev.to/edent"&gt;@edent&lt;/a&gt;, but no answers. I am disappointed. Thankfully, a bit of searching and I learned some things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zoompf.com/blog/2010/01/should-you-use-javascript-library-cdns/" rel="noopener noreferrer"&gt;https://zoompf.com/blog/2010/01/should-you-use-javascript-library-cdns/&lt;/a&gt; says that the network effect of caching simply isn't there. That was in 2010. With even more varying javascript libraries in 2020, it is probably even more true. Further, because of latency of DNS lookup and TLS session creation, a connection to a new 3rd party host is slower than doing none of that because it is already done. Further still, Hoffman says that assuming a slowish internet speed -- although not all that slow for a highly variable mobile connection -- the time to connect is about the same as the time to download at ⅓ of a second. Excellent analysis by Hoffman, I highly recommend reading his post.&lt;/p&gt;

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

&lt;p&gt;Eden gives a strawman argument. I am disappointed. Of course we want our sites to be fast. Attempting to make them faster by misguidedly offloading some large assets to another server isn't a wrong idea. The notion that you shouldn't do A unless you can do B is foolish. But none of it matters given the argument from 1, so we can ignore 2.&lt;/p&gt;

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

&lt;p&gt;Indeed the low hit rates cited by Hoffman in #1 are surely contributed to by the varying versions of libraries out there. This is the world of javascript where there is a new version every week and so there are 520 versions of jquery in the past 10 yrs alone. ;)&lt;/p&gt;

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

&lt;p&gt;Unless you ARE a CDN, it is highly like that the CDN will be more reliable than your own site. I do not feel this is a believable argument.&lt;/p&gt;

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

&lt;p&gt;Excellent questions, but I'd go much further and not form them as a question. By using a CDN you are giving away all of your users usage patterns to a 3rd party. This is unacceptable.&lt;/p&gt;

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

&lt;p&gt;Excellent point, and while IMO it is far less likely that a popular, well-run CDN is going to be compromised than your typical website run by our average IT department or devops group, it is absolutely imperative that we consider the risk.&lt;/p&gt;

&lt;p&gt;So What?&lt;/p&gt;

&lt;p&gt;So What?&lt;/p&gt;

&lt;p&gt;So, understand what risk you are taking by using a 3rd party CDN and consider not using them.&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>cdn</category>
      <category>hosting</category>
    </item>
    <item>
      <title>SOLID principles in the Go programming language</title>
      <dc:creator>Jay R. Wren</dc:creator>
      <pubDate>Tue, 31 Dec 2019 03:19:24 +0000</pubDate>
      <link>https://dev.to/jrwren/solid-principles-in-the-go-programming-language-kbc</link>
      <guid>https://dev.to/jrwren/solid-principles-in-the-go-programming-language-kbc</guid>
      <description>&lt;p&gt;There was a discussion on SOLID principles in the Go programming language and I threw in my two-cents. Once I was done writing, I was surprised with what I came up with.&lt;/p&gt;

&lt;p&gt;Here is why you shouldn't think very much about SOLID in Go:&lt;/p&gt;

&lt;p&gt;Consider each principle:&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Responsibility Principle:
&lt;/h3&gt;

&lt;p&gt;"A class should only have a single responsibility..."&lt;/p&gt;

&lt;p&gt;Go doesn't have classes. Already the principle doesn't apply...&lt;/p&gt;

&lt;p&gt;I write that tongue in cheek, because of course a struct is a bit like a class, and of course SRP is great for a Go struct type, but also, since Go struct types do not have inheritance, we don't get ~classes~types inheriting behaviors. Thus we don't have to worry about un-intended inherited responsibilities. It is easier to achieve SRP in Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-close Principle:
&lt;/h3&gt;

&lt;p&gt;"Software entities ... should be open for extension, but closed for modification."&lt;/p&gt;

&lt;p&gt;No inheritance, thus no extension. Go gets this for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Liskov substitution principle:
&lt;/h3&gt;

&lt;p&gt;"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program." See also design by contract.&lt;/p&gt;

&lt;p&gt;Go doesn't have subtypes. YAY. So this only applies to interfaces implementations. Yes, it is an absolute must for interface implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface segregation principle:
&lt;/h3&gt;

&lt;p&gt;"Many client-specific interfaces are better than one general-purpose interface."&lt;/p&gt;

&lt;p&gt;This is such idiomatic Go that it should go without saying.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Inversion Principle:
&lt;/h3&gt;

&lt;p&gt;One should "depend upon abstractions, not concretions"&lt;/p&gt;

&lt;p&gt;This is another way of saying accept interfaces. Yes, we value this highly in Go.&lt;/p&gt;

&lt;p&gt;In summary:&lt;br&gt;
We think about things slightly differently in Go, because Go isn't OOP, but once translated to Go's type system, SOLID absolutely matches with Go idioms.&lt;/p&gt;

</description>
      <category>solid</category>
      <category>go</category>
    </item>
  </channel>
</rss>
