Tìm hiểu về Clean Architecture

1.Clean Architecture là gì? Clean Architecture được xây dựng dựa trên tư tưởng “độc lập” kết hợp với các nguyên lý thiết kế hướng đối tượng(đại diện tiêu biểu là Dependency Inversion). Độc lập ở đây nghĩa là việc project không bị phụ thuộc vào framework và các công cụ sử dụng trong quá trình

1.Clean Architecture là gì?

Clean Architecture được xây dựng dựa trên tư tưởng “độc lập” kết hợp với các nguyên lý thiết kế hướng đối tượng(đại diện tiêu biểu là Dependency Inversion). Độc lập ở đây nghĩa là việc project không bị phụ thuộc vào framework và các công cụ sử dụng trong quá trình kiểm thử.

Kiến trúc của Clean Architecture chia thành 4 layer với một quy tắc phụ thuộc.

  • Entities
  • Use Cases (Application business rules)
  • Interface Adapter
  • Framework and Drivers

Các layer bên trong không nên biết bất kỳ điều gì về các layer bên ngoài. Điều này có nghĩa là nó có quan hệ phụ thuộc nên “hướng” vào bên trong. Có nghĩa là một layer chỉ có thể biết được các layer bên trong nó chứa gì. Nhìn vào hình vẽ minh họa sau đây:
image.png

image.png

Entities: là khái niệm dùng để mô tả các Business Logic. Đây là layer quan trọng nhất, là nơi bạn thực hiện giải quyết các vấn đề – mục đích khi xây dựng app. Hay nói cách khác là lớp Model. Đây là lớp trong cùng của kiến trúc Clean architecture.

Cấu trúc Entity như sau:
image.png
Ở package trên, các entities gồm book, book_test, entity…

Ví dụ với user như sau:

package entity

import (
	"time"

	"golang.org/x/crypto/bcrypt"
)

//User data
type User struct {
	ID        ID
	Email     string
	Password  string
	FirstName string
	LastName  string
	CreatedAt time.Time
	UpdatedAt time.Time
	Books     []ID
}

//NewUser create a new user
func NewUser(email, password, firstName, lastName string) (*User, error) {
	u := &User{
		ID:        NewID(),
		Email:     email,
		FirstName: firstName,
		LastName:  lastName,
		CreatedAt: time.Now(),
	}
	pwd, err := generatePassword(password)
	if err != nil {
		return nil, err
	}
	u.Password = pwd
	err = u.Validate()
	if err != nil {
		return nil, ErrInvalidEntity
	}
	return u, nil
}

Với entity này, chúng ta có các business như:

  • Tạo 1 user mới với email, password, firstName, lastName.

Use case : chứa các rule liên quan trực tiếp tới ứng dụng cục bộ (application-specific business rules).

ví dụ:
image.png

Các package trong usercase này sẽ triển khai các quy tắc của sản phẩm, ví dụ với quy tắc loan – cho mượn, file service.go sẽ như sau:

package loan

import (
	"github.com/eminetto/clean-architecture-go-v2/entity"
	"github.com/eminetto/clean-architecture-go-v2/usecase/book"
	"github.com/eminetto/clean-architecture-go-v2/usecase/user"
)

//Service loan usecase
type Service struct {
	userService user.UseCase
	bookService book.UseCase
}

//NewService create new use case
func NewService(u user.UseCase, b book.UseCase) *Service {
	return &Service{
		userService: u,
		bookService: b,
	}
}

//Borrow borrow a book to an user
func (s *Service) Borrow(u *entity.User, b *entity.Book) error {
	u, err := s.userService.GetUser(u.ID)
	if err != nil {
		return err
	}
	b, err = s.bookService.GetBook(b.ID)
	if err != nil {
		return err
	}
	if b.Quantity <= 0 {
		return entity.ErrNotEnoughBooks
	}

	err = u.AddBook(b.ID)
	if err != nil {
		return err
	}
	err = s.userService.UpdateUser(u)
	if err != nil {
		return err
	}
	b.Quantity--
	err = s.bookService.UpdateBook(b)
	if err != nil {
		return err
	}
	return nil
}

//Return return a book
func (s *Service) Return(b *entity.Book) error {
	b, err := s.bookService.GetBook(b.ID)
	if err != nil {
		return err
	}

	all, err := s.userService.ListUsers()
	if err != nil {
		return err
	}
	borrowed := false
	var borrowedBy entity.ID
	for _, u := range all {
		_, err := u.GetBook(b.ID)
		if err != nil {
			continue
		}
		borrowed = true
		borrowedBy = u.ID
		break
	}
	if !borrowed {
		return entity.ErrBookNotBorrowed
	}
	u, err := s.userService.GetUser(borrowedBy)
	if err != nil {
		return err
	}
	err = u.RemoveBook(b.ID)
	if err != nil {
		return err
	}
	err = s.userService.UpdateUser(u)
	if err != nil {
		return err
	}
	b.Quantity++
	err = s.bookService.UpdateBook(b)
	if err != nil {
		return err
	}

	return nil
}

Ở file trên các rule sau được triển khai:

  • Mượn sách bởi 1 người dùng.
  • Trả 1 cuốn sách bởi người dùng.

Frameworks and Drivers layer

Lớp ngoài cùng thường chứa các frameworks và tools như Database, Web Framework… Tuy nhiên cần chắc chắn về mức ưu tiên sử dụng các công cụ này trong project.

image.png

Ví dụ, với file infrastructure/repository/user_mysql.go, chúng ta triển khai các interface Repository của MySQL. Nếu chúng ta muốn chuyển đổi sang cơ sở dữ liệu mới, đây là nơi chuyển đổi.

Interface Adapters layer

Codes ở lớp này được thích ứng và chuyển đổi dữ liệu tới format được sử dụng bởi các entities và các use cases cho việc mở rộng bởi các tác nhân bên ngoài như như databases, web… Ở lớp Application, có 2 cách để truy cập tới các UseCases. Đầu tiên là các API và thứ 2 là các command line của ứng dụng CLI.

Cấu trúc của CLI đơn giản như sau:

image.png

image.png

Package handler bao gồm các requests và responses, cũng như các quy tắc có sẵn của business ở usecases:

image.png
Cấu trúc API thường phức tạp hơn với 3 packages: handler, presenter và middleware.

image.png
Lớp presenters chịu trách nhiệm định dạng dữ liệu sinh ra giống như response bởi các handlers.

type User struct {
	ID        ID
	Email     string
	Password  string
	FirstName string
	LastName  string
	CreatedAt time.Time
	UpdatedAt time.Time
	Books     []ID
}

Nó sẽ được chuyển đổi thành:

type User struct {
	ID        entity.ID `json:"id"`
	Email     string    `json:"email"`
	FirstName string    `json:"first_name"`
	LastName  string    `json:"last_name"`
}

Điều này cho phép chúng ta kiểm soát 1 entity sẽ được cung cấp thông qua API.

Trong packages cuối cùng của API là các middlewares, được sử dụng bởi các endpoints:
image.png

Support packages

Chúng là các packages cung cấp các hàm dùng chung như mã hóa, logging, xử lý files,… Chúng là các tính năng không thuộc domain của ứng dụng, và tất cả các lớp đều có thể sử dụng chúng. Ngay cả các ứng dụng khác cũng có thể import và sử dụng các packages này.
image.png

2.Lợi ích của Clean Architecture

Clean Architecture mang lại những lợi ích sau:

  • Mạch lạc – dễ xem (bản gốc ghi screaming với dụng ý là chỉ cần nhìn cấu trúc package cũng có thể hiểu được mục đích và cơ chế hoạt động của ứng dụng)
  • Linh hoạt – thể hiện ở khả năng độc lập, không phụ thuộc vào framework, database, application server.
  • Dễ kiểm thử – testable

2.Hạn chế của Clean Architecture

Bên cạnh những lợi ích trên thì Clean Architecture còn những hạn chế sau:

  • Không thể sử dụng framework theo cách mỳ ăn liền- do luật dependency inversion.
  • Khó áp dụng
  • Indirect – quá nhiều interface?
  • Cồng kềnh – thể hiện ở việc có quá nhiều class so với các project cùng mục tiêu (tuy nhiên các class được thêm vào đều có chủ ý và đáp ứng đúng quy định khi triển khai kiến trúc)

Bài viết của mình được tổng hợp bởi các nguồn liên quan, hy vọng các bạn có thế hiểu thêm về Clean Architecture. Chúc mọi người ngày làm việc vui vẻ.

Tham khảo:

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ