<?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: Davide Imola</title>
    <description>The latest articles on DEV Community by Davide Imola (@davideimola).</description>
    <link>https://dev.to/davideimola</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%2F1191980%2F3b0daa0e-1dd1-4c2b-a85c-7d6fd6b65f28.jpg</url>
      <title>DEV Community: Davide Imola</title>
      <link>https://dev.to/davideimola</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davideimola"/>
    <language>en</language>
    <item>
      <title>I Rebuilt My Site Twice. Here's What the Second Time Taught Me.</title>
      <dc:creator>Davide Imola</dc:creator>
      <pubDate>Fri, 27 Mar 2026 09:26:35 +0000</pubDate>
      <link>https://dev.to/playfulprogramming/i-rebuilt-my-site-twice-heres-what-the-second-time-taught-me-4bci</link>
      <guid>https://dev.to/playfulprogramming/i-rebuilt-my-site-twice-heres-what-the-second-time-taught-me-4bci</guid>
      <description>&lt;p&gt;I rebuilt my personal site. Then I rebuilt it again.&lt;/p&gt;

&lt;p&gt;The first time I thought the problem was the old site. The second time I realized the problem was how I was working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two versions, same problem
&lt;/h2&gt;

&lt;p&gt;The original site was built in Astro. I chose Astro because I wanted to experiment with something new. I'd been doing mostly backend and infrastructure work at the time, and my frontend skills were, let's say, still developing. The result was functional and completely forgettable: white text on a black background, no real personality, nothing that said anything about who I was or what I did.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3i9hhz1qb2bjwo2wzbx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3i9hhz1qb2bjwo2wzbx.webp" alt="The original Astro site: dark, minimal, forgettable" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I decided to rebuild it using AI. I had access to Cursor through work, so I started there. The workflow was straightforward: describe what you want, get a plan, let it generate most of the site at once.&lt;/p&gt;

&lt;p&gt;It was better. Some things were genuinely nice: red underlines styled like brushstrokes, a layout that felt more composed. But it was also a mess. Too many color variations that didn't quite agree with each other. And at some point, because I'd mentioned that I love Japanese culture and aesthetics, the AI decided the obvious move was to add &lt;strong&gt;kanji characters&lt;/strong&gt; to the design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6mxeht6qmcyjhc0pic8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6mxeht6qmcyjhc0pic8.webp" alt="The Cursor rebuild: more styled, but still inconsistent" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't speak Japanese. I removed the kanji.&lt;/p&gt;

&lt;p&gt;The core issue wasn't the kanji. That was just the most visible symptom. The real problem was that I'd handed over a vague brief ("I like Japanese aesthetics") and gotten back a literal interpretation, with all the inconsistencies that come from generating a whole site in one go. &lt;strong&gt;The design system wasn't solid because I'd never defined one.&lt;/strong&gt; Everything was a bit improvised.&lt;/p&gt;

&lt;p&gt;Still not working. But now I knew why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting over with a different question
&lt;/h2&gt;

&lt;p&gt;I could have kept patching the Cursor version. Instead I started from scratch. Partly out of frustration, mostly out of curiosity. I'd been reading and watching more about AI-assisted development, and I wanted to try a fundamentally different approach.&lt;/p&gt;

&lt;p&gt;The question wasn't "can AI build my site?" I already knew it could. The question was: &lt;strong&gt;what happens if I stop treating AI as a site generator and start treating it as a collaborator?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I switched to Claude Code and gave myself one constraint before touching a single component: I would define the design system first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design system first, everything else second
&lt;/h2&gt;

&lt;p&gt;This sounds obvious in retrospect. It wasn't obvious to me at the time.&lt;/p&gt;

&lt;p&gt;I'm not a designer. I can look at something and know whether it works, but I struggle to build the underlying system that makes it consistent. What I needed wasn't something to generate components. I needed something that would push back when my decisions were incoherent.&lt;/p&gt;

&lt;p&gt;So I started with tokens: background colors, text hierarchy, border values, a single accent color (Akane red, &lt;code&gt;#C91F37&lt;/code&gt;). I set strict rules. The AI started flagging things I wouldn't have caught on my own: too many color variations, inconsistent visual language, patterns that looked fine in isolation but clashed in context.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Too many colors. Pick a few that work together and stick to them."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That kind of feedback is what a designer gives you. I was getting it from an AI, but only because I'd asked the right questions and &lt;strong&gt;constrained the scope&lt;/strong&gt;. Working on one isolated part of the project at a time, not the whole thing, made the feedback loops tight and the outcomes predictable.&lt;/p&gt;

&lt;p&gt;The result was a design system I actually understood. Building the site on top of it was a completely different experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The voice input accident
&lt;/h2&gt;

&lt;p&gt;Somewhere in the middle of the second rebuild, I started using voice input.&lt;/p&gt;

&lt;p&gt;I didn't plan to. I'd seen someone mention it in a video, tried it out of curiosity, and kept using it because it worked better than I expected.&lt;/p&gt;

&lt;p&gt;The difference isn't speed. Typing is faster for precise, short instructions. The difference is &lt;strong&gt;how you think&lt;/strong&gt;. When you speak, you reason out loud. You self-correct in real time. You catch yourself saying "actually, no, wait, what I really mean is..." and that process of rephrasing turns out to be genuinely useful.&lt;/p&gt;

&lt;p&gt;I started with the voice input built into Claude, then experimented with other tools. The prompts I produced with voice were longer, more specific, and more honest. I said things I wouldn't have bothered typing.&lt;/p&gt;

&lt;p&gt;I'm not claiming it's some revolutionary technique. It's just a different input method that, for me, unlocked a different way of communicating with the AI. Your mileage may vary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result: a terminal that works
&lt;/h2&gt;

&lt;p&gt;The site is live at &lt;a href="https://davideimola.dev" rel="noopener noreferrer"&gt;davideimola.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5stdqgqdogyyox2gqi9f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5stdqgqdogyyox2gqi9f.webp" alt="The current davideimola.dev — terminal aesthetic, dark design system, character" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thing I'm most happy with is the &lt;strong&gt;terminal aesthetic&lt;/strong&gt;: commands as navigation, monospace headings, a design language that feels like it was made by a developer, not by someone using a portfolio template. That direction came from the AI noticing what I kept gravitating toward and suggesting something more committed.&lt;/p&gt;

&lt;p&gt;It has character now. The old site didn't.&lt;/p&gt;

&lt;p&gt;The tech stack is Next.js, Tailwind v4, TypeScript. Everything I'd have chosen anyway, but this time built on a design system that makes the codebase coherent instead of a collection of decisions made in different moods.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The rebuild was the experiment. What I took away from it was a method: a way of working with AI that I've since applied to other projects.&lt;/p&gt;

&lt;p&gt;I extracted that method into something replicable. That's the next post.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>2023 Retrospective</title>
      <dc:creator>Davide Imola</dc:creator>
      <pubDate>Thu, 28 Dec 2023 10:18:00 +0000</pubDate>
      <link>https://dev.to/playfulprogramming/2023-retrospective-5fd2</link>
      <guid>https://dev.to/playfulprogramming/2023-retrospective-5fd2</guid>
      <description>&lt;p&gt;Here it is, the end of 2023. What a year it has been. I've been thinking about what I want to do in 2024, but before I do that, I want to take a look back at 2023.&lt;/p&gt;

&lt;p&gt;I am writing this retrospective for the second year in a row. I think it is a good way to reflect on the year and to think about what I want to do in the next year.&lt;/p&gt;

&lt;p&gt;I am going to use more or less the same format as &lt;a href="//./2022-retrospective"&gt;last year&lt;/a&gt;, but I am going to add a few more sections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check-in on 2023 Goals&lt;/li&gt;
&lt;li&gt;Business&lt;/li&gt;
&lt;li&gt;Health&lt;/li&gt;
&lt;li&gt;Friends &amp;amp; Family&lt;/li&gt;
&lt;li&gt;Community, Networking, and Speaking&lt;/li&gt;
&lt;li&gt;Learning&lt;/li&gt;
&lt;li&gt;Hobbies

&lt;ul&gt;
&lt;li&gt;Series and Films&lt;/li&gt;
&lt;li&gt;Music&lt;/li&gt;
&lt;li&gt;Games&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Travel&lt;/li&gt;

&lt;li&gt;2024&lt;/li&gt;

&lt;li&gt;Conclusions&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check-in on 2023 Goals
&lt;/h2&gt;

&lt;p&gt;I had a few goals for 2023. Let's see how I did. I am going to put a ✅ if I did it, a ❌ if I did not do it, and a 🟡 if I did it, but not as much as I wanted.&lt;/p&gt;

&lt;p&gt;For each goal, I am going to write a few words about it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Eat healthier and try to lose some weight 🥒
I did not do this. I did not eat healthier, and I did not lose any weight. I tried for a while, but I did not stick with it. I am going to try again in 2024, but for sure I need to go to an expert to help me with this.&lt;/li&gt;
&lt;li&gt;✅ Stay healthy (you don't say) 👨🏻‍
For sure I can improve this by reducing my weight, but I think I did a good job this year. I did not get sick, and I did not have any injuries.&lt;/li&gt;
&lt;li&gt;🟡 Read more books 📚
I read more books than last year, but I did not read as many as I wanted. I would like to read more in 2024.&lt;/li&gt;
&lt;li&gt;✅ Improve my Skills (Golang, Kubernetes)👨🏻‍💻
This thing is based on my feelings, but I think I improved my skills this year quite a lot. I have not been certified in Kubernetes yet, but I am working on it.&lt;/li&gt;
&lt;li&gt;🟡 Start learning Rust 🦀 
I started learning Rust, but I did not do it as much as I wanted. I am going to continue learning it in 2024.&lt;/li&gt;
&lt;li&gt;✅ Finish the work in the house and move in with Sara 🏡 
We did it! We finished the work in the house, and we moved in. We are very happy with the result. We still have some things to do, but we are living in the house, and we are very happy.&lt;/li&gt;
&lt;li&gt;✅ Make a long trip (January is coming… Emirates-Qatar-Oman… 🛳️🤫) 
I went in a cruise in January in Emirates, and it was amazing. I am going to write a more detailed thing in the travel section.&lt;/li&gt;
&lt;li&gt;✅ Try to organize something incredible for the community ❤️ 
I organized the &lt;a href="https://2023.osday.dev/" rel="noopener noreferrer"&gt;Open Source Day 2023&lt;/a&gt;, and it was amazing. I am going to write a more detailed thing in the community section.&lt;/li&gt;
&lt;li&gt;✅ Have a speech at a conference or meetup 
Oh yes! I did it!!! I had the pleasure to speak to 5 different conferences. I am going to write a more detailed thing in the speaking section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end, I am happy with what I did. I did not do everything I wanted, but I did a lot of things. I am going to try to do better in 2024.&lt;/p&gt;

&lt;p&gt;Of course, my greatest delusion is not losing weight. But, as I already said, I am going to try again in 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business
&lt;/h2&gt;

&lt;p&gt;Pretty much the same as last year. I am still working at &lt;a href="https://www.redcarbon.ai/" rel="noopener noreferrer"&gt;RedCarbon&lt;/a&gt;, and I am still very happy with it.&lt;/p&gt;

&lt;p&gt;My role is still the same, but I am doing a lot more things. I am working on a lot of different projects, and I am learning a lot of new things.&lt;/p&gt;

&lt;p&gt;My commitment is always at my best, and I am truly happy that is recognized by my colleagues and my boss. In fact, I got a promotion and a bonus this year, and I am very proud of myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Health
&lt;/h2&gt;

&lt;p&gt;As the previous year, I need to focus more on this. I am not eating healthier, and I am not doing so much exercises.&lt;/p&gt;

&lt;p&gt;I know that my actual status is not so good, a lot of friends told me that, and I know that I need to change it. I am going to try to do better in 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friends &amp;amp; Family
&lt;/h2&gt;

&lt;p&gt;I moved in with Sara (my girlfriend), and we are very happy. We are still working on the house, but we are living in it, and we are very happy. Finally, I can invite my friends to my house, where we can play board games, watch series, and have fun.&lt;br&gt;
I am still in touch with my friends, I'm usually hanging out with them once a week, and I am very happy about it. Also, I made some new friends by organizing the Open Source Day and going to conferences.&lt;/p&gt;

&lt;p&gt;In conclusion, I can say that I am pretty satisfied with this part of my life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community, Networking, and Speaking
&lt;/h2&gt;

&lt;p&gt;Here it is, one of the newest additions to the retrospective. I am going to talk about the community, networking, and speaking.&lt;br&gt;
2023 was game-changing for me. I started to be more active in the community, and I started to speak at conferences.&lt;/p&gt;

&lt;p&gt;First of all, I have been one of the organizers for the &lt;a href="https://2023.osday.dev/" rel="noopener noreferrer"&gt;Open Source Day 2023&lt;/a&gt;. It was a great experience, and I am very happy about the outcome. I met a lot of new people, and I learned a lot of new things. I am going to organize it with the SH folks again in &lt;a href="https://2024.osday.dev" rel="noopener noreferrer"&gt;2024&lt;/a&gt;, and I am going to try to make it even better.&lt;/p&gt;


&lt;blockquote&gt; &lt;a href="https://www.instagram.com/p/CqVxKwnoWTB/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;       Visualizza questo post su Instagram            &lt;/a&gt;&lt;p&gt;&lt;a href="https://www.instagram.com/p/CqVxKwnoWTB/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;Un post condiviso da Davide Imola (@davideimola)&lt;/a&gt;&lt;/p&gt;


&lt;/blockquote&gt; 

&lt;p&gt;Second, I had the courage to start proposing talks, and unexpectedly, I got accepted to speak at 4 different conferences. I am going to list them here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/wli1Vv9f_uw?si=LA0T7njnt-MCskMS&amp;amp;t=11030" rel="noopener noreferrer"&gt;DevOps Day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2023.incontrodevops.it/" rel="noopener noreferrer"&gt;Incontro DevOps Italia 2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2023.devsecopsday.it/" rel="noopener noreferrer"&gt;DevSecOps Day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://golab.io/past-editions/2023" rel="noopener noreferrer"&gt;GoLab 2023&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a great achievement for me. By speaking at these conferences, I met a lot of new people, and I learned a lot of new things. I am going to try to speak at more conferences in 2024.&lt;/p&gt;


&lt;blockquote&gt; &lt;a href="https://www.instagram.com/p/CpxsFq2INBr/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;       Visualizza questo post su Instagram            &lt;/a&gt;&lt;p&gt;&lt;a href="https://www.instagram.com/p/CpxsFq2INBr/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;Un post condiviso da Davide Imola (@davideimola)&lt;/a&gt;&lt;/p&gt;


&lt;/blockquote&gt; 

&lt;p&gt;Third, I have finally organized a few meetups in my hometown. Probably, this was one of the most beautiful things I did in 2023. The reasons are pretty simple. Verona is not a big city, and it is not easy to find meetups here. I wanted to change this, and I did it! I organized 3 meetups in 2023, thanks to AQuest, and I am going to organize more in 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning
&lt;/h2&gt;

&lt;p&gt;Learning is one of the most important things for me. I am always trying to learn new things, and I am always trying to improve my skills.&lt;br&gt;
This year is no different. I started to learn Rust, and I am going to continue learning it in 2024.&lt;/p&gt;

&lt;p&gt;I also started to increase my knowledge about Kubernetes and Go, and finally I can say that I am pretty confident with them. Of course, I still have a lot to learn!&lt;/p&gt;

&lt;p&gt;This year I also understand that I'd like to learn more about security, Artificial Intelligence, and mobile development. I am going to try to learn more about these topics in 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hobbies
&lt;/h2&gt;

&lt;p&gt;For this year retro, I'd like to give more space to my hobbies. I think it is important to talk about them, and I think it is important to write about them, so people can know me better.&lt;br&gt;
I am going to talk a little more deeply about series and films, music, and games.&lt;/p&gt;

&lt;h3&gt;
  
  
  Series and Films
&lt;/h3&gt;

&lt;p&gt;Ok let's start with series and films. Starting from the series, I watched a lot of them, and I am going to list a few of them here, in no particular order like an honorable mention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt14452776" rel="noopener noreferrer"&gt;The Bear&lt;/a&gt;: What is this one?! Frustration, anger, stress, and a lot of other feelings. This is a short series, and it is very well done. It is a great cooking serie, but not only that. I am not going to spoil anything, but I can say that it is worth watching it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt13207736/" rel="noopener noreferrer"&gt;DAHMER - Monster&lt;/a&gt;: This is a great series. It is about the life of Jeffrey Dahmer, a very famous serial killer. It's not easy to watch it, but it is very well done.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt4955642/" rel="noopener noreferrer"&gt;The Good Place&lt;/a&gt;: This is a comedy series, and it is very funny. I really enjoyed it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's talk about films. I really love going to the cinema, and I really love watching films. I watched a lot of films this year, and I am going to list a few of them here, in no particular order like an honorable mention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt15398776/" rel="noopener noreferrer"&gt;Oppenheimer&lt;/a&gt;: Christopher Nolan. I should not say anything else. One of the best biographical films I have ever seen. Through the life of J. Robert Oppenheimer, we can see the story of the atomic bomb. I do love the way the film brings you some very deep thoughts.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt1517268/" rel="noopener noreferrer"&gt;Barbie&lt;/a&gt;: I went to the cinema with my girlfriend to watch this film, but I did not expect it to be so good. Fun but also deep. I really enjoyed it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt1745960/" rel="noopener noreferrer"&gt;Top Gun: Maverick&lt;/a&gt;: I like the first Top Gun, so I watched this sequel. In my opinion, this is better than the first one.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt11138512/" rel="noopener noreferrer"&gt;The Northman&lt;/a&gt;: I recovered this film this year. For sure, I can say I am a huge fan of the Vikings mythology, and this film does not disappoint me. I really enjoyed it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.imdb.com/title/tt1490017/" rel="noopener noreferrer"&gt;The Lego Movie&lt;/a&gt;: Another film that I recovered this year. I do love Lego, but I have always thought that this film was for kids. I was wrong. It is a very funny film with a lot of references to other series or films!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Music
&lt;/h3&gt;

&lt;p&gt;I think one image may be enough to describe my music taste. My Spotify Wrapped 2023:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvj986ils3axtmsm3zss.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvj986ils3axtmsm3zss.jpeg" alt="Spotify Wrapped" width="665" height="1182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Games
&lt;/h3&gt;

&lt;p&gt;From my childhood, I have always loved playing games. From video games to board games, I love them all. Unfortunately, I am not very consistent with them. I play a lot for a few months, and then I stop playing for a few months.&lt;br&gt;
This year, also because of the house renovation, I did not have a lot of time to play, but I played a few games that I really enjoyed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://zelda.nintendo.com/tears-of-the-kingdom/" rel="noopener noreferrer"&gt;The Legend of Zelda: Tears of the Kingdom&lt;/a&gt;: Breath of the Wild is one of my favorite games ever, so I was very excited to play this sequel. I pre-ordered it as soon as I could, and I played from the Day One. Unfortunately, I did not have the time to finish it, but I am really enjoining it. If BOTW was good, this one is even better.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hogwartslegacy.com/" rel="noopener noreferrer"&gt;Hogwarts Legacy&lt;/a&gt;: I am a huge fan of Harry Potter, and I am a huge fan of RPG games. This game is a dream come true for me. I can not explain the feelings I had when I saw for the first time Hogwarts in this game. Probably, is not the best game ever, but I can not be objective with this one. I love it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amzn.eu/d/8Ro9N2w" rel="noopener noreferrer"&gt;Munchkin&lt;/a&gt;: As I said earlier, I also love bord games. I played a few of them this year, but I'd like to mention Munchkin. It is a very fun game to play with friends.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://it.wikipedia.org/wiki/S%C3%AC,_Oscuro_Signore!" rel="noopener noreferrer"&gt;Si, Oscuro Signore!&lt;/a&gt;: This is an Italian game, and it is very fun to play with friends. This is a very interesting game because you have to create stories and characters. It is like a Dungeons and Dragons, but far more simple. You don't need a game master, and you don't need to create a character. You just need to play as a Goblin or the Dark Overlord.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amzn.eu/d/7NttaGo" rel="noopener noreferrer"&gt;Ka-Blab&lt;/a&gt;: This is one of my latest purchases. I got it quite by accident, as I was looking for a simple game to play with a few friends. It is a very simple game, but it is very fun to play.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Travel
&lt;/h2&gt;

&lt;p&gt;Finally, I can talk about travel. I love traveling, and I love visiting new places. This year I did not travel a lot, but I did a few things.&lt;br&gt;
I had a cruise in January, and it was amazing. I visited a lot of new places!&lt;/p&gt;

&lt;p&gt;I visited Dubai and Abu Dhabi in the Emirates. I went to the top of the Burj Khalifa, and it wasn't so amazing, because I suffer from vertigo. 🤦🏻‍&lt;br&gt;
I also visited the Sheikh Zayed Grand Mosque, and it was by far one of the most luxurious places I have ever seen.&lt;/p&gt;


&lt;blockquote&gt; &lt;a href="https://www.instagram.com/p/CoCqiiPI__r/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;       Visualizza questo post su Instagram            &lt;/a&gt;&lt;p&gt;&lt;a href="https://www.instagram.com/p/CoCqiiPI__r/?utm_source=ig_embed&amp;amp;utm_campaign=loading" rel="noopener noreferrer"&gt;Un post condiviso da Davide Imola (@davideimola)&lt;/a&gt;&lt;/p&gt;


&lt;/blockquote&gt; 

&lt;p&gt;Everything is so big there. Including the malls. They also have a ski slope inside a mall. It is crazy!&lt;/p&gt;

&lt;p&gt;In the same cruise, I also visited Doha in Qatar. Qatar was the country that hosted the 2022 FIFA World Cup, so I found a lot of things still related to football. As I understood from the locals, Doha is a very modern city, and it is growing very fast. So I think it is worth visiting it again in a few years.&lt;/p&gt;

&lt;p&gt;Last, but not least, I visited Muscat in Oman. Unfortunately, I did not have a lot of time to visit it, but I really enjoyed it. I visited the Sultan Qaboos Grand Mosque, and it was amazing. I also visited the Mutrah Souq, and it was very interesting.&lt;/p&gt;

&lt;p&gt;In this year, I also visited a few places in Italy. I visited Trento and Arco in Trentino Alto Adige. I have also been three times in Florence this year, I can say I am starting to know it very well!&lt;/p&gt;

&lt;p&gt;Udine, or better, San Daniele del Friuli (for the ham) and Torino, are the other two cities I visited this year. I don't have a lot to say about them, but I would like to mention one thing about Torino. The Egyptian Museum, I had the opportunity to visit it with a guide thanks to my company. It is something out of this world. It gives me the chills just thinking about it. I can not explain the feelings I had when I saw the mummies. I think it is something that everyone should see at least once in their life. Now, I would like to return in Egypt to see the pyramids and the other things I did not have the opportunity to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  2024
&lt;/h2&gt;

&lt;p&gt;I think I have talked enough about 2023. Now, it is time to look forward to 2024. As the last year, I want to set some goals for the next year.&lt;br&gt;
For the next year, I want to focus more on my health and my hobbies. I know, as a developer, it is not easy to find the time to do everything, but I think it is important to find the time to do the things you love.&lt;/p&gt;

&lt;p&gt;So, let's start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I want to lose weight. I know it is not easy, but I want to try. I want to start going to walk more and try to eat better.&lt;/li&gt;
&lt;li&gt;My girlfriend has gifted me a board game of the Lord of the Rings. I want to organize a good campaign with my friends!&lt;/li&gt;
&lt;li&gt;Start playing at role-playing games again. But this time, I want to do it more consistently.&lt;/li&gt;
&lt;li&gt;I want to organize more and more meetups in Verona!&lt;/li&gt;
&lt;li&gt;I want to learn more about security, Artificial Intelligence, and mobile development.&lt;/li&gt;
&lt;li&gt;I would love to organize a better event for the next Open Source Day!&lt;/li&gt;
&lt;li&gt;Create more content for my blog, YouTube channel, and Twitch channel.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;And that's all. It seems that only yesterday I was writing the 2022 retrospective, and now I am writing the 2023 one. Time flies, and I think it is important to stop and think about what you have done in the last year. I think it is important to think about what you have done, and what you want to do in the next year.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading this post, and I hope you will have a great 2024!&lt;/p&gt;

</description>
      <category>retrospective</category>
      <category>2024</category>
      <category>2023</category>
      <category>yearinreview</category>
    </item>
    <item>
      <title>Level UP your RDBMS Productivity in GO</title>
      <dc:creator>Davide Imola</dc:creator>
      <pubDate>Tue, 05 Dec 2023 13:30:00 +0000</pubDate>
      <link>https://dev.to/playfulprogramming/level-up-your-rdbms-productivity-in-go-51eo</link>
      <guid>https://dev.to/playfulprogramming/level-up-your-rdbms-productivity-in-go-51eo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: All the things in this article are highly opinionated, and they are not a standard. I'm just sharing my experience and what I think is the best way to do it.&lt;br&gt;
If you have a better way to do it, please let me know in the comments. Examples are in PostgreSQL, but you can use the same approach for MySQL, SQLite, etc.&lt;/p&gt;

&lt;p&gt;No DB have been harmed in the making of this article, but a couple was truncated. 🤫&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Let's start with the actual status
&lt;/h2&gt;

&lt;p&gt;Handling SQL DataBases in GO, as for different languages, can bring a lot of pain and frustration.&lt;/p&gt;

&lt;p&gt;We may encounter a lot of problems, like:&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling the DB Code
&lt;/h3&gt;

&lt;p&gt;For sure, you have seen a lot of code like this:&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rows&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users"&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;rows&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn't it beautiful? ?? Let's be honest! It's not! Who loves writing again and again all this code? I don't!&lt;/p&gt;

&lt;p&gt;No, Copilot (or any generative AI 🤖) is not the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding hidden errors in SQL
&lt;/h3&gt;

&lt;p&gt;We may have a lot of errors in our SQL code that we can't find until we run the code.&lt;/p&gt;

&lt;p&gt;Let's play! Can you find the error in this code? If, yes write it in the comments.&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rows&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM upsers"&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;rows&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQL Injection
&lt;/h3&gt;

&lt;p&gt;Security? What is that? 🤔 Let's take this code as example:&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM users WHERE id = %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;User&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;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, we are using &lt;code&gt;fmt.Sprintf&lt;/code&gt; to build our query. This is a very bad practice because we are exposing ourself to SQL Injection.&lt;/p&gt;

&lt;p&gt;SQL Injection is a code injection technique that might destroy your database. It is one of the most common web hacking techniques.&lt;/p&gt;

&lt;p&gt;For example, in this case, if the user pass as &lt;code&gt;id&lt;/code&gt; the value &lt;code&gt;1 OR 1=1&lt;/code&gt; the query will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&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="k"&gt;OR&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this will return all the users in the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code and Database Synchronization
&lt;/h3&gt;

&lt;p&gt;Maintaining synchronization between code and the database schema is critical to avoid runtime errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rows&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM upsers"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a new column in the database without updating the code could lead to errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual type sync and possible downtimes
&lt;/h3&gt;

&lt;p&gt;Doing things manually is always a bad idea. Because we are humans and we make mistakes.&lt;/p&gt;

&lt;p&gt;And if we are going to do things manually, we may have some downtime in our application. Because we need to stop the application, run the migration, and then start the application again.&lt;/p&gt;

&lt;p&gt;This is not a good idea, especially if we have a lot of users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated tests with DB (Why mocking is not a good idea)
&lt;/h3&gt;

&lt;p&gt;When performing unit tests, we are always going to mock the DB, because we don't want to bring the DB up and down for every test.&lt;/p&gt;

&lt;p&gt;But, mocking the DB is not a good idea, because we are not testing the real code. We are testing a fake code that we wrote.&lt;/p&gt;

&lt;p&gt;So, if we have a bug in our SQL code, we are not going to find it, until we are gonna run it somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can we do?
&lt;/h2&gt;

&lt;p&gt;Ok, we have seen a lot of problems, but what can we do to solve them? 🤔&lt;/p&gt;

&lt;p&gt;In this article, we are gonna see how to solve all these problems with the help of some tools and paradigms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL-first approach&lt;/li&gt;
&lt;li&gt;Migrations&lt;/li&gt;
&lt;li&gt;Test containers (or Docker test)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SQL-first approach
&lt;/h2&gt;

&lt;p&gt;The SQL-first approach is a paradigm that focuses on writing the SQL code first and then generate the code.&lt;/p&gt;

&lt;p&gt;This approach is very useful because we are gonna focus on the SQL code and not how to handle it inside the code.&lt;/p&gt;

&lt;p&gt;There are other approaches which you can use, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ORM (Object Relational Mapping)&lt;/li&gt;
&lt;li&gt;Query Builders&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ORM
&lt;/h3&gt;

&lt;p&gt;ORM is a programming technique that enables a seamless conversion between data stored in a relational database table to an object-oriented programming language.&lt;/p&gt;

&lt;p&gt;So you are going to create a code like the following:&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="c"&gt;// Read&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// find product with integer primary key&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"code = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"D42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// find product with code D42&lt;/span&gt;

&lt;span class="c"&gt;// Update - update product's price to 200&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// Update - update multiple fields&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Updates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"F42"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c"&gt;// non-zero fields&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't like so much, not ony because I think the APIs built for Go are not ugly, but you are not writing SQL code, you are writing code that is going to generate SQL code. Also, you can't use all the features of the DB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Builders
&lt;/h3&gt;

&lt;p&gt;Query Builders are tools or libraries that provide a way to programmatic and or fluent way to create SQL queries.&lt;/p&gt;

&lt;p&gt;For example, you can write code like this:&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;users&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"emails USING (email_id)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"deleted_at"&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;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&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;active&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToSql&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users JOIN emails USING (email_id) WHERE deleted_at IS NULL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with this approach is that you don't generate type-safe code. You are just generating a string that you are going to pass to the DB.&lt;/p&gt;

&lt;p&gt;So, you still need to map your data and maintains all the types.&lt;/p&gt;

&lt;p&gt;Also, just for the record, I don't like the syntax of this library. I think it's not so readable. Because, you are mixing the SQL code with the Go code.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL-first approach vs ORM vs Query Builders
&lt;/h3&gt;

&lt;p&gt;I think the SQL-first approach is the best approach because you are writing SQL code and you are generating type-safe code.&lt;/p&gt;

&lt;p&gt;Also, you can use all the features of the DB, like JSONB filtering, etc.&lt;/p&gt;

&lt;p&gt;So I have made this table to compare the different approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SQL-first&lt;/th&gt;
&lt;th&gt;ORM&lt;/th&gt;
&lt;th&gt;Query Builders&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type-safe&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All DB features&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protect you from SQL Injection&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean API&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (in GO)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code generation&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I like it&lt;/td&gt;
&lt;td&gt;✅✅✅✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Use a mixed approach
&lt;/h3&gt;

&lt;p&gt;The best thing you can do is to use a mixed approach. You can use the SQL-first approach for the most common queries and then use the ORM or Query Builders for the rest.&lt;/p&gt;

&lt;p&gt;Because, not all the queries are the same. Some queries are very simple and you don't need to write a lot of code, but some queries are very complex and you need to write a lot of code.&lt;/p&gt;

&lt;p&gt;Also they may change during the execution depending to different factors. So, an SQL-first approach is not the best solution in this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrations
&lt;/h2&gt;

&lt;p&gt;Migrations are a way to keep your DB schema in sync with your code. They are very useful because you can keep track of all the changes you made to the DB.&lt;/p&gt;

&lt;p&gt;Also, you can use them to create the DB schema from scratch.&lt;/p&gt;

&lt;p&gt;The migrations consists of 2 parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up - The code that is going to be executed when you are going to run the migration&lt;/li&gt;
&lt;li&gt;Down - The code that is going to be executed when you are going to rollback the migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, let's say that we want to create a table called &lt;code&gt;users&lt;/code&gt; with the following schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- migrate:up&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- migrate:down&lt;/span&gt;

&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrations are usually stored inside a directory within the source code and they are named with a timestamp and a name.&lt;/p&gt;

&lt;p&gt;They can be executed in 2 ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually: You can run the migration manually with a CLI&lt;/li&gt;
&lt;li&gt;Automatically: You can run the migration automatically when the application starts

&lt;ul&gt;
&lt;li&gt;By running the migration inside the code&lt;/li&gt;
&lt;li&gt;By running the migration through a Job or a CronJob&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evolutionary Database Design
&lt;/h3&gt;

&lt;p&gt;Evolutionary Database Design is a technique that allows you to evolve your database schema in a simple and agile way.&lt;/p&gt;

&lt;p&gt;The idea is to create a migration for every change you make to the DB schema. So, you can keep track of all the changes you made.&lt;/p&gt;

&lt;p&gt;If you want to add a breaking change, you must introduce it in multiple steps. Because, you can't break the application.&lt;/p&gt;

&lt;p&gt;If you want to learn more about this technique, I suggest you to read the following article at this &lt;a href="https://martinfowler.com/articles/evodb.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test containers (or Docker Test)
&lt;/h2&gt;

&lt;p&gt;Of firstly we have talked about the problems of mocking the DB. So, how can we test our code without mocking the DB?&lt;/p&gt;

&lt;p&gt;The answer is simple: &lt;strong&gt;Test containers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Test containers are a way to run a real DB instance inside a container and then run the tests against it. So, we are going to test the real implementation of the code.&lt;/p&gt;

&lt;p&gt;For example, let's say that we want to test a code which is going to interact with a DB.&lt;/p&gt;

&lt;p&gt;With test containers, we can run a real DB instance inside a container and then run the tests against it.&lt;/p&gt;

&lt;p&gt;There's no magic here. We are just running a real DB in a "Dockerized" environment. So, you are sure that the code is working as expected where it's gonna run.&lt;/p&gt;

&lt;p&gt;Also, you can run the tests in parallel, because you are not sharing the DB instance with other tests.&lt;/p&gt;

&lt;p&gt;The best thing is that you can run the tests in your CI/CD pipeline. So, you are sure that the code is working as expected. You must simply have a Docker environment.&lt;/p&gt;

&lt;p&gt;This thing does not apply only to the DB, but to all the external services you are using in your application, like Redis, Kafka, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's code
&lt;/h2&gt;

&lt;p&gt;Ok, now that we have seen the theory, let's see how to do it in practice.&lt;/p&gt;

&lt;p&gt;For the purpose of this article, we are going to set up a simple application that is going to handle users.&lt;/p&gt;

&lt;p&gt;The application is going to expose the following proto service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;UsersService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateUserRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateUserResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListUsersRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListUsersResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;DeleteUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeleteUserRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeleteUserResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetUserRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetUserResponse&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;I have decided to use &lt;a href="https://grpc.io/" rel="noopener noreferrer"&gt;gRPC&lt;/a&gt; because it's a very simple protocol and it's very easy to use.&lt;/p&gt;

&lt;p&gt;So, let's start with the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the schema
&lt;/h3&gt;

&lt;p&gt;The first thing we are going to do is to create the schema of the DB.&lt;/p&gt;

&lt;p&gt;As we want to maintain the track of our changes to the DB, we are going to use migrations. In this case, we are going to use &lt;a href="https://github.com/amacneil/dbmate" rel="noopener noreferrer"&gt;dbmate&lt;/a&gt;. But, you can use any other tool you want.&lt;/p&gt;

&lt;p&gt;So, let's create the first migration by performing the following commands in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbmate n init_users_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to create a new migration file called &lt;code&gt;XXXXXXXXXXXXX_init_users_table.sql&lt;/code&gt;, where &lt;code&gt;XXXXXXXXXXXXX&lt;/code&gt; is a timestamp.&lt;/p&gt;

&lt;p&gt;Now, let's open the file and write the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- migrate:up&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- migrate:down&lt;/span&gt;

&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, we have created a table called &lt;code&gt;users&lt;/code&gt; with the following columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; - The ID of the user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; - The name of the user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; - The creation date of the user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updated_at&lt;/code&gt; - The update date of the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's run the migration by creating a &lt;code&gt;.env&lt;/code&gt; file with the environment variable with the DB connection string, and after performing the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbmate up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to create the table in the DB and a schema file which is going to be used by the code generator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate the code
&lt;/h3&gt;

&lt;p&gt;Now, we are going to generate the code. For this purpose, we are going to use &lt;a href="https://sqlc.dev" rel="noopener noreferrer"&gt;sqlc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all we need to create a sqlc configuration file called &lt;code&gt;sqlc.yaml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql"&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal/queries/"&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db/migrations/"&lt;/span&gt;
    &lt;span class="na"&gt;gen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;go&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queries"&lt;/span&gt;
        &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal/queries/"&lt;/span&gt;
        &lt;span class="na"&gt;sql_package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pgx/v5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to tell sqlc where to find the queries and the schema files, and where to generate the code.&lt;/p&gt;

&lt;p&gt;Now, let's create the queries file called &lt;code&gt;internal/queries/users.sql&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- name: ListUsers :many&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="n"&gt;sqlc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'limit'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="n"&gt;sqlc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'offset'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- name: CountUsers :one&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- name: CreateUser :one&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&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="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- name: DeleteUser :one&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- name: GetUser :one&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, we have created the queries we need to handle the users. We have also added some arguments to the queries.&lt;/p&gt;

&lt;p&gt;Now, let's generate the code by performing the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlc generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to generate the code inside the &lt;code&gt;internal/queries&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Going back to the implementation of the service we are going to import the generated package and by using the generated &lt;code&gt;Queries&lt;/code&gt; struct.&lt;/p&gt;

&lt;p&gt;For example, let's say that we want to implement the &lt;code&gt;ListUsers&lt;/code&gt; method. We are going to write the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queries&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;NewUsersService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;usersv1connect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UsersServiceHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;connect_go&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;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsersRequest&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="n"&gt;connect_go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsersResponse&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;users&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsersParams&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tot&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CountUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;res&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&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;connect_go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsersResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Totat&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tot&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, we are using the generated &lt;code&gt;Queries&lt;/code&gt; struct to perform all the queries we need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the tests
&lt;/h3&gt;

&lt;p&gt;Now, let's run the tests. For this purpose, we are going to use &lt;a href="https://github.com/ory/dockertest" rel="noopener noreferrer"&gt;dockertest&lt;/a&gt;, but test containers is also a good solution.&lt;/p&gt;

&lt;p&gt;First of all, we need to configure the Postgres container, in dockertest, first of all we need to create a &lt;code&gt;Pool&lt;/code&gt; and creating the resource.&lt;/p&gt;

&lt;p&gt;The resource can be internally expose py performing a port mapping. In this case, we are going to expose the port &lt;code&gt;5432/tcp&lt;/code&gt; as we are working with Postgres, dockertest is going to find a free port and expose it.&lt;/p&gt;

&lt;p&gt;The container can be configured by passing environment variables, arguments, etc.&lt;/p&gt;

&lt;p&gt;As the code is a bit long, I'm not going to paste it here, but you can find it &lt;a href="https://github.com/davideimola/rdbms-productivity-in-go/blob/68c979eadb608e7e5c29dd075c142262e94a3ca0/internal/testutils/pg.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's write the actual test. Firstly we need to perform the &lt;code&gt;InitPostgres&lt;/code&gt; function to initialize the Postgres container inside the &lt;code&gt;TestMain&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Then, we need to create a new &lt;code&gt;Queries&lt;/code&gt; struct by passing the connection string to the &lt;code&gt;NewUsersService&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Now, we can perform the tests. For example, let's say that we want to test the &lt;code&gt;ListUsers&lt;/code&gt; method. We are going to write the following code:&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;TestListEmptyUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&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;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsersRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Offset&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;Limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;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;usersCli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&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;t&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;"Could not list users: %s"&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;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nil&lt;/span&gt;&lt;span class="p"&gt;(&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&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;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Totat&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;By running the test, we are going to see that the test is passing!&lt;/p&gt;

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

&lt;p&gt;In this article, we have seen how to improve our RDBMS productivity in GO. We have seen how to use the SQL-first approach, migrations, and test containers.&lt;/p&gt;

&lt;p&gt;We have also seen the benefits of using these tools and how to use them in a real application.&lt;/p&gt;

&lt;p&gt;If you want to see the full code, you can find it &lt;a href="https://github.com/davideimola/rdbms-productivity-in-go" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this article. If you have any questions, please let me know in the comments.&lt;/p&gt;

</description>
      <category>go</category>
      <category>rdbms</category>
      <category>sql</category>
      <category>db</category>
    </item>
    <item>
      <title>Securing Secrets in the Age of GitOps</title>
      <dc:creator>Davide Imola</dc:creator>
      <pubDate>Fri, 27 Oct 2023 09:07:21 +0000</pubDate>
      <link>https://dev.to/playfulprogramming/securing-secrets-in-the-age-of-gitops-2478</link>
      <guid>https://dev.to/playfulprogramming/securing-secrets-in-the-age-of-gitops-2478</guid>
      <description>&lt;p&gt;Kubernetes and GitOps offer a powerful way to manage your infrastructure and applications. However, when it comes to securing sensitive information like passwords, tokens, and certificates, challenges arise. In this article, we'll explore different methods to secure secrets in the GitOps era and how to seamlessly integrate them into your workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of Kubernetes Secrets
&lt;/h2&gt;

&lt;p&gt;Kubernetes provides a dedicated solution for safeguarding sensitive data: Secrets. These are Kubernetes objects designed to securely store information like passwords, OAuth tokens, and SSH keys. Using Secrets is a safer and more versatile approach compared to embedding sensitive data directly into Pod definitions or container images.&lt;/p&gt;

&lt;p&gt;But, as powerful as Secrets are, there's a significant challenge when it comes to managing them within a GitOps workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitOps Conundrum
&lt;/h2&gt;

&lt;p&gt;GitOps revolves around using Git as the single source of truth for declarative infrastructure and applications. With Git at the heart of your delivery pipelines, developers can accelerate application deployments and streamline operations tasks in Kubernetes through pull requests.&lt;/p&gt;

&lt;p&gt;However, when it comes to secrets, things get complicated. Secrets can't be stored directly in a Git repository because the data isn't encrypted; it's merely encoded in &lt;code&gt;base64&lt;/code&gt;. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YmFy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To highlight the security issue with this approach, let's decode the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;YmFy | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, if someone gains access to the Git repository, they can effortlessly decode the data and compromise the secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Sealed Secrets
&lt;/h2&gt;

&lt;p&gt;The solution to this problem is Sealed Secrets, a Kubernetes Custom Resource Definition Controller. It enables the encryption of Secrets, allowing you to store them in Git repositories safely. A Sealed Secret is shareable, even in public repositories, and can be given to colleagues, all while remaining impenetrable. Only the controller running in the target cluster can decrypt the Sealed Secret.&lt;/p&gt;

&lt;p&gt;Using Sealed Secrets is straightforward. First, install the controller in your cluster using a Helm chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
&lt;span class="nv"&gt;$ &lt;/span&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;sealed-secrets sealed-secrets/sealed-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, you can use the &lt;code&gt;kubeseal&lt;/code&gt; CLI to retrieve the public key and encrypt your secrets with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Retrieve the public key&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubeseal &lt;span class="nt"&gt;--fetch-cert&lt;/span&gt; &lt;span class="nt"&gt;--controller-namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sealed-secrets &lt;span class="nt"&gt;--controller-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sealed-secrets &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pub-cert.pem

&lt;span class="c"&gt;# Encrypt a secret&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubeseal &lt;span class="nt"&gt;--cert&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pub-cert.pem &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yaml &amp;lt; mysecret.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mysealedsecret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty of Sealed Secrets is that you can store the sealed secret in a Git repository and apply it to the cluster. The controller will decrypt the secret, creating the original Secret without requiring any changes to your app's Deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring Secrets Managers
&lt;/h2&gt;

&lt;p&gt;Another approach for managing secrets in a GitOps workflow is using a Secrets Manager. These tools offer secure storage and management of secrets and come with numerous features, including encryption, access control, auditing, secret rotation, and more. Notable examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.vaultproject.io/" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/key-vault/" rel="noopener noreferrer"&gt;Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;Google Cloud Secret Manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While Secrets Managers offer robust security, they come with certain drawbacks, such as not being Kubernetes-native, a steep learning curve, costs (except for HashiCorp Vault's open-source version), and complex installation and maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridging the Gap with Secret Store CSI Driver
&lt;/h2&gt;

&lt;p&gt;To incorporate a Secrets Manager seamlessly into your GitOps workflow, consider using the Secret Store CSI Driver. It's a Kubernetes CSI driver that allows you to store and manage secrets in Kubernetes using your preferred Secrets Manager. This Kubernetes-native solution is easy to install and maintain, free, and open source. It supports a variety of Secrets Managers through provider-specific modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hashicorp/secrets-store-csi-driver-provider-vault" rel="noopener noreferrer"&gt;Vault Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.github.io/secrets-store-csi-driver-provider-azure/" rel="noopener noreferrer"&gt;Azure Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp" rel="noopener noreferrer"&gt;GCP Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/secrets-store-csi-driver-provider-aws" rel="noopener noreferrer"&gt;AWS Provider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For detailed guidance on implementing these providers, refer to the &lt;a href="https://secrets-store-csi-driver.sigs.k8s.io/getting-started/getting-started" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leveraging SDKs
&lt;/h2&gt;

&lt;p&gt;Alternatively, you can integrate a Secrets Manager into your GitOps workflow by using SDKs provided by the Secrets Manager. These SDKs are available for various programming languages and can be incorporated into your application code to retrieve secrets. This approach offers flexibility but comes with the need to modify application code, manage SDKs within the application, and control access to the Secrets Manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Right Choice
&lt;/h2&gt;

&lt;p&gt;In this post, we've explored multiple methods for securing secrets in a GitOps workflow. The decision ultimately rests with you, as you choose the solution that best aligns with your specific needs. However, it's crucial to prioritize security in your decision-making process. Choose wisely, and ensure your secrets remain safe and sound.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>security</category>
      <category>git</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
