0
0
Lập trình
Hưng Nguyễn Xuân 1
Hưng Nguyễn Xuân 1xuanhungptithcm

Giải Quyết Vấn Đề Kết Nối Module Trong PHP: Kiến Trúc Modular

Đăng vào 3 tuần trước

• 10 phút đọc

Giải Quyết Vấn Đề Kết Nối Module Trong PHP: Kiến Trúc Modular

Vấn Đề Khiến Tôi Mất Ngủ 😴

Là một lập trình viên PHP với nhiều năm kinh nghiệm, tôi đã xây dựng không ít ứng dụng phức tạp. Nhưng luôn có một vấn đề kiến trúc khiến tôi cảm thấy khó chịu:

Ranh giới giữa các module trong ứng dụng PHP thường không rõ ràng và dễ bị phá vỡ.

Điều này có nghĩa là:

php Copy
class OrderService
{
    public function __construct(
        private UserService $userService,
        private PaymentService $paymentService,
        private InventoryService $inventory,
        private EmailService $emailService
    ) {}
}

Nhưng điều gì sẽ xảy ra khi:

  • Ai đó tái cấu trúc UserService mà không biết rằng OrderService phụ thuộc vào nó?
  • Bạn muốn kiểm tra OrderService nhưng lại phải giả lập 4 module khác nhau?
  • Bạn cần tách module thanh toán thành một microservice?
  • Một lập trình viên mới gia nhập mà không thể phân biệt cái gì phụ thuộc vào cái gì?

Các phụ thuộc này không rõ ràng và bị ẩn giấu. Container DI của bạn biết về chúng, nhưng mã nguồn không làm cho chúng trở nên rõ ràng.

Cảm Hứng: Học Hỏi Từ Các Hệ Sinh Thái Khác 💡

Xuất phát từ nền tảng kỹ thuật mạng và làm việc với Angular ở phía frontend, tôi đã thấy cách mà các hệ sinh thái khác xử lý vấn đề này:

Các Module trong Angular

typescript Copy
@NgModule({
  imports: [CommonModule, UserModule],     // Nhập khẩu rõ ràng
  exports: [OrderComponent, OrderService], // Xuất khẩu rõ ràng
  providers: [OrderService]                // Dịch vụ nội bộ
})
export class OrderModule { }

OSGi (Java)

java Copy
// Bundle rõ ràng khai báo những gì nó nhập khẩu/xuất khẩu
Import-Package: com.example.user.service
Export-Package: com.example.order.service

Mô hình rõ ràng: các hệ thống module thành công làm cho các phụ thuộc trở nên rõ ràng.

Giải Pháp Của Tôi: PowerModules Framework 🚀

Tôi quyết định mang những mô hình này vào PHP. Đây là những gì tôi đã xây dựng:

1. Mỗi Module Có Container DI Riêng

Thay vì một container toàn cầu, mỗi module có không gian riêng biệt:

php Copy
class OrderModule implements PowerModule 
{
    public function register(ConfigurableContainerInterface $container): void 
    {
        // Container này chỉ dành cho OrderModule
        $container->set(OrderService::class, OrderService::class);
        $container->set(OrderRepository::class, OrderRepository::class);
        // Các dịch vụ này là riêng tư cho module này theo mặc định
    }
}

2. Hợp Đồng Xuất Khẩu Rõ Ràng

Nếu bạn muốn chia sẻ một dịch vụ, bạn phải xuất khẩu rõ ràng:

php Copy
class UserModule implements PowerModule, ExportsComponents
{
    public static function exports(): array 
    {
        return [
            UserService::class,  // Chỉ cái này có sẵn cho các module khác
        ];
    }

    public function register(ConfigurableContainerInterface $container): void 
    {
        $container->set(UserService::class, UserService::class);
        $container->set(PasswordHasher::class, PasswordHasher::class); // Riêng tư!
    }
}

3. Hợp Đồng Nhập Khẩu Rõ Ràng

Nếu bạn muốn sử dụng dịch vụ của module khác, bạn phải nhập khẩu rõ ràng:

php Copy
class OrderModule implements PowerModule, ImportsComponents
{
    public static function imports(): array 
    {
        return [
            ImportItem::create(UserModule::class, UserService::class),
            ImportItem::create(PaymentModule::class, PaymentService::class),
        ];
    }

    public function register(ConfigurableContainerInterface $container): void 
    {
        // Bây giờ UserService và PaymentService có sẵn để tiêm
        $container->set(OrderService::class, OrderService::class)
            ->addArguments([
                UserService::class,
                PaymentService::class,
            ]);
    }
}

Điều kỳ diệu: Các phụ thuộc của bạn bây giờ được hiển thị trong mã của bạn, không bị ẩn trong các tệp cấu hình!

Mô Hình PowerModuleSetup ⚡

Đây là nơi trở nên thú vị. Làm thế nào để thêm chức năng chéo (như định tuyến, sự kiện, ghi log) cho TẤT CẢ các module mà không phá vỡ sự đóng gói?

Tôi đã tạo ra mô hình PowerModuleSetup:

php Copy
class RoutingSetup implements CanSetupPowerModule
{
    public function setup(PowerModuleSetupDto $dto): void 
    {
        // Điều này chạy cho MỌI module trong quá trình xây dựng ứng dụng
        if ($dto->powerModule instanceof HasRoutes) {
            // Lấy tất cả các route được định nghĩa trong module này và đăng ký chúng với router
            $this->registerRoutes($dto->powerModule, $dto->moduleContainer);
        }
    }
}

// Sử dụng
$app = new ModularAppBuilder(__DIR__)
    ->withModules(UserModule::class, OrderModule::class)
    ->addPowerModuleSetup(new RoutingSetup())  // Mở rộng TẤT CẢ các module với HasRoutes interface
    ->build();

Mô hình này cho phép các phần mở rộng hoạt động trên tất cả các module trong khi vẫn duy trì sự cô lập. Đây là cách mà phần mở rộng power-modules/router hoạt động, và nó cũng hình thành nền tảng cho hệ thống xuất khẩu/nhập khẩu rõ ràng của framework. Cả các tính năng chéo và mối quan hệ module đều được đảm bảo thông qua PowerModuleSetup, đảm bảo sự đóng gói và khả năng nhìn thấy đồng thời.

Xây Dựng Ứng Dụng: API Lưu Loát 🏗️

Kết hợp tất cả:

php Copy
$app = new ModularAppBuilder(__DIR__)
    ->withConfig(Config::forAppRoot(__DIR__))
    ->withModules(
        AuthModule::class,
        UserModule::class, 
        OrderModule::class,
        PaymentModule::class
    )
    ->addPowerModuleSetup(new RoutingSetup())
    ->addPowerModuleSetup(new EventSetup())
    ->build();

// Truy cập bất kỳ dịch vụ nào đã xuất khẩu
$orderService = $app->get(OrderService::class);

Những Gì Tôi Học Được Khi Xây Dựng Điều Này 🎓

1. Giải Quyết Phụ Thuộc Là Phức Tạp

Tôi đã phải triển khai sắp xếp topo để xử lý các phụ thuộc module:

  • Xây dựng đồ thị phụ thuộc từ các câu lệnh nhập khẩu
  • Phát hiện các phụ thuộc vòng
  • Sắp xếp các module theo thứ tự tải đúng
  • Lưu trữ kết quả để tối ưu hiệu suất

2. Tải Hai Giai Đoạn Là Cần Thiết

  • Giai đoạn 1: Đăng ký tất cả các module và thu thập các xuất khẩu
  • Giai đoạn 2: Giải quyết các nhập khẩu và áp dụng các phần mở rộng PowerModuleSetup

Điều này đảm bảo tất cả các xuất khẩu có sẵn trước khi bất kỳ nhập khẩu nào cố gắng giải quyết chúng.

3. Cấu Trúc Container Quan Trọng

Copy
Container Gốc
├── Container Module A (cô lập)
├── Container Module B (cô lập)
└── Dịch Vụ Xuất Khẩu (bí danh cho container module)

Mỗi container của module hoàn toàn cô lập, nhưng các dịch vụ xuất khẩu có thể truy cập thông qua container gốc.

4. Rõ Ràng Thì Tốt Hơn Mờ Nhạt

Các hợp đồng nhập khẩu/xuất khẩu làm cho kiến trúc của bạn trở nên rõ ràng:

  • Các lập trình viên mới có thể thấy mối quan hệ giữa các module ngay lập tức
  • Tái cấu trúc trở nên an toàn hơn với các phụ thuộc rõ ràng
  • Kiểm tra trở nên dễ dàng hơn với các ranh giới rõ ràng

Tác Động Thực Tế 📊

Dưới đây là những gì cách tiếp cận này cho phép:

Khả Năng Mở Rộng Nhóm Tốt Hơn

php Copy
// Nhóm A sở hữu AuthModule
class AuthModule implements ExportsComponents {
    public static function exports(): array {
        return [
            UserService::class,
            AuthMiddleware::class
        ];
    }
}

// Nhóm B sở hữu OrderModule  
class OrderModule implements ImportsComponents {
    public static function imports(): array {
        return [
            ImportItem::create(AuthModule::class, UserService::class),
        ];
    }
}

Các nhóm có thể làm việc độc lập với các hợp đồng rõ ràng giữa các module.

Kiểm Tra Dễ Dàng Hơn

php Copy
// Kiểm tra OrderModule một cách độc lập
$testApp = new ModularAppBuilder(__DIR__)
    ->withModules(
        MockUserModule::class,  // Mô phỏng
        OrderModule::class      // Thực thi thực tế
    )
    ->build();

Sự Tiến Hóa Microservice

php Copy
// Hôm nay: Modular monolith
ImportItem::create(UserModule::class, UserService::class)

// Ngày mai: Gọi API HTTP
ImportItem::create(UserApiModule::class, UserService::class)

Các hợp đồng nhập khẩu/xuất khẩu tự nhiên trở thành hợp đồng API.

Chi Tiết Kỹ Thuật 🔧

Thông số Framework:

  • ~1,600 dòng mã nguồn chính
  • PHPStan level 8 (phân tích tĩnh tối đa)
  • PHP 8.4+ với kiểu dữ liệu nghiêm ngặt
  • Độ bao phủ kiểm tra toàn diện
  • Tương thích container PSR-11
  • Giấy phép MIT

Các Thành Phần Chính:

  • PowerModule: Giao diện module chính
  • ConfigurableContainer: Container DI tùy chỉnh với chuỗi phương thức
  • ModularAppBuilder: Xây dựng ứng dụng lưu loát
  • PowerModuleSetup: Hệ thống mở rộng
  • ImportItem: Khai báo phụ thuộc

So Sánh Với Các Giải Pháp Hiện Có 🔍

So với Symfony Bundles

php Copy
// Symfony (các phụ thuộc mờ nhạt trong services.yaml)
class OrderController {
    // Các phụ thuộc được cấu hình trong YAML, không rõ ràng trong mã
}

// PowerModules (các nhập khẩu rõ ràng trong mã)  
class OrderModule implements ImportsComponents {
    public static function imports(): array {
        return [
            ImportItem::create(UserModule::class, UserService::class),
        ];
    }
}

So với Laravel Service Providers

php Copy
// Laravel (container chia sẻ)
App::bind(OrderService::class, function($app) {
    return new OrderService($app->make(UserService::class));
});

// PowerModules (containers cô lập)
$container->set(OrderService::class, OrderService::class)
    ->addArguments([UserService::class]); // Được giải quyết từ container của module

Hãy Thử Ngay 🚀

bash Copy
composer require power-modules/framework

Ví dụ cơ bản:

php Copy
class MyModule implements PowerModule, ExportsComponents
{
    public static function exports(): array
    {
        return [MyService::class];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        $container->set(MyService::class, MyService::class);
    }
}

$app = new ModularAppBuilder(__DIR__)
    ->withModules(MyModule::class)
    ->build();

$service = $app->get(MyService::class);
// $service đã sẵn sàng để sử dụng, và tính năng tự động hoàn thành của IDE hoạt động!

Điều Gì Tiếp Theo? 🔮

Tôi đang làm việc trên:

  • power-modules/events: Mở rộng kiến trúc theo sự kiện
  • Tài liệu tốt hơn: Nhiều ví dụ và mô hình hơn
  • Tối ưu hóa hiệu suất: Bộ nhớ cache cho các route module
  • ... Gợi ý luôn được hoan nghênh!

Kết Luận 💭

Xây dựng framework này đã dạy tôi nhiều hơn về tiêm phụ thuộc, hệ thống module và các mẫu kiến trúc hơn nhiều năm chỉ sử dụng các công cụ hiện có.

Những điểm quan trọng:

  • Phụ thuộc rõ ràng thì tốt hơn phụ thuộc mờ nhạt
  • Ranh giới giữa các module nên được thực thi, không chỉ là quy ước
  • Các vấn đề chéo có thể được thêm vào mà không phá vỡ sự đóng gói
  • Kiến trúc tốt hỗ trợ sự tiến hóa (monolith → microservices)

Có phải nó mang tính cách mạng? Không - những mô hình này đã tồn tại trong các hệ sinh thái khác.

Có phải nó hữu ích? Tôi nghĩ vậy - nó giải quyết những vấn đề thực sự mà tôi đã đối mặt trong các ứng dụng PHP phức tạp.

Tài Nguyên 📚

  • GitHub: power-modules/framework
  • Packagist: power-modules/framework
  • Mở rộng Router: power-modules/router
  • Trường Hợp & Ví Dụ: Trường Hợp & Ví Dụ

Bạn đang đối mặt với những thách thức kiến trúc nào trong các dự án PHP của bạn? Bạn đã thử những cách tiếp cận tương tự chưa? Tôi rất muốn nghe ý kiến của bạn trong phần bình luận! 💬

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào