VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Kiến trúc và chất lượng codeKiến trúc ứng dụng
Bài 16/17
6 phút

Clean Architecture

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Chia tầng xong vẫn còn một câu hỏi: tầng nghiệp vụ gọi xuống tầng dữ liệu, vậy nó có phụ thuộc vào Oracle không? Nếu có, đổi database là phải sửa cả quy tắc nghiệp vụ. Clean Architecture đảo chiều phụ thuộc để phần lõi không biết gì về database hay giao diện.

Khái niệm

🧅 Clean Architecture: cách tổ chức ứng dụng đặt nghiệp vụ ở lõi, database và giao diện ở vòng ngoài, mọi phụ thuộc chỉ hướng từ ngoài vào lõi.

Vòng Chứa gì Được phép biết
Core (lõi) Product, ProductStore, OrderService không biết vòng nào khác
Infrastructure ProductRepository, DbProductStore, Spring Data JPA Core
Giao diện controller của shop-api Core và Infrastructure

Điểm mấu chốt: ProductStore nằm trong Core (không còn ở tầng dữ liệu như bài trước), còn class implement nó nằm ở Infrastructure. Đây là DIP của khoá OOP, áp dụng cho cả ứng dụng.

Trong shop-api, mỗi vòng là một package: com.shop.api.core, com.shop.api.infrastructure, còn controller ở lại com.shop.api. Class không ghi public chỉ dùng được trong package của nó, nên kiểu nào được dùng từ package khác phải ghi public.

Controller infrastructure: DbProductStore core: OrderService, ProductStore
Phụ thuộc hướng vào lõi

Ví dụ

Đặt hàng xong cần gửi email. Core cần "gửi email" nhưng không được biết máy chủ email cụ thể, nên Core khai báo interface:

// shop-api
// EmailSender.java
package com.shop.api.core;

public interface EmailSender {
    void send(String to, String message);
}

// CheckoutService.java
package com.shop.api.core;

import org.springframework.stereotype.*;

@Service
public class CheckoutService {
    private final EmailSender sender;

    public CheckoutService(EmailSender sender) {
        this.sender = sender;
    }

    public void complete(
            String orderCode, String email) {
        sender.send(email, "Đã nhận đơn " + orderCode);
    }
}

Infrastructure implement interface đó bằng công nghệ cụ thể:

// shop-api
// ConsoleEmailSender.java
package com.shop.api.infrastructure;

import com.shop.api.core.*;
import org.springframework.stereotype.*;

@Service
public class ConsoleEmailSender implements EmailSender {
    @Override
    public void send(String to, String message) {
        System.out.println(
                "Gửi %s: %s".formatted(to, message));
    }
}
  • CheckoutService chỉ biết EmailSender. Nó không biết email gửi bằng gì.
  • Đổi sang dịch vụ email khác chỉ cần viết một class mới ở Infrastructure, rồi bỏ @Service ở class cũ.
  • Hai phần được ghép bởi Spring container như bài Dependency injection trong Spring: ConsoleEmailSender có @Service, nên CheckoutService nhận được nó qua constructor.

Thử ngay

Thêm class test này vào src/test/java/com/shop/api/ rồi chạy ./mvnw test. Test tự ghép hai phần bằng new, không cần Spring:

// shop-api
// CheckoutTests.java
package com.shop.api;

import com.shop.api.core.*;
import com.shop.api.infrastructure.*;
import org.junit.jupiter.api.*;

class CheckoutTests {
    @Test
    public void completeSendsEmail() {
        EmailSender sender = new ConsoleEmailSender();
        var checkout = new CheckoutService(sender);
        checkout.complete("DH1", "an@shop.vn");
    }
}

Đoán trước khi chạy: dòng in ra là gì, và class nào in nó?

Xem kết quả
[INFO] Running com.shop.api.CheckoutTests
Gửi an@shop.vn: Đã nhận đơn DH1
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.056 s -- in com.shop.api.CheckoutTests

CheckoutService của Core gọi send qua interface, còn ConsoleEmailSender của Infrastructure mới là class in ra.

Lỗi hay gặp

Đặt interface ở Infrastructure. Core phải import Infrastructure để lấy interface, tức lõi lại phụ thuộc vòng ngoài và kéo theo Spring Data JPA, Oracle.

# SAI — lõi phụ thuộc vòng ngoài
core chứa: OrderService
infrastructure chứa: ProductStore, DbProductStore
core import infrastructure
# ĐÚNG — interface ở lõi, vòng ngoài implement
core chứa: ProductStore, OrderService
infrastructure chứa: DbProductStore
infrastructure import core

Tóm tắt

  • Core chứa entity, interface và quy tắc nghiệp vụ, không biết database hay giao diện.
  • Infrastructure implement các interface của Core bằng công nghệ cụ thể.
  • Phụ thuộc chỉ hướng từ ngoài vào lõi.
  • Interface nằm ở Core, class implement nằm ở vòng ngoài. Kiểu dùng từ package khác phải public.

Tự kiểm tra

0/3 câu
Câu 1

Cửa hàng chuyển từ Oracle sang PostgreSQL. Với Clean Architecture, phần nào phải sửa?

Câu 2

ProductStore nên đặt ở package nào?

Câu 3

Package core có nên import org.springframework.data.jpa.repository không?

Chia tầngTách nhiều module Maven

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt