Skip to main content

Refactoring dns-updater 3.0.0: How Studying DDD Helped Me Write Better Go

844 words·4 mins
Table of Contents
dns-updater - This article is part of a series.
Part 2: This Article

Version 3.0.0 of dns-updater is more than a technical refactor for me. It’s a true visualisation of how I think about software design. I’ve spent quite a lot of time reading the book “Domain-Driven Design: Tackling Complexity in the Heart of Software” by Eric Evans, and it changed how I write Go completely. Not because it makes the codebase smaller or faster to start from scratch, but because everything is neatly organised and not a “Big Ball of Mud”. This post is about that journey: the code improvements, the design wins, and the personal growth behind them.

Why This Release Is Personal
#

Version 3.0.0 of dns-updater is a technical milestone, but for me it is also a personal one. During this refactor, I spent serious time trying to organise the codebase and implement as much of what I’ve learned from the Domain-Driven Design (DDD) book as possible. I didn’t want to “make it work” anymore. I wanted to design software that is easier to understand, easier to evolve, and safer to change.

This release is the result of my newly gained knowledge. The architecture changes are important, but the bigger story is how learning design principles and patterns changed how I write Go and how I think about long-term software quality.

What I Learned from DDD (and Why It Mattered in Go)
#

DDD gave me a better way to reason about complexity. Instead of organising code around what they interacted with, like gcp, file or utils. I started organising around responsibilities and boundaries. Core behaviour in one place, technical integrations in another.

That mindset helped me improve quickly. No more battling import cycles. No more funky function names because the function intent was all over the place. Interfaces became true contracts, dependencies became intentional, and flow through the system became easier to follow. The result was not only better architecture, but also better day-to-day coding decisions.

Where v2 Hurt: Changing Core Behaviour
#

The biggest pain point in v2 was changing core functionality, like adding scheduling and safeguards. Even when the feature itself was pretty straightforward, implementing it without introducing a messy codebase was hard because responsibilities were too coupled. Small changes had a larger impact than expected. Some packages were imported in many places, and the code was not structured to support that kind of change.

Challenges like this slow down development and make it harder to reason about the codebase. Collaboration is also harder without clear boundaries.

How Ports and Adapters Changed the Way I Build Software
#

In v3, moving to ports and adapters made a huge difference. Core logic now sits behind clear interfaces, while providers, IP lookup, cache, and runtime concerns connect via adapters around it. This separation reduced coupling and made changes safer. Want to add a new DNS provider? Sure, pop it right in! Want to change the IP lookup provider? No problem, just swap the adapter. Want to add a new scheduling mechanism? Easy, just implement the interface. Even the flags of enabling or disabling features could be as simple as swapping adapters to a no-op implementation. The core logic would remain untouched, but the behaviour would change.

Scheduling is a good example. Instead of threading concerns through the entire codebase, behaviour can now be composed around the core service. The same applies to provider support and operational features. The architecture is both cleaner and more practical for real change.

Three Career Wins from One Refactor
#

  1. I became better at writing maintainable code, not just functional code.
  2. I deepened my Go knowledge by connecting architecture concepts to concrete implementation work.
  3. I now collaborate better with software architects at work because I understand the terminology and design intent behind architectural decisions, and I understand the impact a platform change can have on a product.

These are long-term wins that extend much farther than this single project.

What Quality Code Means to Me Now
#

Before, I mostly defined success by whether the feature worked or not. Now, I also define it by how confidently I can change it later. For me, quality code means clear boundaries, explicit contracts, and designs that support evolution without constant rewrites.

dns-updater 3.0.0 is the first version where I truly feel that alignment: personal growth, better architecture, and better software delivered together.

Where to Go From Here
#

I’m a platform engineer by heart, and I work on a platform that is used by many teams. Kubernetes, automation, and cloud-native architecture are my daily bread. I want to continue learning and applying DDD principles, coding patterns and architecture concepts to make the software I create more maintainable, more evolvable, and more enjoyable to work with.

I know DDD is not a silver bullet, and it’s not always the right approach. But for me, it has been a transformative experience that has changed how I write Go and how I think about software design. With this new knowledge, I’m getting more confident in when to use certain patterns, when to refactor, and how to communicate design decisions with my team.

dns-updater - This article is part of a series.
Part 2: This Article