Go lang: From 0 to Employed
Go lang foundations :: Clean Architecture :: part 1
Golang Clean Architecture tutorial
Looking to conquer Clean Architecture with Golang? This guide’s got your back. Embark on a coding journey with this compact guide to learn Clean Architecture with Gorilla Mux! You’ll go from project setup to server deployment, learning key concepts such as dependency injection and Clean Architecture. Expect hands-on work with entities, use cases, testing, and building RESTful API endpoints.
By the end, you’ll be equipped with skills to develop, test, and deploy resilient Golang servers. So, ready to transform your coding journey and fast-track your way to Golang proficiency? Let’s get started and make coding magic happen!
Attention: For who is this guide?
This guide is part of a web series intended for individuals who are already proficient in a programming language and are looking to learn GoLang in a simple and fast manner. Therefore, I cover various aspects of the language directly and succinctly, aiming to provide the necessary material for a smooth career transition from other languages to Go. The focus is on supplying ample learning material and support, enabling developers unfamiliar with Go to start working with GoLang as quickly as possible.
Index:
- Introduction to Clean Architecture and Gorilla Mux
- Setting up the development environment
- Creating the project structure
— Folder structure
— Organizing the application in layers - Understanding dependency injection in Clean Architecture
— Separation of concerns
— Inversion of control
— Benefits of dependency injection in Clean Architecture - Defining entities and use cases
— Entities
— Use Cases - Implementing the repository and service
— Repository
— Service - Implementing the presentation layer
— Presenting Gorilla Mux
— Installing Gorilla Mux
— Define routes and HTTP methods
— Create a handler struct
— Create a server Router with the Gorilla Mux router
— Middlewares
— Handling input validation and error handling
— Starting the server - Writing tests for the Golang server
— Unit testing
— Integration testing the API endpoints
— Best practices for testing Clean Architecture applications - Deploying the Golang server
— Overview
— Preparing the application for deployment
— Monitoring and maintaining the application
Feeling lost? Fear not, adventurer! Follow this link for a fresh restart. Your journey starts here!
Introduction to Clean Architecture and Gorilla Mux
Clean Architecture is a software design pattern that focuses on separation of concerns, modularity, and maintainability. It is an architectural pattern that emphasizes the organization of code into independent layers, each with a specific responsibility. Clean Architecture aims to make the application easier to understand, test, and maintain, while providing a flexible foundation for future enhancements.
In this tutorial, we will combine the principles of Clean Architecture with the Gorilla Mux library to build a Golang server that is modular, maintainable, and scalable. By following Clean Architecture principles, we will ensure that our Golang server is easy to understand, test, and extend. Gorilla Mux will allow us to manage our routes and handle HTTP requests effectively, providing a solid foundation for building a RESTful API.
Gorilla Mux is a popular and powerful URL router and dispatcher for Golang applications. It provides a simple and efficient way to manage HTTP routes, handle requests, and build RESTful APIs. Gorilla Mux is well-suited for Golang server development due to its performance, ease of use, and extensibility.
Setting up the development environment
Before we start building our Golang server using Clean Architecture and the Gorilla Mux library, we need to set up our development environment. Follow these steps to get started:
- Install Go: Download and install the latest version of the Go programming language from the official website: https://golang.org/dl/. Make sure to follow the installation instructions for your specific operating system.
- Set up GOPATH: After installing Go, set up your GOPATH environment variable to point to your Go workspace, which is the directory where your Go projects will be stored. Follow the instructions in the Go documentation for your operating system: https://golang.org/doc/gopath_code.html#GOPATH.
- Install a code editor: To write and edit your Go code, you will need a code editor or an integrated development environment (IDE). Some popular options include Visual Studio Code, GoLand, and Sublime Text. Install the one that best suits your needs and preferences.
- Install Go extensions: If you are using Visual Studio Code or another editor that supports extensions, install the relevant Go extensions or plugins to enable features like syntax highlighting, code completion, and formatting. For Visual Studio Code, the recommended extension is “Go” by the Go Team at Google.
- Install Git: If you don’t have it already, install Git to manage your project’s version control. You can download Git from the official website: https://git-scm.com/downloads.
- Create a new project directory: Create a new directory for your project within your GOPATH, following the Go workspace conventions. For example, if your GOPATH is set to /home/user/go, you could create a new directory named /home/user/go/src/github.com/yourusername/myproject.
Now that your development environment is set up, you’re ready to start building your Golang server using Clean Architecture and the Gorilla Mux library.
Creating the project structure
Organizing your Golang server project using Clean Architecture principles involves separating the code into different layers based on their responsibilities. Let’s start by creating the project structure:
Folder structure
..
├── cmd
│ └── server
│ └── main.go
├── internal
│ ├── domain
│ │ ├── errors.go # Contains DomainError struct
│ │ ├── post.go # Contains your `Post` struct
│ │ └── repository.go # Defines interfaces for your repositories
│ ├── usecases
│ │ ├── post_usecase.go # Contains methods for executing business logic related to posts
│ │ └── post_usecase_test.go # Contains tests for the post usecase
│ ├── infrastructure
│ │ ├── repository
│ │ │ └── post_repository.go # Contains implementation of the `PostRepository` interface
│ │ └── database
│ │ └── database.go # Contains logic for connecting to and interacting with the database
│ └── presentation
│ ├── api
│ | └── post_handler.go # Contains handlers for HTTP requests related to posts
│ └── middleware
│ └── middleware.go # Contains any middleware functions your application might use
├── pkg
│ ├── logger
│ │ └── logger.go
│ ├── config
│ │ └── config.go
│ └── middleware
│ └── middleware.go
├── configs
│ └── config.yaml
├── migrations
├── scripts
├── go.mod
├── go.sum
├── README.md
└── etc…
Organizing the application in layers
When organizing the application layers in a Golang server following Clean Architecture principles, you’ll create separate directories for each layer, focusing on their specific responsibilities. Here’s a breakdown of the different layers and their roles:
- domain: This layer contains the core business logic of your application. It includes entities, value objects, and domain interfaces that represent the fundamental business concepts and rules. The domain layer should not depend on any other layer and should be kept as pure as possible.
- usecases: The usecases layer holds the application-specific business rules and orchestrates the interaction between the domain layer and other layers, such as infrastructure and presentation. It contains the implementations of use cases (e.g., creating a new user, fetching a list of posts) and input/output data structures for each use case. The usecases layer depends on the domain layer and may rely on interfaces defined in the domain layer.
- infrastructure: This layer is responsible for the implementation details of external services and resources, such as databases, message queues, or third-party APIs. The infrastructure layer should provide concrete implementations of interfaces defined in the domain layer to interact with these external services. This separation ensures that the core business logic remains independent of specific technologies or implementations.
- presentation: The presentation layer contains the code responsible for handling user input, presenting the output, and managing the user interface. In a RESTful API server, this layer would consist of API controllers, middleware, and error handling logic. The presentation layer depends on the usecases layer to execute the desired actions and present the results.
- The pkg directory (short for “package”) is a common directory seen in many Go projects, and it’s used to store code that can be used by other projects. This is where you would put libraries and packages that were designed with good reusability and a high level of abstraction
- The cmd directory (short for “command”) is another common directory in many Go projects, and it’s used to store the main applications for your project.
By organizing your Golang server application into these distinct layers, you adhere to the Clean Architecture principles, resulting in a modular, maintainable, and scalable project structure.
Understanding dependency injection in Clean Architecture
Dependency injection is a technique that facilitates the management of dependencies between components in a software application, allowing for better modularity and flexibility. In Clean Architecture, dependency injection plays a critical role in maintaining separation of concerns and achieving inversion of control, two key principles that contribute to a more maintainable and scalable codebase. In this section, we’ll discuss the role of dependency injection in Clean Architecture and how it contributes to overall architectural benefits.
Separation of concerns
Clean Architecture promotes a clear separation of concerns between different components and layers of an application. Each layer should have a specific responsibility and should not be concerned with the implementation details of other layers. Dependency injection helps achieve this separation by allowing components to depend on abstractions (interfaces) rather than concrete implementations.
For example, when building a Golang server application, the use case layer should not be concerned with how data is persisted or retrieved. Instead, it should rely on an interface that defines the necessary methods for data access, such as a PostRepository interface. The concrete implementation of the repository is then provided via dependency injection, allowing the use case layer to focus on its core responsibilities.
Inversion of control
Inversion of control (IoC) is a principle that states that components should not be responsible for creating their dependencies. Instead, dependencies should be provided (injected) by an external entity. Dependency injection helps achieve IoC by allowing components to receive their dependencies via constructor functions or method parameters.
By inverting the control of dependencies, components become less tightly coupled and more adaptable to changes. For example, when testing a use case, you can easily provide a mock implementation of the repository without modifying the use case code. Similarly, if you need to change the data persistence mechanism, you can create a new repository implementation and inject it into the use case without affecting the use case logic.
Benefits of dependency injection in Clean Architecture
- Modularity: Dependency injection promotes a modular codebase by allowing components to depend on abstractions rather than concrete implementations. This makes it easier to refactor, extend, and maintain the application.
- Testability: By injecting dependencies, you can easily provide mock implementations for testing purposes, making it simpler to write unit tests for individual components.
- Flexibility: Dependency injection allows you to swap out implementations without modifying the dependent components, making it easier to adapt the application to new requirements or technologies.
- Maintainability: By promoting separation of concerns and inversion of control, dependency injection contributes to a more maintainable codebase, making it easier to understand, debug, and extend the application.
In conclusion, dependency injection is a key technique in Clean Architecture that helps maintain separation of concerns, achieve inversion of control, and build a modular, testable, and maintainable Golang server application. By understanding and applying dependency injection principles, you can create a more robust and scalable codebase that can better accommodate future changes and requirements.
Defining entities and use cases
In the domain directory, create files for each of your entities and define their structs, methods, and interfaces. For example, if you are building a blog server, you might have entities like post.go and author.go.
Next, in the usecases directory, create files for each use case your application will support. Define the input and output data structures for each use case, along with the interfaces that the use case will rely on. Implement the use case by creating a struct that satisfies the defined interfaces.
For instance, in a blog server, you might have use cases like create_post.go and list_posts.go. Each use case should have a corresponding input and output data structure, as well as an interface that describes the required functionality of the underlying repository or service.
By organizing your code into these layers, you will adhere to the Clean Architecture principles and create a modular, maintainable, and scalable Golang server.
In the Clean Architecture approach, the domain and usecases layers play a crucial role in organizing the application’s core business logic and interactions. Let’s take a closer look at how to define entities and use cases in a Golang server project:
Entities
Entities are the core business objects in your application. They represent the central concepts and business rules. To define entities, create files for each entity within the domain directory, and then define their structs, methods, and interfaces. Entities should be focused on the business logic and should not depend on external services or resources.
For example, if you are building a blog server, you might have entities like post.go and author.go:
// domain/post.go
type Post struct {
ID int
Title string
Content string
AuthorID int
CreatedAt time.Time
}
Use Cases
Use cases represent the application-specific business rules and orchestrate the interaction between the domain layer and other layers, such as infrastructure and presentation. To define use cases, create files for each use case within the usecases directory. Define the input and output data structures for each use case, along with the interfaces that the use case will rely on. Then, implement the use case by creating a struct that satisfies the defined interfaces.
For instance, in a blog server, you might have use cases like create_post.go and list_posts.go. Each use case should have a corresponding input and output data structure, as well as an interface that describes the required functionality of the underlying repository or service.
// usecases/create_post.go
type CreatePostInput struct {
Title string
Content string
AuthorID int
}
type CreatePostOutput struct {
ID int
Title string
Content string
AuthorID int
CreatedAt time.Time
}
type CreatePostUseCase interface {
Execute(input CreatePostInput) (*CreatePostOutput, error)
}
func NewCreatePostUseCase(postRepo domain.PostRepository) CreatePostUseCase {
return &createPostUseCase{
postRepo: postRepo,
}
}
type createPostUseCase struct {
postRepo domain.PostRepository
}
func (c *createPostUseCase) Execute(input CreatePostInput) (*CreatePostOutput, error) {
// Implement the use case logic here
}
By defining entities and use cases in this manner, you create a clear separation of concerns and adhere to the Clean Architecture principles, resulting in a modular, maintainable, and scalable Golang server application.
Implementing the repository and service
In Clean Architecture, the infrastructure layer is responsible for providing concrete implementations of the repositories and services that interact with external resources. This separation ensures that the core business logic remains independent of specific technologies or implementations.
To implement the repository and service layers, follow these steps:
Repository
Step 1: Define the repository interfaces
In the domain layer, define the repository interfaces required by your entities. These interfaces represent the data access operations that will be implemented in the infrastructure layer. For example, in the post.go file of a blog server, you might define a PostRepository interface:
// domain/post.go
type PostRepository interface {
FindByID(id int) (*Post, error)
FindAll() ([]*Post, error)
Create(post *Post) error
}
Step 2: Implement the repository interfaces
In the infrastructure layer, create a new directory called repositories. Within this directory, create a new file for each repository implementation, such as post_repository.go. Implement the repository interface defined in the domain layer using a specific technology, such as a relational database or a document store.
For example, you might implement the PostRepository interface using a SQL database:
// infrastructure/repositories/post_repository.go
import (
"github.com/yourusername/myproject/domain"
)
type PostSQLRepository struct {
db *sql.DB
}
func (r *PostSQLRepository) FindByID(id int) (*domain.Post, error) {
// Implement the FindByID method using SQL queries
}
func (r *PostSQLRepository) FindAll() ([]*domain.Post, error) {
// Implement the FindAll method using SQL queries
}
func (r *PostSQLRepository) Create(post *domain.Post) error {
// Implement the Create method using SQL queries
}
Want to learn how to use db *sql.DB? Don’t wait, learn it here!
Service
In some cases, your use cases might rely on external services or APIs. In such scenarios, define the service interfaces in the domain layer, and then implement them in the infrastructure layer using a specific technology or API.
For instance, if your application needs to send email notifications, you could define an EmailService interface in the domain layer and implement it using a third-party email provider in the infrastructure layer.
Step 1: Define the service interfaces in the domain layer.
// domain/emailservice.go
package domain
type EmailService interface {
Send(to string, subject string, body string) error
}
This interface provides a contract for sending an email. Note that it doesn’t specify any implementation details — those will be handled in the infrastructure layer. The purpose of this interface is to define what actions should be possible related to email services.
Step 2: Implement the service interfaces in the infrastructure layer.
// infrastructure/emailservice.go
package infrastructure
import (
"fmt"
"domain"
)
type SMTPEmailService struct {
// This might include additional fields such as an SMTP server address, credentials, etc.
}
func (s SMTPEmailService) Send(to string, subject string, body string) error {
// Here you'd implement the actual logic for sending an email using an SMTP server.
// For the sake of this example, we'll just print the email details.
fmt.Printf("Sending email to %s with subject %s and body %s\n", to, subject, body)
return nil
}
In this example, we’ve provided an implementation of the EmailService interface that uses an SMTP server to send emails. By defining the service interface in the domain layer, we’ve made it possible to easily switch out this SMTP implementation for another one — for example, an email service that uses a third-party API — without changing any of the code in the domain layer that uses this service.
Note that in a real implementation, the SMTPEmailService.Send() method would contain the logic for connecting to an SMTP server and sending an email, and would return an error if any part of that process failed.
The use of interfaces and separation into domain and infrastructure layers here allows you to separate the definition of what your application should do (the domain layer) from the details of how it does it (the infrastructure layer). This makes your code easier to maintain and test, and more flexible in the face of changing requirements.
By implementing the repository and service layers in the infrastructure layer, you maintain a clear separation of concerns and adhere to the Clean Architecture principles, resulting in a modular, maintainable, and scalable Golang server application.
Your adventure isn’t over yet! Embark on Part 2, dive into new knowledge right here!