
Writing clean Golang code is crucial for scalable and maintainable projects. This means avoiding unnecessary complexity and keeping code organized.
A good starting point is to use the Go standard library, which is designed to be efficient and easy to use. This reduces the likelihood of introducing external dependencies that can make code harder to maintain.
Golang's built-in concurrency features, such as goroutines and channels, can greatly simplify concurrent programming. By using these features, developers can write concurrent code that is easier to reason about and debug.
Proper naming conventions, like using clear and concise variable names, can make a big difference in code readability. This is especially important in Golang, where variable names are often used to convey the purpose of the code.
Recommended read: Golang Variable Naming Convention
Go Code
The implementation pattern shown in many Go packages, including the standard library sync package for the sync.Map type, is actually quite common.
The problem with this implementation is that it's not ideal for all use cases.
In fact, the example of using this package shows that it's not always the best solution.
Expand your knowledge: Golang Package
The Go Highway
The Go Highway is a great way to structure your Go project. It uses the standard Go project structure with /cmd and /internal folders.
In /cmd, the main.go file has very little code, but this is good practice to follow. I've found that keeping the main file simple helps with project organization.
The /internal folder is where you'll find all the project logic, including other folders like /pkg. You can also find the main file, its test file, and its mock for testing, which is a good practice to avoid cyclic dependency problems.
The /domain folder stores the implementation of business rules, containing entities and use cases. You can also find this folder named /core in some projects.
Entities are information in their rawest state and are stored in the /entities folder. They contain data models and some logic that deals directly with them.
Recommended read: Golang Package Structure
Internal/Repo/Webapi
In Go code, internal/repo/webapi is an abstract web API that business logic works with.
It's essentially a separate entity that business logic accesses via the REST API.
This package name can change depending on the purpose it serves in the code.
For example, it could be another microservice that business logic interacts with.
The internal/repo/webapi package is designed to be flexible and adaptable to different use cases.
Code Organization
Clean code establishes trust in the codebase and helps minimize the chances of careless bugs being introduced.
Organizing code effectively is crucial for maintainable software. Package organization is key to this, and it's essential to separate different layers of the code into their own packages.
The shipping example in go-kit is a great illustration of this, where the transport and service were in different packages. This design is convenient in the long run.
A well-organized package structure helps developers maintain their agility, which typically plummets as the codebase expands.
Error Handling
Error handling is a crucial aspect of clean Go code. In cases where custom errors are dynamic, returning a static error variable might not be viable.
The solution is to return a new interface with added functionality, allowing you to check the error type and still call .Error() on it. This approach won't break any existing implementations.
This new data structure still works as a standard error, enabling you to compare it to nil and call .Error() on it. You can then check for a specific error type in your HTTP handler function.
The code can be refactored to check for a specific error, making it safer and more efficient.
Curious to learn more? Check out: Golang Check Type
Go Fundamentals
Go is a statically typed language, which means that the compiler checks the types of variables at compile time, not at runtime.
This allows for better code quality and fewer runtime errors.
In Go, functions are first-class citizens, meaning they can be passed as arguments to other functions and returned from functions.
This makes Go functions very flexible and reusable.
Go has a concept called "zero value", where variables have a default value of zero when declared.
This can be useful for initializing variables without explicitly setting a value.
A unique perspective: Golang Runtime
Variable Scope
Writing smaller functions can eliminate reliance on mutable variables that leak into the global scope.
Global variables are problematic and make it difficult for programmers to understand the current state of a variable.
If a variable is global and mutable, its value can be changed by any part of the codebase, making it hard to guarantee its value at any point.
Non-global variables with a large scope can cause problems, as seen in the code example where a variable val is declared in the main function.
This variable val is declared in the first line of main, but its value is later modified in a switch statement, causing variable shadowing.
Variable shadowing occurs when a new variable is declared in a smaller scope, hiding the original variable.
A simple refactor can resolve this issue by reducing the scope of the variable val.
By doing so, we can eliminate the problem of variable shadowing and make our code easier to understand.
In fact, writing smaller functions can typically eliminate reliance on mutable variables that leak into the global scope.
This makes our code more predictable and easier to maintain.
Developers should take responsibility for their own code and avoid using global variables whenever possible.
By doing so, we can reduce the complexity of our code and make it easier for others to understand.
Pointers in Go
Pointers in Go can add complexity to code, so it's essential to understand how to use them without introducing unnecessary complexity.
Incorrectly using pointers can introduce nasty side effects or bugs that are difficult to debug. By sticking to the basic principles of writing clean code, we can reduce the chances of introducing unnecessary complexity to our code.
Pointers are a big part of working with the language, and it's impossible to write Go without some knowledge of pointers and their workings.
Go pointers can be a bit tricky to handle, so it's crucial to be cautious when using them.
Internal/Use Case
In Go, the internal/usecase folder is where you'll find the business logic of your application. This is where the magic happens.
The methods in internal/usecase are grouped by area of application, making it easy to organize and maintain your code. Each group has its own structure, and one file should have one structure.
Business logic structures, such as repositories and web APIs, are injected into the use case structures. This is a key concept in Go programming, known as Dependency Injection.
Here are some key characteristics of use cases in Go:
- Methods are grouped by area of application
- Each group has its own structure
- One file - one structure
By following these guidelines, you can create maintainable and scalable use cases in your Go applications. Remember, the goal is to keep your code organized and easy to understand.
Interfaces and Types
Interfaces in Go are incredibly useful, especially when dealing with structs that contain interface properties. These properties have a default value of nil, which can be particularly useful for avoiding potential nil value errors.
In Go, interfaces are also useful for creating a loosely coupled architecture, making it easier to add functionality without affecting other parts of the code. This is achieved by passing functions, also known as closures, as input and returning a float64.
Structs containing data can be manipulated through methods like Do(), which perform operations on that data. However, if the operation is known ahead of time, the logic can be handled directly in the Do() method.
Nil Values
Nil values in Go can be tricky, and it's essential to understand how they work to avoid common pitfalls. Every interface property in Go has a default value of nil.
In Go, nil values correspond to uninitialized pointers, similar to NULL in C. This can cause problems when trying to access methods or properties of a nil value.
Things break when you try to access methods or properties of a nil value, leading to runtime errors. It's recommended to avoid returning a nil value when possible to prevent these issues.
Incorrectly initializing a struct can lead to nil properties, which can cause a panic if accessed. This can happen if a property within the struct is not initialized properly.
Invoking a method on a nil property can cause a panic with a message like "panic: runtime error: invalid memory address or nil pointer dereference". This is what happened in the example code that tried to access the Add method on an uninitialized Cache property.
To avoid these issues, you can turn a property into a private property and create a getter-like method to access it. This ensures that you're not returning a nil value and gives users of your package more control over what they're using.
Check this out: Golang Method
Interfaces in Go
Interfaces in Go are all about flexibility and loosely coupled architecture. This pattern can be particularly useful for creating a loosely coupled architecture, making it easier to add functionality without affecting other parts of the code.

Every interface property in Go has a default value of nil, which is useful for any struct that has an interface property. This is also true for structs that contain channels, maps, and slices, which could potentially also have a nil value.
A closure can be passed as an input to another function, returning a float64, which is a key aspect of interfaces in Go. This allows for the creation of a loosely coupled architecture, making it easier to add functionality without affecting other parts of the code.
In Go, a closure can be used to create a function that takes a single byte array as an input argument, but it could just as well have been the opposite case. This flexibility is a hallmark of interfaces in Go.
Check this out: Golang Function with Many Parameters
Internal/Controller
In the internal/controller layer, server handler layers are organized using the MVC controller pattern.
Server routers are written in a specific style that groups handlers by area of application.

Handlers are grouped by a common basis, making it easier to manage and maintain the code.
Each group has its own router structure, which processes paths and calls the business logic.
The structure of the business logic is injected into the router structure, allowing for a clear separation of concerns.
This approach enables developers to write efficient and scalable code.
Here are the key characteristics of server routers in the internal/controller layer:
- Handlers are grouped by area of application
- Each group has its own router structure
- Business logic is injected into the router structure
Internal/Entity
Internal/Entity is a crucial part of your project structure.
In the Go project structure, the /internal/entity folder stores the implementation of business logic models, also known as entities. These entities can be used in any layer of your project.
The /internal/entity folder is a great place to put methods for validation, as seen in example 2.
Entities are information in their rawest state, making them a fundamental part of your project's data models. You can find more details about the project organization in the classic document Standard Go Project Layout.
In the /domain folder, you'll find the implementation of business rules, including entities and use cases. This is where the magic happens, and your project starts to take shape.
Architecture
Clean Architecture is a software design that separates concerns, allowing developers to encapsulate business logic and keep it independent from delivery and framework mechanisms. This separation is achieved by dividing the software into layers, with the outer layer containing low-level components like UI, DB, transport, or 3rd party services.
The dependency between modules/components can be described by the arrows in the Clean Architecture diagram, where components behind the boundary belong to the outer layers. The controller, presenter, and database are all part of the outer layers.
The Interactor, where business logic is implemented, is the use-case layer and is not dependent on the outer layers. This is achieved through dependency injection and inversion of control, where the Interactor speaks with the presenter via a Boundary interface and with the data layer via an Entity Gateway interface.
The Request Model and Response Model are objects describing the data that the inner layer requires and returns, respectively. The controller translates the request into the Request Model, and the presenter formats the Response Model into data that can be presented by the View Model.
Readers also liked: Gorm Golang Clean Table
Here's a summary of the different layers in Clean Architecture:
By separating concerns and using dependency injection and inversion of control, Clean Architecture allows developers to encapsulate business logic and keep it independent from the delivery and framework mechanisms. This makes it easier to change the outer layers without affecting the inner layers.
What Is Architecture
Architecture is a software design that separates concerns, allowing developers to encapsulate business logic and keep it independent from delivery and framework mechanisms. This design was created by Robert Martin, also known as Uncle Bob.
Clean Architecture is a software architecture design that emerged in the first decade of the century, inspired by the idea of separation of concerns and influenced by Domain-Driven Design. Several architectures, such as Hexagonal Architecture, have contributed to the development of Clean Architecture.
Clean Architecture is the best starting point for those starting their studies in project design, helping them understand the principles behind this design. This design has practical results, as it can reduce development time and make it easier to maintain and update software systems.
The dependency rule in Clean Architecture is a key concept, where outer layers cannot depend on inner layers. This means that if something is declared in an outer circle, it must not be mentioned in the inner circle code.
Readers also liked: Golang Design
Service
The service is where the business logic (BL) is implemented. It has no knowledge of the endpoint or the transport domain, such as HTTP.
The service is also known as the interactor, and it's where you define the methods for triggering the BL. For example, in the shipping example, the service has methods for actually triggering the BL.
The transport layer uses a function to create the server, such as an HTTP server or gRPC server. This function takes a decoder and encoder as arguments, which are defined in the transport layer.
Here's a breakdown of what's involved in creating the server:
- DecodeRequestFunc translates the HTTP request to the Request Model.
- EncodeResponseFunc formats the Response Model and encodes it in the HTTP response.
- The returned *server implements http.Server (has ServeHTTP method).
The service interface is defined in the endpoint.go file, and it includes methods for triggering the BL. The transport layer registers the HTTP routes in the transport.go file.
The MakeEndpoint function is used to create the endpoint on runtime, and it provides a decoder for deserializing the request and an encoder for formatting and encoding the response.
Putting It Together:

When you're designing an application's architecture, it's essential to visualize the relationships between different components. A dependency diagram can help you understand how each part interacts with others.
In a simple application, the entity has no dependencies, only inward arrows. This means it doesn't rely on other components to function.
The outer layers, such as transport and inmem, only have arrows pointing towards the inner layers of the Business Logic (BL) and the entity. This indicates that these layers are dependent on the inner layers to operate.
Take a look at this: Golang Application
Dependency Management
Dependency Management is crucial for clean Golang code.
Dependency Injection is used to remove dependence of business logic on external packages, making it independent and portable.
This allows us to override the implementation of the interface without making changes to the usecase package.
By injecting dependencies, we can auto-generate mocks and easily write unit tests.
We're not tied to specific implementations, so we can change one component to another as long as the new component implements the interface.
Nothing needs to be changed in the business logic, making it highly maintainable.
Go-Kit and Architecture
Go-Kit is not an MVC framework, which means it doesn't follow the traditional model-view-controller pattern.
Instead, it divides the application into three distinct layers: Transport, Endpoint, and Service. This layered approach helps build robust and maintainable microservices.
The Transport layer is responsible for handling communication between services. The Endpoint layer defines the API endpoints, and the Service layer contains the business logic.
This layered architecture makes it easier to develop and maintain microservices, as each layer has a specific responsibility.
Best Practices
In Go, it's essential to restrain yourself from modifying variables that are meant to be immutable, as there's no way to declare a const struct or static variables.
You can clarify your code with comments to make it easier to understand, but it won't prevent mutations like modifying the sender variable.
Go's lack of const structs or static variables means you'll have to be mindful of your code's mutability.
As you refactor functions, remember to clarify your code with comments to make it cleaner and more maintainable.
In Go, you can't prevent mutations like modifying a variable at a later point in the code, so it's crucial to be mindful of your code's mutability.
Featured Images: pexels.com


