DEV Community

Jastria Rahmat
Jastria Rahmat

Posted on

Decoupling Dependencies in Clean Architecture: A Practical Guide

In software development, adhering to clean architecture principles is essential for creating applications that are scalable, maintainable, and flexible. One of the foundational rules within clean architecture is the decoupling of dependencies, which aligns closely with the SOLID principles, particularly the concept of "separation of concerns."

Understanding Dependency Decoupling

Before delving into examples, let's briefly explore the concept of decoupling dependencies. Dependency decoupling involves designing components in a way that reduces interdependence between them. This separation enhances modularity, making it easier to replace or modify components without affecting the entire system.

Example in TypeScript Express

Before Implementing Decoupling:

In a typical Express.js application written in TypeScript, components often tightly couple dependencies, leading to issues with scalability and maintainability. Take, for instance, the following code snippet:

import express, { Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { UserRepository } from './repositories/UserRepository';

const app = express();
const userRepository = new UserRepository();

app.use(express.json());

app.post('/register', (req: Request, res: Response) => {
  const userId = uuidv4();

  const newUser = {
    id: userId,
    name: req.body.userName,
    password: req.body.password,
    email: req.body.email
  };

  const user = userRepository.createNewUser(newUser);

  res.json({ message: "Success creating user" });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Enter fullscreen mode Exit fullscreen mode

After Implementing Decoupling (Using Dependency Injection):

By introducing dependency injection, we can decouple dependencies effectively. Here's how it looks in TypeScript:

import express, { Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { UserRepository } from './repositories/UserRepository';

class UserController {
  private userRepository: UserRepository;

  constructor(userRepository: UserRepository) {
    this.userRepository = userRepository;
  }

  registerUser(req: Request, res: Response): void {
    const userId = uuidv4();

    const newUser = {
      id: userId,
      name: req.body.userName,
      password: req.body.password,
      email: req.body.email
    };

    const user = this.userRepository.createNewUser(newUser);

    res.json({ message: "Success creating user" });
  }
}

const app = express();
const userRepository = new UserRepository();
const userController = new UserController(userRepository);

app.use(express.json());

app.post('/register', (req: Request, res: Response) => {
  userController.registerUser(req, res);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Example in Go and Echo

Before Implementing Decoupling (Without Dependency Injection):

In Go applications using Echo framework, dependencies are often tightly coupled, hindering flexibility and testability. Consider the following code snippet:

package main

import (
    "github.com/labstack/echo/v4"
    "github.com/google/uuid"
)

type UserRepository struct {}

func (repo *UserRepository) CreateNewUser(newUser map[string]string) map[string]string {
    // Logic to create new user
    return newUser
}

func registerUser(c echo.Context) error {
    userId := uuid.New().String()

    newUser := map[string]string{
        "id":       userId,
        "name":     c.FormValue("userName"),
        "password": c.FormValue("password"),
        "email":    c.FormValue("email"),
    }

    userRepository := UserRepository{}
    user := userRepository.CreateNewUser(newUser)

    return c.JSON(200, map[string]string{"message": "Success creating user"})
}

func main() {
    e := echo.New()
    e.POST("/register", registerUser)
    e.Logger.Fatal(e.Start(":3000"))
}

Enter fullscreen mode Exit fullscreen mode

After Implementing Decoupling (Using Dependency Injection):

With dependency injection, we can achieve decoupling in Go and Echo applications. Here's an example:

package main

import (
    "github.com/labstack/echo/v4"
    "github.com/google/uuid"
)

type UserRepository interface {
    CreateNewUser(newUser map[string]string) map[string]string
}

type UserRepositoryImpl struct {}

func (repo *UserRepositoryImpl) CreateNewUser(newUser map[string]string) map[string]string {
    // Logic to create new user
    return newUser
}

type UserController struct {
    userRepository UserRepository
}

func (controller *UserController) RegisterUser(c echo.Context) error {
    userId := uuid.New().String()

    newUser := map[string]string{
        "id":       userId,
        "name":     c.FormValue("userName"),
        "password": c.FormValue("password"),
        "email":    c.FormValue("email"),
    }

    user := controller.userRepository.CreateNewUser(newUser)

    return c.JSON(200, map[string]string{"message": "Success creating user"})
}

func main() {
    e := echo.New()

    userRepository := &UserRepositoryImpl{}
    userController := &UserController{userRepository}

    e.POST("/register", userController.RegisterUser)
    e.Logger.Fatal(e.Start(":3000"))
}

Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Decoupling Dependencies: Decoupling dependencies enhances modularity and flexibility, allowing for easier maintenance and scalability.

  • Dependency Injection: Dependency injection promotes loose coupling by passing dependencies as arguments, making components more testable and maintainable.

  • Clean Architecture Principles: Adhering to clean architecture principles, such as separation of concerns, fosters the development of robust and scalable applications.

Conclusion

In summary, decoupling dependencies through dependency injection is crucial for building clean and maintainable software architectures. By following these principles, developers can create applications that are easier to test, maintain, and extend, ultimately leading to a more efficient and sustainable development process.

By incorporating these practices into your projects, you can significantly improve the quality and longevity of your software applications.

Thank you for reading! If you found this article helpful, please consider sharing it with your network.

Keywords: Clean Architecture, Dependency Decoupling, SOLID Principles, Dependency Injection, TypeScript Express, Go, Echo Framework.

Top comments (0)