Skip to main content

Command Palette

Search for a command to run...

Clean Arch is a must

Updated
3 min read
Clean Arch is a must

How does this thinking help our clients?

  • Readily adapts to new and evolving technologies, ensuring long-term relevance.

  • The distinct boundaries in Clean Architecture simplify understanding and maintaining the code.

In the dynamic world of software development, the choices we make regarding programming languages and architectural paradigms have profound implications for the scalability, maintainability, and success of our projects. Let's explore why this combination clicks.

It heavily advocates the usage of interfaces

By defining our logic through interfaces, Clean Architecture reduce dependencies between various layers of our application. This makes our code far more flexible and resilient to changes, as implementations can be swapped without disrupting the overall system. Ex: switch frameworks (like web frameworks) or databases (like MySQL to PostgreSQL) without rewriting significant portions of your code. This flexibility is particularly valuable in Golang's ever-evolving ecosystem, ensuring your code stays relevant and adaptable in the long run.

In the following illustration, core business logic is independent of the database layer as captured in UserService. It is easy to switch from one implementation of database storage to another without affecting the UserService business logic.

type UserService struct {
    UserRepository IUserRepository
}

func NewUserService(repo IUserRepository) UserService {
    return UserService{UserRepository: repo}
}

func (s *UserService) GetUserByID(id int64) (*User, error) {
    return s.UserRepository.GetByID(id)
}
// IUserRepository defines CRUD operations for users
type IUserRepository interface {
   GetByID(int64) (*User, error)
   Create(*User) error
   Update(*User) error
   Delete(int64) error
}

It sets you up to achieve 100% code test coverage for core business logic.

Clean Architecture advocates for a clear separation of concerns, preventing high-level business logic from depending on low-level details like databases.

By defining interfaces within our core domain layer and letting dependencies flow outwards, we create a distinct zone for our core logic. This isolated zone, free from dependencies on databases, frameworks, or external services, becomes the perfect candidate for achieving 100% unit test coverage. This translates into several benefits: thorough verification of business rules, a safety net for future evolution and new features, and reduced debugging thanks to well-tested code.

In the below example, the UpdateUserEmail use case contains the essential business logic for changing a user's email. The unit test concentrates exclusively on validating the use case logic, employing a mock user repository to isolate it from external interactions.

// UpdateUserEmail Usecase
func (uc *UpdateUserEmail) Execute(id int64, newEmail string) error {
    user, err := uc.repo.GetByID(id)
    if err != nil {
        return err
    }

    // Business logic to validate and update email (e.g., unique email check)
    if !validateEmail(newEmail) {
        return errors.New("invalid email format")
    }
    user.Email = newEmail
    return uc.repo.Update(user)
}
// Unit test for UpdateUserEmail Usecase
func TestUpdateUserEmail(t *testing.T) {
    mockRepo := new(mock.Mock)
    defer mockRepo.AssertExpectations(t)

    mockRepo.On("GetByID", 1).Return(&domain.User{ID: 1, Username: "user1", Email: "user1@example.com"}, nil)
    mockRepo.On("Update", &domain.User{ID: 1, Username: "user1", Email: "new_email@example.com"}).Return(nil)

    uc := domain.NewUpdateUserEmail(mockRepo)

    err := uc.Execute(1, "new_email@example.com")

    assert.NoError(t, err)
    mockRepo.Verify(t)
}

DIP is applied well in practice.

One of the core strengths of Clean Architecture is its strong adherence to the Dependency Inversion Principle (DIP). This principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions, and abstractions should not depend on details. We translate this principle into clear separation of concerns within our applications. Our core business logic resides in the inner domain layer, free from dependencies. This layer interacts with the outside world through well-defined interfaces.

Applying DIP through Clean Architecture unlocks several advantages such as Enhanced Security and Unparalleled Adaptability.

Go Lang

Part 1 of 1

This is to showcase our GoLang Expertise