Building a Go Web Application with Clean Architecture

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up the Project
  4. Creating the Core Domain
  5. Implementing the Use Cases
  6. Building the Infrastructure
  7. Creating the Web Application
  8. Conclusion

Introduction

In this tutorial, we will learn how to build a web application in Go using the principles of Clean Architecture. Clean Architecture is a software design pattern that emphasizes separation of concerns and a robust domain model. By the end of this tutorial, you will have a clear understanding of how to structure and develop a web application in Go with Clean Architecture.

Prerequisites

Before starting this tutorial, you should have basic knowledge of Go programming language and web development concepts. You should also have Go installed on your machine. If you haven’t already, you can download and install Go from the official website: https://golang.org/

Setting Up the Project

To begin, let’s set up the project structure and dependencies. Open your terminal, navigate to your desired project directory, and create a new directory for your project. We will call it “go-webapp-clean-architecture”.

$ mkdir go-webapp-clean-architecture

Next, go into the project directory and initialize a new Go module.

$ cd go-webapp-clean-architecture
$ go mod init github.com/your-username/go-webapp-clean-architecture

Now, we need to install some required dependencies. We will be using the “gorilla/mux” package for routing and the “gorm” package for database interactions. Run the following commands to install these dependencies:

$ go get -u github.com/gorilla/mux
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/sqlite

Creating the Core Domain

The first step in building a web application with Clean Architecture is to define the core domain of your application. The core domain represents the fundamental concepts and business rules of your application.

Create a new directory named “domain” within the project directory.

$ mkdir domain

Inside the “domain” directory, create a new file named “user.go”. This file will define the User entity in our application.

package domain

type User struct {
    ID       int
    Name     string
    Email    string
    Password string
}

Next, create a file named “repository.go” in the same “domain” directory. This file will define the interfaces for interacting with the User repository.

package domain

type UserRepository interface {
    FindByID(id int) (*User, error)
    FindByEmail(email string) (*User, error)
    Create(user *User) error
    Update(user *User) error
    Delete(id int) error
}

Implementing the Use Cases

Use cases represent the actions or operations that can be performed on the core domain entities. In our case, we will create use cases for user registration, login, and updating user profile.

Create a new directory named “usecase” within the project directory.

$ mkdir usecase

Inside the “usecase” directory, create a new file named “user_uc.go”. This file will define the interface and implementation for our user-related use cases.

package usecase

import "github.com/your-username/go-webapp-clean-architecture/domain"

type UserUseCase interface {
    RegisterUser(name, email, password string) error
    LoginUser(email, password string) (*domain.User, error)
    UpdateUserProfile(id int, name, email string) error
}

type userUseCase struct {
    userRepository domain.UserRepository
}

func NewUserUseCase(userRepository domain.UserRepository) UserUseCase {
    return &userUseCase{
        userRepository: userRepository,
    }
}

func (uc *userUseCase) RegisterUser(name, email, password string) error {
    // Implement user registration logic
}

func (uc *userUseCase) LoginUser(email, password string) (*domain.User, error) {
    // Implement user login logic
}

func (uc *userUseCase) UpdateUserProfile(id int, name, email string) error {
    // Implement updating user profile logic
}

Building the Infrastructure

The infrastructure layer is responsible for handling external dependencies, such as the database. In this tutorial, we will use SQLite as our database.

Create a new directory named “repository” within the project directory.

$ mkdir repository

Inside the “repository” directory, create a new file named “user_repo.go”. This file will define the implementation of the UserRepository interface.

package repository

import (
    "github.com/your-username/go-webapp-clean-architecture/domain"
    "gorm.io/gorm"
)

type userRepository struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) domain.UserRepository {
    return &userRepository{
        db: db,
    }
}

func (r *userRepository) FindByID(id int) (*domain.User, error) {
    // Implement finding a user by ID
}

func (r *userRepository) FindByEmail(email string) (*domain.User, error) {
    // Implement finding a user by email
}

func (r *userRepository) Create(user *domain.User) error {
    // Implement creating a new user
}

func (r *userRepository) Update(user *domain.User) error {
    // Implement updating a user
}

func (r *userRepository) Delete(id int) error {
    // Implement deleting a user
}

Creating the Web Application

Now that we have defined the core domain, implemented the use cases, and built the infrastructure layer, it’s time to create the web application layer.

Create a new directory named “delivery” within the project directory.

$ mkdir delivery

Inside the “delivery” directory, create a new file named “handler.go”. This file will define the HTTP handlers for our web application.

package delivery

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/your-username/go-webapp-clean-architecture/domain"
    "github.com/your-username/go-webapp-clean-architecture/usecase"
)

type UserHandler struct {
    userUseCase usecase.UserUseCase
}

func NewUserHandler(userUseCase usecase.UserUseCase) *UserHandler {
    return &UserHandler{
        userUseCase: userUseCase,
    }
}

func (h *UserHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
    // Implement registering a new user using the user use case
}

func (h *UserHandler) LoginUser(w http.ResponseWriter, r *http.Request) {
    // Implement user login using the user use case
}

func (h *UserHandler) UpdateUserProfile(w http.ResponseWriter, r *http.Request) {
    // Implement updating user profile using the user use case
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprint(w, `{"error": "Not Found"}`)
}

func SetUpRoutes(userHandler *UserHandler) *mux.Router {
    router := mux.NewRouter()

    router.HandleFunc("/register", userHandler.RegisterUser).Methods("POST")
    router.HandleFunc("/login", userHandler.LoginUser).Methods("POST")
    router.HandleFunc("/update-profile/{id}", userHandler.UpdateUserProfile).Methods("PUT")

    router.NotFoundHandler = userHandler

    return router
}

Finally, create the main.go file in the project directory for starting the web server.

package main

import (
    "log"
    "net/http"

    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "github.com/your-username/go-webapp-clean-architecture/delivery"
    "github.com/your-username/go-webapp-clean-architecture/repository"
    "github.com/your-username/go-webapp-clean-architecture/usecase"
)

func main() {
    // Set up the database connection
    db, err := gorm.Open(sqlite.Open("database.db"), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }

    // Migrate the database tables
    db.AutoMigrate(&domain.User{})

    // Initialize the repositories
    userRepository := repository.NewUserRepository(db)

    // Initialize the use cases
    userUseCase := usecase.NewUserUseCase(userRepository)

    // Initialize the HTTP handlers
    userHandler := delivery.NewUserHandler(userUseCase)

    // Set up the routes
    router := delivery.SetUpRoutes(userHandler)

    // Start the server
    log.Println("Server started at http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Conclusion

Congratulations! You have successfully built a Go web application using Clean Architecture. We covered the process of structuring your project using the core domain, use cases, infrastructure, and web application layers. You also learned how to create HTTP handlers and set up routes using the Gorilla Mux package. Feel free to expand upon this application by adding more entities, use cases, and functionality.