Skip to main content

Component-Based Architecture

Component-Based Architecture (CBA)"A software engineering paradigm that emphasizes the decomposition of systems into independent, reusable, replaceable, and composable components, each encapsulating a set of related functionality with well-defined interfaces." — Clemens Szyperski, 1997

Tổng quan

Component-Based Architecture (CBA) là một kiến trúc phần mềm trong đó hệ thống được xây dựng từ các component độc lập, có thể tái sử dụng, thay thế được, và có thể kết hợp với nhau thông qua các interface được định nghĩa rõ ràng. Mỗi component là một đơn vị triển khai độc lập, chứa cả code lẫn dữ liệu (hoặc reference đến dữ liệu) cho một chức năng cụ thể.

Khác với Object-Oriented Programming (OOP) nơi đơn vị cơ bản là object (instance của class), CBA lấy component làm đơn vị kiến trúc cơ bản. Một component thường chứa nhiều class, resources, configuration, và đôi khi là toàn bộ một subsystem.

Nguồn gốc và lịch sử:

  • 1986 — Brad Cox: Khái niệm "Software IC" — phần mềm như chip điện tử, có thể cắm ghép
  • 1990s — Microsoft COM/DCOM: Component Object Model — binary standard cho component interaction
  • 1996 — Sun JavaBeans: Component model cho Java
  • 1998 — CORBA Component Model (CCM): OMG standard cho distributed components
  • 2000s — Enterprise JavaBeans (EJB): Component model cho enterprise Java
  • 2000s — .NET Components: Assembly, NuGet packages
  • 2010s — Microservices: Kiến trúc component ở cấp độ service
  • 2020s — Web Components: W3C standard cho reusable UI components

Những người tiên phong:

TênĐóng góp
Brad Cox"Software IC" concept — nền tảng cho CBA
Clemens SzyperskiĐịnh nghĩa chuẩn về Component Software
Grady BoochUML — component diagram notation
Robert C. MartinSOLID principles — nền tảng cho component design
Ivar JacobsonComponent-Based Software Engineering (CBSE)

Phân biệt với các khái niệm liên quan:

Khái niệmSo với Component
Object (OOP)Component CHỨA nhiều objects. Component là deployment unit, object là runtime unit.
ModuleModule là logical grouping. Component là physical deployment unit (JAR, DLL, package)
MicroserviceMicroservice là component triển khai độc lập qua network. Component có thể in-process.
PackagePackage là distribution unit. Component là runtime unit + deployment unit.

Bài toán

Vấn đề 1: Tái sử dụng code thất bại

Một tập đoàn tài chính có 5 sản phẩm phần mềm khác nhau: Internet Banking, Mobile Banking, Core Banking, CRM, và Risk Management. Mỗi sản phẩm đều cần xử lý "User Authentication" và "Transaction Logging". Nhưng mỗi đội implement riêng:

  • Team A dùng bcrypt + PostgreSQL
  • Team B dùng scrypt + MongoDB
  • Team C dùng SHA-256 + file log

Kết quả: code trùng lặp, security inconsistency, khó audit, lãng phí 2000+ giờ dev mỗi năm. CBA giải quyết bằng cách đóng gói Authentication và Logging thành shared components. Mỗi sản phẩm chỉ cần khai báo dependency, import component, configure, và dùng. Component được maintain bởi một team riêng, security best practices được áp dụng một lần.

Vấn đề 2: "Big ball of mud" — dependency hell

Kiến trúc monolithic truyền thống dẫn đến circular dependencytight coupling:

  • AuthService import UserService
  • UserService import OrderService
  • OrderService import AuthService (xác thực đơn hàng)

Kết quả: không thể test riêng module nào, không thể deploy riêng module nào, không thể hiểu system structure. Mỗi lần thay đổi đều sợ side effect.

CBA giải quyết bằng component graph với directed acyclic dependencies:

  • Component A → Component B (A depends on B)
  • Không có circular dependency
  • Mỗi component có interface rõ ràng, implementation thay thế được

Vấn đề 3: Deployment không linh hoạt

Một hệ thống ERP có 50+ modules. Mỗi lần bug fix nhỏ ở module "Inventory", phải redeploy toàn bộ ERP (200MB WAR/React build). Thời gian deploy: 30 phút, downtime: 5 phút. Nếu deploy 2 lần/tuần → 10 phút downtime/tuần → 8+ giờ downtime/năm.

Với CBA, mỗi component được deploy độc lập:

  • Fix Inventory component → chỉ deploy Inventory component (5MB)
  • Downtime: 0 (hot-reload hoặc rolling update)
  • Risk: thấp (chỉ ảnh hưởng Inventory)

Vấn đề 4: Third-party integration không kiểm soát

Công ty bảo hiểm tích hợp với 20+ đối tác (Bảo Việt, Bảo Minh, PVI, PTI, ...). Mỗi đối tác có API khác nhau, format khác nhau (SOAP, REST, custom XML). Nếu code tích hợp lẫn trong core business logic, khi một đối tác thay đổi API, phải sửa core code, test toàn bộ, redeploy toàn bộ.

CBA tách mỗi tích hợp thành một adapter component:

  • Core business logic chỉ giao tiếp qua interface InsuranceProvider
  • Mỗi đối tác có component riêng implement interface đó
  • Đối tác thay đổi → chỉ sửa component của đối tác đó
  • Thêm đối tác mới → thêm component mới, không sửa core

Vấn đề 5: Testing khó do tight coupling

Trong monolithic, để test processOrder(), bạn cần:

  • Database thật hoặc mock phức tạp
  • Payment gateway sandbox
  • Email server
  • Shipping API

Setup mất 30 phút, test chạy 10 phút. Developer ngại viết test.

CBA cho phép component isolation testing:

  • Mỗi component test riêng với mock của dependencies
  • Component có thể test trong isolation (hexagonal testing)
  • Integration test: chỉ test component interaction, không phải implementation

Nguyên lý thiết kế

1. Component Contract — Interface trước, Implementation sau

Mỗi component có:

  • Provided Interface — service component cung cấp cho component khác
  • Required Interface — service component cần từ component khác
┌──────────────┐ ┌──────────────┐
│ Component A │ ──uses──▶ Component B │
│ │ │ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │Provided│ │ │ │Provided│ │
│ │Interface│ │ │ │Interface│ │
│ └────────┘ │ │ └────────┘ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │Required│ │ │ │Required│ │
│ └────────┘ │ │ └────────┘ │
└──────────────┘ └──────────────┘

2. Design by Contract (DbC)

Bertrand Meyer's Design by Contract:

  • Precondition: Điều kiện để gọi component method (input validation)
  • Postcondition: Đảm bảo sau khi method chạy xong (output guarantee)
  • Invariant: Điều kiện luôn đúng trong suốt vòng đời component

3. Component Composition

Component được kết hợp theo ba cách:

PatternMô tảVí dụ
SequentialComponent A gọi B, B gọi CPipeline, workflow
HierarchicalComponent chứa component conUI component tree
Peer-to-peerCác component cùng level, gọi nhau qua interfaceMicroservices

4. Component Granularity

Cấp độKích thướcVí dụ
Fine-grained1-5 classesLogger, Validator, Mapper
Medium-grained5-20 classesUserService, PaymentProcessor
Coarse-grained20+ classesAuthentication Component, Reporting Component
Very coarseWhole subsystemERP Module, CRM System

Nguyên tắc: Component đủ nhỏ để dễ hiểu, đủ lớn để có ý nghĩa business.

5. Component Discovery

Component có thể được khám phá:

  • Static: Import trực tiếp (import package)
  • Dynamic: Plugin/SPI discovery
  • Registry-based: Component registry (Spring context, IoC container)
  • Service discovery: DNS, Consul, Eureka (cho distributed components)

Cấu trúc chi tiết

Component Structure

Một component thường có cấu trúc:

component-name/
├── api/ # Provided interfaces + DTOs
│ ├── __init__.py
│ ├── interfaces.py # Abstract interfaces (SPI)
│ └── models.py # Data transfer objects, value objects
├── internal/ # Implementation (private)
│ ├── __init__.py
│ ├── impl.py # Implementation của interfaces
│ ├── repository.py # Data access
│ └── validators.py # Business validation
├── spi/ # Required interfaces (what this component needs)
│ ├── __init__.py
│ └── required_interfaces.py
├── config/ # Component configuration
│ ├── __init__.py
│ └── settings.py
├── exceptions/ # Component-specific exceptions
│ └── __init__.py
├── tests/ # Component tests
│ ├── unit/
│ └── integration/
├── pyproject.toml # Package metadata + dependencies
└── README.md

Core Components (E-Commerce Example)

ComponentProvided InterfaceDependenciesResponsibility
UserComponentIUserServiceUser CRUD, authentication, authorization
ProductComponentIProductServiceProduct catalog, inventory, pricing
OrderComponentIOrderServiceIUserService, IProductServiceOrder lifecycle management
PaymentComponentIPaymentServicePayment gateway integration
ShippingComponentIShippingServiceIOrderServiceShipping calculation, logistics
NotificationComponentINotificationServiceIUserComponentEmail, SMS, push notifications
AnalyticsComponentIAnalyticsServiceIOrderService, IProductServiceReporting, BI

Dependency Graph

UserComponent ProductComponent
│ │
└──────────┬───────────┘

OrderComponent
│ │
│ │
PaymentComp NotificationComp

ShippingComp

Sơ đồ kiến trúc (ASCII)

┌──────────────────────────────────────────────────────────────────────────┐
│ COMPONENT-BASED ARCHITECTURE │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ UserComponent │ │ ProductComponent │ │ ConfigComponent │ │
│ │ ────────────── │ │ ──────────────── │ │ ─────────────── │ │
│ │ □ IUserService │ │ □ IProductService │ │ □ IConfigService │ │
│ │ ────────────── │ │ ──────────────── │ │ ─────────────── │ │
│ │ + register() │ │ + getProduct() │ │ + getConfig() │ │
│ │ + authenticate()│ │ + search() │ │ + updateConfig() │ │
│ │ + authorize() │ │ + updateStock() │ │ │ │
│ └────────┬─────────┘ └────────┬──────────┘ └──────────────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌───────────▼────────────┐ ┌──────────────────┐ │
│ │ OrderComponent │ │ NotificationComp │ │
│ │ ────────────────── │ │ ─────────────── │ │
│ │ □ IOrderService │◄───│ □ INotifyService │ │
│ │ ────────────────── │ │ ─────────────── │ │
│ │ + createOrder() │ │ + sendEmail() │ │
│ │ + processPayment() │ │ + sendSMS() │ │
│ │ + cancelOrder() │ │ + sendPush() │ │
│ └────────────────────────┘ └──────────────────┘ │
│ │ │
│ ┌───────────▼────────────┐ ┌──────────────────┐ │
│ │ PaymentComponent │ │ ShippingComp │ │
│ │ ────────────────── │ │ ─────────────── │ │
│ │ □ IPaymentService │ │ □ IShippingSvc │ │
│ │ ────────────────── │ │ ─────────────── │ │
│ │ + processPayment() │ │ + calculate() │ │
│ │ + refund() │ │ + createShip() │ │
│ │ + validateCard() │ │ + trackShipment() │ │
│ └────────────────────────┘ └──────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ COMPOSITION ROOT │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ DI Container │ │ Component │ │ Wiring │ │ │
│ │ │ (Autofac, │ │ Registry │ │ (Assembler) │ │ │
│ │ │ Spring) │ │ │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘

Ví dụ code hoàn chỉnh

Cách làm sai: Monolithic tightly-coupled

from __future__ import annotations
from typing import Any


class MonolithicOrderProcessor:
"""Mọi thứ trong một class — tightly coupled, không thể test riêng."""

def process_order(self, order_id: str, user_id: str, items: list[dict], payment_info: dict) -> dict:
# User validation
user = self._get_user_from_db(user_id)
if not user or user["status"] != "active":
return {"error": "User not found or inactive"}

# Product validation
total = 0
for item in items:
product = self._get_product_from_db(item["product_id"])
if not product or product["stock"] < item["quantity"]:
return {"error": f"Product {item['product_id']} out of stock"}
total += product["price"] * item["quantity"]

# Payment
payment_result = self._call_payment_gateway(payment_info, total)
if payment_result.get("status") != "success":
return {"error": "Payment failed"}

# Create order in DB
order = self._save_order_to_db(order_id, user_id, items, total, payment_result)

# Send email
self._send_email(user["email"], "Order Confirmed", f"Your order {order_id} is confirmed")

return {"order_id": order_id, "total": total, "status": "confirmed"}

def _get_user_from_db(self, user_id: str) -> dict | None:
return {"id": user_id, "name": "Test", "email": "test@test.com", "status": "active"}

def _get_product_from_db(self, product_id: str) -> dict | None:
return {"id": product_id, "name": "Product", "price": 100.0, "stock": 10}

def _call_payment_gateway(self, payment_info: dict, amount: float) -> dict:
return {"status": "success", "transaction_id": "TXN123"}

def _save_order_to_db(self, order_id: str, user_id: str, items: list, total: float, payment: dict) -> dict:
return {"order_id": order_id, "user_id": user_id, "total": total}

def _send_email(self, to: str, subject: str, body: str) -> None:
pass

Cách làm đúng: Component-Based Architecture

from __future__ import annotations
import time
import logging
from dataclasses import dataclass, field
from typing import Any, Callable, Optional, Protocol, TypeVar
from enum import Enum, auto
from abc import ABC, abstractmethod
from collections import defaultdict
import threading

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)


# ======================================================================
# 1. CROSS-CUTTING TYPES
# ======================================================================

class ComponentState(Enum):
CREATED = auto()
INITIALIZED = auto()
STARTED = auto()
STOPPED = auto()
FAILED = auto()


@dataclass
class Address:
street: str
city: str
state: str
zip_code: str
country: str = "VN"


@dataclass
class Money:
amount: float
currency: str = "VND"

def __add__(self, other: Money) -> Money:
if self.currency != other.currency:
raise ValueError(f"Currency mismatch: {self.currency} vs {other.currency}")
return Money(self.amount + other.amount, self.currency)

def __mul__(self, factor: float) -> Money:
return Money(self.amount * factor, self.currency)


@dataclass
class OrderItem:
product_id: str
name: str
price: Money
quantity: int

@property
def subtotal(self) -> Money:
return self.price * self.quantity


@dataclass
class Order:
order_id: str
user_id: str
items: list[OrderItem]
shipping_address: Address
subtotal: Money = field(default_factory=lambda: Money(0))
shipping_fee: Money = field(default_factory=lambda: Money(0))
tax: Money = field(default_factory=lambda: Money(0))
discount: Money = field(default_factory=lambda: Money(0))
total: Money = field(default_factory=lambda: Money(0))
status: str = "pending"
payment_id: str = ""
tracking_id: str = ""
created_at: float = field(default_factory=time.time)

def calculate_total(self) -> None:
self.total = self.subtotal + self.shipping_fee + self.tax - self.discount


class OrderStatus(Enum):
PENDING = "pending"
PAID = "paid"
PROCESSING = "processing"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
REFUNDED = "refunded"


# ======================================================================
# 2. COMPONENT MODEL — COMPOSITION ROOT
# ======================================================================

T = TypeVar("T")


class ComponentRegistry:
"""Central registry — quản lý component instances và lifecycle."""

def __init__(self) -> None:
self._instances: dict[type, Any] = {}
self._named_instances: dict[str, Any] = {}
self._states: dict[str, ComponentState] = {}
self._lock = threading.RLock()

def register(self, interface: type, implementation: Any, name: str = "") -> None:
with self._lock:
self._instances[interface] = implementation
if name:
self._named_instances[name] = implementation
impl_name = name or interface.__name__
self._states[impl_name] = ComponentState.CREATED
logger.debug("Registered: %s → %s", interface.__name__, type(implementation).__name__)

def resolve(self, interface: type, name: str = "") -> Any:
with self._lock:
if name:
return self._named_instances.get(name)
return self._instances.get(interface)

def resolve_all(self, interface: type) -> list[Any]:
with self._lock:
return [impl for iface, impl in self._instances.items()
if issubclass(iface, interface) or iface == interface]

def set_state(self, name: str, state: ComponentState) -> None:
with self._lock:
self._states[name] = state

def get_state(self, name: str) -> ComponentState:
return self._states.get(name, ComponentState.CREATED)

def initialize_all(self) -> None:
for name, impl in list(self._named_instances.items()) + \
[(t.__name__, i) for t, i in self._instances.items()
if t.__name__ not in self._named_instances]:
if hasattr(impl, "initialize"):
try:
impl.initialize(self)
self.set_state(name, ComponentState.INITIALIZED)
logger.info("Initialized component: %s", name)
except Exception as e:
self.set_state(name, ComponentState.FAILED)
logger.error("Failed to initialize component %s: %s", name, e)

def start_all(self) -> None:
for name, impl in list(self._named_instances.items()) + \
[(t.__name__, i) for t, i in self._instances.items()
if t.__name__ not in self._named_instances]:
if hasattr(impl, "start"):
try:
impl.start()
self.set_state(name, ComponentState.STARTED)
logger.info("Started component: %s", name)
except Exception as e:
logger.error("Failed to start component %s: %s", name, e)

def stop_all(self) -> None:
for name, impl in list(self._named_instances.items()) + \
[(t.__name__, i) for t, i in self._instances.items()
if t.__name__ not in self._named_instances]:
if hasattr(impl, "stop"):
try:
impl.stop()
self.set_state(name, ComponentState.STOPPED)
logger.info("Stopped component: %s", name)
except Exception as e:
logger.error("Failed to stop component %s: %s", name, e)


class Component(ABC):
"""Abstract base cho mọi component — có lifecycle."""

@abstractmethod
def initialize(self, registry: ComponentRegistry) -> None:
...

def start(self) -> None:
...

def stop(self) -> None:
...


# ======================================================================
# 3. COMPONENT: USER COMPONENT
# ======================================================================

@dataclass
class User:
user_id: str
email: str
name: str
role: str = "customer" # customer, admin, manager
is_active: bool = True
created_at: float = field(default_factory=time.time)


class IUserService(Protocol):
"""Provided interface của UserComponent."""

def register(self, email: str, name: str, password: str) -> User: ...
def authenticate(self, email: str, password: str) -> User | None: ...
def get_user(self, user_id: str) -> User | None: ...
def get_users_by_role(self, role: str) -> list[User]: ...
def deactivate_user(self, user_id: str) -> bool: ...


class UserComponent(Component):
"""User management component."""

def __init__(self) -> None:
self._users: dict[str, User] = {}
self._email_index: dict[str, User] = {}
self._lock = threading.RLock()
self._initialized = False

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("UserComponent initializing...")
registry.register(IUserService, self)
# Seed data
self._users["user_001"] = User("user_001", "alice@example.com", "Alice", "customer")
self._users["user_002"] = User("user_002", "bob@example.com", "Bob", "customer")
self._users["user_003"] = User("user_003", "admin@example.com", "Admin", "admin")
for u in self._users.values():
self._email_index[u.email] = u
self._initialized = True
logger.info("UserComponent initialized with %d users", len(self._users))

def start(self) -> None:
logger.info("UserComponent started")

def stop(self) -> None:
with self._lock:
self._users.clear()
self._email_index.clear()
logger.info("UserComponent stopped")

def register(self, email: str, name: str, password: str) -> User:
with self._lock:
if email in self._email_index:
raise ValueError(f"Email already registered: {email}")
user_id = f"user_{int(time.time())}"
user = User(user_id=user_id, email=email, name=name)
self._users[user_id] = user
self._email_index[email] = user
logger.info("Registered user: %s (%s)", name, email)
return user

def authenticate(self, email: str, password: str) -> User | None:
if email in self._email_index:
user = self._email_index[email]
if user.is_active:
logger.info("Authenticated user: %s", email)
return user
return None

def get_user(self, user_id: str) -> User | None:
return self._users.get(user_id)

def get_users_by_role(self, role: str) -> list[User]:
return [u for u in self._users.values() if u.role == role]

def deactivate_user(self, user_id: str) -> bool:
user = self._users.get(user_id)
if user:
user.is_active = False
logger.info("Deactivated user: %s", user_id)
return True
return False


# ======================================================================
# 4. COMPONENT: PRODUCT CATALOG
# ======================================================================

@dataclass
class Product:
product_id: str
name: str
description: str
price: Money
category: str
stock: int
is_active: bool = True


class IProductService(Protocol):
"""Provided interface của ProductComponent."""

def get_product(self, product_id: str) -> Product | None: ...
def search_products(self, query: str, category: str = "") -> list[Product]: ...
def update_stock(self, product_id: str, quantity: int) -> bool: ...
def get_products_by_category(self, category: str) -> list[Product]: ...
def add_product(self, product: Product) -> None: ...


class ProductComponent(Component):
"""Product catalog component."""

def __init__(self) -> None:
self._products: dict[str, Product] = {}
self._lock = threading.RLock()

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("ProductComponent initializing...")
registry.register(IProductService, self)
# Seed data
self._products = {
"prod_001": Product("prod_001", "iPhone 15 Pro", "Latest Apple phone",
Money(27990000), "Electronics", 50),
"prod_002": Product("prod_002", "MacBook Pro 16", "Apple laptop M3 Pro",
Money(59990000), "Electronics", 20),
"prod_003": Product("prod_003", "AirPods Pro", "Wireless earbuds",
Money(5490000), "Electronics", 100),
"prod_004": Product("prod_004", "Samsung Galaxy S24", "Android flagship",
Money(21990000), "Electronics", 30),
"prod_005": Product("prod_005", "Sony WH-1000XM5", "Noise cancelling headphones",
Money(7990000), "Electronics", 40),
"prod_006": Product("prod_006", "Nike Air Max", "Running shoes",
Money(3500000), "Fashion", 60),
"prod_007": Product("prod_007", "Levi's 501", "Classic jeans",
Money(1200000), "Fashion", 80),
}
logger.info("ProductComponent initialized with %d products", len(self._products))

def start(self) -> None:
logger.info("ProductComponent started")

def stop(self) -> None:
with self._lock:
self._products.clear()
logger.info("ProductComponent stopped")

def get_product(self, product_id: str) -> Product | None:
return self._products.get(product_id)

def search_products(self, query: str, category: str = "") -> list[Product]:
query = query.lower()
results = []
with self._lock:
for product in self._products.values():
if (query in product.name.lower() or query in product.description.lower()):
if not category or product.category == category:
results.append(product)
return results

def update_stock(self, product_id: str, quantity: int) -> bool:
product = self._products.get(product_id)
if product and product.stock >= quantity:
product.stock -= quantity
logger.info("Stock updated: %s (-%d, remaining=%d)", product_id, quantity, product.stock)
return True
return False

def get_products_by_category(self, category: str) -> list[Product]:
return [p for p in self._products.values() if p.category == category]

def add_product(self, product: Product) -> None:
with self._lock:
self._products[product.product_id] = product


# ======================================================================
# 5. COMPONENT: PAYMENT PROCESSING
# ======================================================================

@dataclass
class PaymentTransaction:
transaction_id: str
order_id: str
amount: Money
method: str # credit_card, bank_transfer, momo, vnpay
status: str # pending, success, failed, refunded
gateway_response: dict[str, Any] = field(default_factory=dict)
created_at: float = field(default_factory=time.time)


class IPaymentService(Protocol):
"""Provided interface của PaymentComponent."""

def process_payment(self, order_id: str, amount: Money, method: str, payment_info: dict[str, Any]) -> PaymentTransaction: ...
def refund_payment(self, transaction_id: str) -> PaymentTransaction: ...
def get_transaction(self, transaction_id: str) -> PaymentTransaction | None: ...
def get_available_methods(self) -> list[str]: ...


class PaymentComponent(Component):
"""Payment processing component."""

def __init__(self) -> None:
self._transactions: dict[str, PaymentTransaction] = {}
self._lock = threading.RLock()
self._payment_methods = ["credit_card", "bank_transfer", "vnpay", "momo"]

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("PaymentComponent initializing...")
registry.register(IPaymentService, self)
logger.info("PaymentComponent initialized")

def start(self) -> None:
logger.info("PaymentComponent started")

def stop(self) -> None:
with self._lock:
self._transactions.clear()
logger.info("PaymentComponent stopped")

def process_payment(self, order_id: str, amount: Money, method: str, payment_info: dict[str, Any]) -> PaymentTransaction:
if method not in self._payment_methods:
raise ValueError(f"Unsupported payment method: {method}")
txn_id = f"TXN{int(time.time())}"
logger.info("Processing payment: order=%s method=%s amount=%.0f", order_id, method, amount.amount)
txn = PaymentTransaction(
transaction_id=txn_id,
order_id=order_id,
amount=amount,
method=method,
status="success",
gateway_response={"code": "00", "message": "Approved"},
)
with self._lock:
self._transactions[txn_id] = txn
logger.info("Payment success: %s (%.0f %s)", txn_id, amount.amount, amount.currency)
return txn

def refund_payment(self, transaction_id: str) -> PaymentTransaction:
txn = self._transactions.get(transaction_id)
if txn is None:
raise ValueError(f"Transaction not found: {transaction_id}")
if txn.status != "success":
raise ValueError(f"Cannot refund transaction with status: {txn.status}")
txn.status = "refunded"
logger.info("Payment refunded: %s", transaction_id)
return txn

def get_transaction(self, transaction_id: str) -> PaymentTransaction | None:
return self._transactions.get(transaction_id)

def get_available_methods(self) -> list[str]:
return list(self._payment_methods)


# ======================================================================
# 6. COMPONENT: SHIPPING
# ======================================================================

@dataclass
class ShippingQuote:
method: str
fee: Money
estimated_days: int
provider: str


@dataclass
class Shipment:
tracking_id: str
order_id: str
method: str
status: str
address: Address
created_at: float = field(default_factory=time.time)


class IShippingService(Protocol):
"""Provided interface của ShippingComponent."""

def get_quotes(self, items: list[OrderItem], address: Address) -> list[ShippingQuote]: ...
def create_shipment(self, order_id: str, method: str, address: Address) -> Shipment: ...
def track_shipment(self, tracking_id: str) -> dict[str, Any]: ...


class ShippingComponent(Component):
"""Shipping management component."""

def __init__(self) -> None:
self._shipments: dict[str, Shipment] = {}
self._lock = threading.RLock()

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("ShippingComponent initializing...")
registry.register(IShippingService, self)
logger.info("ShippingComponent initialized")

def start(self) -> None:
logger.info("ShippingComponent started")

def stop(self) -> None:
with self._lock:
self._shipments.clear()
logger.info("ShippingComponent stopped")

def get_quotes(self, items: list[OrderItem], address: Address) -> list[ShippingQuote]:
total_weight = sum(item.quantity for item in items)
return [
ShippingQuote("standard", Money(total_weight * 15000), 3, "GHN"),
ShippingQuote("express", Money(total_weight * 30000), 1, "GHN Express"),
ShippingQuote("economy", Money(total_weight * 10000), 5, "ViettelPost"),
]

def create_shipment(self, order_id: str, method: str, address: Address) -> Shipment:
tracking_id = f"SHIP{int(time.time())}"
with self._lock:
shipment = Shipment(tracking_id, order_id, method, "created", address)
self._shipments[tracking_id] = shipment
logger.info("Shipment created: %s for order %s (%s)", tracking_id, order_id, method)
return shipment

def track_shipment(self, tracking_id: str) -> dict[str, Any]:
shipment = self._shipments.get(tracking_id)
if not shipment:
raise ValueError(f"Shipment not found: {tracking_id}")
return {
"tracking_id": tracking_id,
"order_id": shipment.order_id,
"status": shipment.status,
"method": shipment.method,
"estimated_delivery": "3-5 business days",
}


# ======================================================================
# 7. COMPONENT: NOTIFICATION
# ======================================================================

@dataclass
class Notification:
notification_id: str
user_id: str
channel: str # email, sms, push, in_app
title: str
message: str
status: str = "pending"
sent_at: float | None = None


class INotificationService(Protocol):
"""Provided interface của NotificationComponent."""

def send_email(self, user_id: str, subject: str, body: str) -> Notification: ...
def send_sms(self, user_id: str, message: str) -> Notification: ...
def send_in_app(self, user_id: str, title: str, message: str) -> Notification: ...
def get_user_notifications(self, user_id: str, limit: int = 10) -> list[Notification]: ...


class NotificationComponent(Component):
"""Notification management component."""

def __init__(self) -> None:
self._notifications: dict[str, Notification] = {}
self._lock = threading.RLock()

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("NotificationComponent initializing...")
registry.register(INotificationService, self)

# Required interface: need IUserService to get user email
self._user_service: IUserService | None = None
# We'll resolve it lazily
logger.info("NotificationComponent initialized")

def start(self) -> None:
logger.info("NotificationComponent started")

def stop(self) -> None:
with self._lock:
self._notifications.clear()
logger.info("NotificationComponent stopped")

def _resolve_user_service(self, registry: ComponentRegistry) -> None:
if self._user_service is None:
self._user_service = registry.resolve(IUserService)

def send_email(self, user_id: str, subject: str, body: str) -> Notification:
notif_id = f"NOTIF{int(time.time())}"
notif = Notification(notif_id, user_id, "email", subject, body, "sent", time.time())
with self._lock:
self._notifications[notif_id] = notif
logger.info("[EMAIL] User=%s | Subject=%s", user_id, subject)
return notif

def send_sms(self, user_id: str, message: str) -> Notification:
notif_id = f"NOTIF{int(time.time())}"
notif = Notification(notif_id, user_id, "sms", "SMS", message, "sent", time.time())
with self._lock:
self._notifications[notif_id] = notif
logger.info("[SMS] User=%s | Message=%s...", user_id, message[:30])
return notif

def send_in_app(self, user_id: str, title: str, message: str) -> Notification:
notif_id = f"NOTIF{int(time.time())}"
notif = Notification(notif_id, user_id, "in_app", title, message, "sent", time.time())
with self._lock:
self._notifications[notif_id] = notif
return notif

def get_user_notifications(self, user_id: str, limit: int = 10) -> list[Notification]:
with self._lock:
user_notifs = [n for n in self._notifications.values() if n.user_id == user_id]
return sorted(user_notifs, key=lambda n: n.sent_at or 0, reverse=True)[:limit]


# ======================================================================
# 8. COMPONENT: ORDER PROCESSING (CORE BUSINESS)
# ======================================================================

class IOrderService(Protocol):
"""Provided interface của OrderComponent."""

def create_order(self, user_id: str, items: list[dict[str, Any]], shipping_address: Address,
payment_method: str, payment_info: dict[str, Any]) -> Order: ...
def get_order(self, order_id: str) -> Order | None: ...
def cancel_order(self, order_id: str) -> bool: ...
def get_user_orders(self, user_id: str) -> list[Order]: ...


class OrderComponent(Component):
"""Core business component — phối hợp các component khác."""

def __init__(self) -> None:
self._orders: dict[str, Order] = {}
self._lock = threading.RLock()
self._product_service: IProductService | None = None
self._payment_service: IPaymentService | None = None
self._shipping_service: IShippingService | None = None
self._notification_service: INotificationService | None = None
self._user_service: IUserService | None = None

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("OrderComponent initializing...")
registry.register(IOrderService, self)

# Resolve dependencies from registry
self._product_service = registry.resolve(IProductService)
self._payment_service = registry.resolve(IPaymentService)
self._shipping_service = registry.resolve(IShippingService)
self._notification_service = registry.resolve(INotificationService)
self._user_service = registry.resolve(IUserService)

if not all([self._product_service, self._payment_service, self._shipping_service,
self._notification_service, self._user_service]):
logger.warning("OrderComponent: some dependencies not resolved")

logger.info("OrderComponent initialized")

def start(self) -> None:
logger.info("OrderComponent started")

def stop(self) -> None:
with self._lock:
self._orders.clear()
logger.info("OrderComponent stopped")

def create_order(self, user_id: str, items_data: list[dict[str, Any]],
shipping_address: Address, payment_method: str,
payment_info: dict[str, Any]) -> Order:
"""Tạo đơn hàng — phối hợp các components."""
logger.info("=" * 60)
logger.info("Creating order for user=%s with %d items", user_id, len(items_data))

# 1. Validate user
user = self._user_service.get_user(user_id) if self._user_service else None
if not user or not user.is_active:
raise ValueError(f"User not found or inactive: {user_id}")
logger.info("User validated: %s (%s)", user.name, user.role)

# 2. Validate products and build OrderItem list
order_items: list[OrderItem] = []
for item_data in items_data:
product = self._product_service.get_product(item_data["product_id"]) if self._product_service else None
if not product or not product.is_active:
raise ValueError(f"Product not found: {item_data['product_id']}")
if product.stock < item_data.get("quantity", 1):
raise ValueError(f"Product {product.name} out of stock (available: {product.stock})")
order_items.append(OrderItem(
product_id=product.product_id,
name=product.name,
price=product.price,
quantity=item_data.get("quantity", 1),
))
# Reserve stock
if self._product_service:
self._product_service.update_stock(product.product_id, item_data.get("quantity", 1))
logger.info("Products validated: %d items", len(order_items))

# 3. Calculate subtotal
subtotal = Money(sum(
item.price.amount * item.quantity for item in order_items
))
logger.info("Subtotal: %.0f", subtotal.amount)

# 4. Calculate shipping
shipping_fee = Money(0)
shipping_method = "standard"
if self._shipping_service:
quotes = self._shipping_service.get_quotes(order_items, shipping_address)
if quotes:
shipping_method = quotes[0].method
shipping_fee = quotes[0].fee
logger.info("Shipping: %s (%.0f)", shipping_method, shipping_fee.amount)

# 5. Calculate tax (simplified: 8% VAT)
tax = Money(subtotal.amount * 0.08)
logger.info("Tax (8%%): %.0f", tax.amount)

# 6. Create order
order_id = f"ORD{int(time.time())}_{user_id[:4]}"
order = Order(
order_id=order_id,
user_id=user_id,
items=order_items,
shipping_address=shipping_address,
subtotal=subtotal,
shipping_fee=shipping_fee,
tax=tax,
)
order.calculate_total()
logger.info("Total: %.0f", order.total.amount)

# 7. Process payment
if self._payment_service:
txn = self._payment_service.process_payment(order_id, order.total, payment_method, payment_info)
order.payment_id = txn.transaction_id
order.status = "paid"
logger.info("Payment processed: %s", txn.transaction_id)
else:
order.status = "pending_payment"

# 8. Create shipment
if self._shipping_service and order.status == "paid":
shipment = self._shipping_service.create_shipment(order_id, shipping_method, shipping_address)
order.tracking_id = shipment.tracking_id
order.status = "processing"
logger.info("Shipment created: %s", shipment.tracking_id)

# 9. Send notifications
if self._notification_service:
self._notification_service.send_email(
user_id, f"Order {order_id} Confirmed",
f"Your order has been confirmed. Total: {order.total.amount} {order.total.currency}. "
f"Tracking: {order.tracking_id or 'N/A'}.",
)
self._notification_service.send_in_app(
user_id, "Order Confirmed",
f"Order {order_id} confirmed. Track your shipment with ID: {order.tracking_id}",
)

# 10. Store order
with self._lock:
self._orders[order_id] = order
logger.info("Order %s created successfully!", order_id)
return order

def get_order(self, order_id: str) -> Order | None:
return self._orders.get(order_id)

def cancel_order(self, order_id: str) -> bool:
order = self._orders.get(order_id)
if order and order.status in ("pending", "paid"):
order.status = "cancelled"
logger.info("Order cancelled: %s", order_id)
return True
return False

def get_user_orders(self, user_id: str) -> list[Order]:
return [o for o in self._orders.values() if o.user_id == user_id]


# ======================================================================
# 9. COMPONENT: ANALYTICS (EXTENSION)
# ======================================================================

class IAnalyticsService(Protocol):
def get_sales_summary(self, days: int = 7) -> dict[str, Any]: ...
def get_popular_products(self, limit: int = 5) -> list[dict[str, Any]]: ...


class AnalyticsComponent(Component):
"""Analytics component — extension component."""

def __init__(self) -> None:
self._order_service: IOrderService | None = None

def initialize(self, registry: ComponentRegistry) -> None:
logger.info("AnalyticsComponent initializing...")
registry.register(IAnalyticsService, self)
self._order_service = registry.resolve(IOrderService)
logger.info("AnalyticsComponent initialized")

def start(self) -> None:
logger.info("AnalyticsComponent started")

def stop(self) -> None:
logger.info("AnalyticsComponent stopped")

def get_sales_summary(self, days: int = 7) -> dict[str, Any]:
if not self._order_service:
return {"error": "Order service not available"}
# In production: query from database
return {
"total_orders": 0,
"total_revenue": 0,
"average_order_value": 0,
"period_days": days,
}

def get_popular_products(self, limit: int = 5) -> list[dict[str, Any]]:
return []


# ======================================================================
# 10. COMPOSITION ROOT — SYSTEM ASSEMBLER
# ======================================================================

class SystemAssembler:
"""Composition root — khởi tạo và wiring tất cả components."""

def __init__(self) -> None:
self.registry = ComponentRegistry()
self._components: list[Component] = []

def build(self) -> ComponentRegistry:
"""Build the component graph."""
logger.info("=== Building Component System ===")

# 1. Create all components
components = [
UserComponent(),
ProductComponent(),
PaymentComponent(),
ShippingComponent(),
NotificationComponent(),
OrderComponent(),
AnalyticsComponent(),
]

# 2. Initialize (each component registers its interface + resolves dependencies)
for component in components:
component.initialize(self.registry)
self._components.append(component)

# 3. Start all
self.registry.start_all()

logger.info("=== System built with %d components ===", len(components))
return self.registry

def shutdown(self) -> None:
logger.info("=== Shutting down system ===")
self.registry.stop_all()
logger.info("=== System shut down ===")

def get_component_states(self) -> dict[str, str]:
return {name: state.name for name, state in self.registry._states.items()}


# ======================================================================
# 11. MAIN — SIMULATION
# ======================================================================

def main() -> None:
logger.info("=== Component-Based Architecture: E-Commerce Platform ===")

# Build system
assembler = SystemAssembler()
registry = assembler.build()

# Get services
user_service = registry.resolve(IUserService)
product_service = registry.resolve(IProductService)
order_service = registry.resolve(IOrderService)
notification_service = registry.resolve(INotificationService)

if not all([user_service, product_service, order_service]):
logger.error("Required services not available!")
return

# Show available products
logger.info("\n=== Available Products ===")
products = product_service.search_products("")
for p in products:
logger.info(" %s | %s | %.0f VND | Stock: %d", p.product_id, p.name, p.price.amount, p.stock)

# Create orders
logger.info("\n=== Creating Orders ===")

order1 = order_service.create_order(
user_id="user_001",
items_data=[
{"product_id": "prod_001", "quantity": 1},
{"product_id": "prod_003", "quantity": 2},
],
shipping_address=Address("123 Nguyễn Huệ", "HCM", "HCM", "70000"),
payment_method="credit_card",
payment_info={"card_number": "4111-1111-1111-1111", "cvv": "123"},
)

order2 = order_service.create_order(
user_id="user_002",
items_data=[{"product_id": "prod_002", "quantity": 1}],
shipping_address=Address("456 Lê Lợi", "Hà Nội", "HN", "10000"),
payment_method="momo",
payment_info={"phone": "0909123456"},
)

# Query orders
logger.info("\n=== User Orders ===")
for uid in ["user_001", "user_002"]:
orders = order_service.get_user_orders(uid)
user = user_service.get_user(uid)
logger.info("\n%s's Orders:", user.name if user else uid)
for o in orders:
logger.info(" %s | Status: %s | Total: %.0f VND | Items: %d",
o.order_id, o.status, o.total.amount, len(o.items))

# Try cancelling
order_service.cancel_order("ORD_cancel_test")
logger.info("\n=== Component System Stats ===")
states = assembler.get_component_states()
for name, state in states.items():
if "Component" in name:
logger.info(" %s: %s", name, state)

# Shutdown
assembler.shutdown()
logger.info("=== Component-Based Architecture Demo Complete ===")


if __name__ == "__main__":
main()

Khi nào dùng / Khi nào không

Khi nào dùngKhi nào không
Hệ thống lớn, nhiều module, cần phân chia rõ ràngHệ thống nhỏ (< 10k LOC) — overhead không đáng
Nhiều sản phẩm dùng chung componentsToàn bộ system chỉ có 1-2 tính năng
Cần tái sử dụng code trên nhiều projectsComposition overhead là không chấp nhận được
Team phân tán, mỗi team maintain component riêngCircular dependency tự nhiên trong domain
Cần thay thế implementation (A/B testing)Yêu cầu performance tối đa (in-process call > IPC)
Third-party integration cần pluggableComponent interface thay đổi liên tục
Multi-product strategy (platform thinking)Hệ thống batch processing đơn giản

Ưu điểm / Nhược điểm

Ưu điểmNhược điểm
Tái sử dụng cao: Component dùng lại trên nhiều sản phẩmOverhead thiết kế ban đầu lớn: Cần thiết kế contract cẩn thận
Thay thế được: Implementation swap dễ dàng qua interfaceComplexity: Component graph có thể rất phức tạp
Triển khai độc lập: Mỗi component deploy riêngVersioning: Component dependency version management khó
Testing dễ dàng: Mock component, isolation testPerformance: Interface abstraction có overhead (dispatch)
Parallel development: Team làm việc độc lậpComponent granularity khó: Quá nhỏ → nhiều interfaces; quá lớn → monolithic
System comprehension: Component diagram dễ hiểuCircular dependency detection: Cần tool hỗ trợ
Language agnostic: Component có thể viết bằng ngôn ngữ khácDistribution overhead: Nếu component distributed (cần serialization)

Công cụ và Framework

TênLoạiNgôn ngữGhi chú
Spring IOC / Spring BootFrameworkJavaComponent scanning, DI, AOP
Google GuiceDI FrameworkJavaLightweight dependency injection
AutofacDI Container.NET.NET component lifecycle
Unity ContainerDI Container.NETMicrosoft's DI container
Python injectLibraryPythonLightweight DI
dependency-injectorLibraryPythonPython DI framework
Angular DIFrameworkTypeScriptClient-side component DI
React ComponentsLibraryJavaScriptUI component model
Web ComponentsW3C StandardJavaScriptBrowser-native component model
OSGiModule SystemJavaDynamic component loading
.NET MAUI / WPFUI Framework.NETXAML component model
Flutter WidgetsUI FrameworkDartWidget component tree

Kiểm thử

Component-based architecture yêu cầu testing ở nhiều cấp độ: unit test cho component riêng, integration test cho component interaction, end-to-end test cho toàn hệ thống.

from __future__ import annotations
import pytest
from typing import Any, Protocol


class TestUserComponent:
def test_register_user(self, user_component_initialized: IUserService) -> None:
user = user_component_initialized.register("new@test.com", "New User", "pass123")
assert user.email == "new@test.com"
assert user.name == "New User"
assert user.is_active is True
assert user.role == "customer"

def test_register_duplicate_email(self, user_component_initialized: IUserService) -> None:
with pytest.raises(ValueError, match="already registered"):
user_component_initialized.register("alice@example.com", "Alice Again", "pass")

def test_authenticate_valid(self, user_component_initialized: IUserService) -> None:
user = user_component_initialized.authenticate("alice@example.com", "pass")
assert user is not None
assert user.user_id == "user_001"

def test_authenticate_inactive(self, user_component_initialized: IUserService) -> None:
user_component_initialized.deactivate_user("user_001")
user = user_component_initialized.authenticate("alice@example.com", "pass")
assert user is None

def test_get_user(self, user_component_initialized: IUserService) -> None:
user = user_component_initialized.get_user("user_001")
assert user is not None
assert user.name == "Alice"

def test_get_users_by_role(self, user_component_initialized: IUserService) -> None:
admins = user_component_initialized.get_users_by_role("admin")
assert len(admins) == 1
assert admins[0].email == "admin@example.com"

def test_deactivate_user(self, user_component_initialized: IUserService) -> None:
result = user_component_initialized.deactivate_user("user_002")
assert result is True
user = user_component_initialized.get_user("user_002")
assert user is not None
assert user.is_active is False


class TestProductComponent:
def test_get_product(self, product_component_initialized: IProductService) -> None:
product = product_component_initialized.get_product("prod_001")
assert product is not None
assert product.name == "iPhone 15 Pro"
assert product.price.amount == 27990000

def test_search_products(self, product_component_initialized: IProductService) -> None:
results = product_component_initialized.search_products("iphone")
assert len(results) >= 1
assert all("iphone" in r.name.lower() for r in results)

def test_update_stock_success(self, product_component_initialized: IProductService) -> None:
result = product_component_initialized.update_stock("prod_001", 10)
assert result is True
product = product_component_initialized.get_product("prod_001")
assert product is not None
assert product.stock == 40 # originally 50 - 10

def test_update_stock_insufficient(self, product_component_initialized: IProductService) -> None:
result = product_component_initialized.update_stock("prod_001", 999)
assert result is False

def test_get_products_by_category(self, product_component_initialized: IProductService) -> None:
fashion = product_component_initialized.get_products_by_category("Fashion")
assert len(fashion) == 2
assert all(p.category == "Fashion" for p in fashion)


class TestPaymentComponent:
def test_process_payment(self, payment_component_initialized: IPaymentService) -> None:
txn = payment_component_initialized.process_payment(
"ORD001", Money(100000), "credit_card", {"number": "4111-1111"}
)
assert txn.status == "success"
assert txn.amount.amount == 100000
assert txn.order_id == "ORD001"

def test_refund_payment(self, payment_component_initialized: IPaymentService) -> None:
txn = payment_component_initialized.process_payment("ORD002", Money(50000), "momo", {})
refunded = payment_component_initialized.refund_payment(txn.transaction_id)
assert refunded.status == "refunded"

def test_unsupported_method(self, payment_component_initialized: IPaymentService) -> None:
with pytest.raises(ValueError, match="Unsupported"):
payment_component_initialized.process_payment("ORD003", Money(100), "bitcoin", {})

def test_get_available_methods(self, payment_component_initialized: IPaymentService) -> None:
methods = payment_component_initialized.get_available_methods()
assert "credit_card" in methods
assert "momo" in methods
assert "vnpay" in methods


class TestShippingComponent:
def test_get_quotes(self, shipping_component_initialized: IShippingService) -> None:
items = [OrderItem("p1", "Product", Money(100000), 2)]
address = Address("123 Street", "City", "ST", "12345")
quotes = shipping_component_initialized.get_quotes(items, address)
assert len(quotes) == 3
assert quotes[0].fee.amount > 0

def test_create_shipment(self, shipping_component_initialized: IShippingService) -> None:
address = Address("456 Road", "Town", "ST", "67890")
shipment = shipping_component_initialized.create_shipment("ORD001", "express", address)
assert shipment.tracking_id.startswith("SHIP")
assert shipment.order_id == "ORD001"

def test_track_shipment(self, shipping_component_initialized: IShippingService) -> None:
address = Address("789 Ave", "City", "ST", "11111")
shipment = shipping_component_initialized.create_shipment("ORD002", "standard", address)
tracking = shipping_component_initialized.track_shipment(shipment.tracking_id)
assert tracking["order_id"] == "ORD002"
assert "estimated_delivery" in tracking


class TestOrderComponent:
def test_create_order_success(self, system_with_components: IOrderService) -> None:
order = system_with_components.create_order(
user_id="user_001",
items_data=[{"product_id": "prod_001", "quantity": 1}],
shipping_address=Address("123 Main", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={"number": "4111-1111"},
)
assert order.order_id.startswith("ORD")
assert order.status in ("paid", "processing")
assert len(order.items) == 1
assert order.payment_id != ""

def test_create_order_insufficient_stock(self, system_with_components: IOrderService) -> None:
with pytest.raises(ValueError):
system_with_components.create_order(
user_id="user_001",
items_data=[{"product_id": "prod_001", "quantity": 9999}],
shipping_address=Address("123 Main", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={},
)

def test_create_order_inactive_user(self, system_with_components: IOrderService) -> None:
with pytest.raises(ValueError):
system_with_components.create_order(
user_id="nonexistent",
items_data=[{"product_id": "prod_001", "quantity": 1}],
shipping_address=Address("123 Main", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={},
)

def test_cancel_order(self, system_with_components: IOrderService) -> None:
order = system_with_components.create_order(
user_id="user_001",
items_data=[{"product_id": "prod_003", "quantity": 1}],
shipping_address=Address("123 Main", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={},
)
result = system_with_components.cancel_order(order.order_id)
assert result is True
cancelled = system_with_components.get_order(order.order_id)
assert cancelled is not None
assert cancelled.status == "cancelled"

def test_get_user_orders(self, system_with_components: IOrderService) -> None:
system_with_components.create_order(
user_id="user_001",
items_data=[{"product_id": "prod_001", "quantity": 1}],
shipping_address=Address("123 Main", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={},
)
orders = system_with_components.get_user_orders("user_001")
assert len(orders) >= 1
assert all(o.user_id == "user_001" for o in orders)


class TestComponentLifecycle:
def test_component_states(self) -> None:
assembler = SystemAssembler()
registry = assembler.build()
states = assembler.get_component_states()
for name, state in states.items():
assert state in ("STARTED", "INITIALIZED"), f"{name} is {state}"
assembler.shutdown()
for name, state in assembler.get_component_states().items():
assert state in ("STOPPED", "FAILED"), f"{name} is {state} after shutdown"

def test_component_dependency_resolution(self) -> None:
assembler = SystemAssembler()
registry = assembler.build()
# All core services should be resolvable
assert registry.resolve(IUserService) is not None
assert registry.resolve(IProductService) is not None
assert registry.resolve(IPaymentService) is not None
assert registry.resolve(IShippingService) is not None
assert registry.resolve(INotificationService) is not None
assert registry.resolve(IOrderService) is not None
assembler.shutdown()

def test_component_wiring(self) -> None:
"""OrderComponent should have its dependencies wired correctly."""
assembler = SystemAssembler()
registry = assembler.build()
order_component = registry.resolve(IOrderService)
if order_component:
# Should be able to create an order (which requires all dependencies)
order = order_component.create_order(
user_id="user_001",
items_data=[{"product_id": "prod_001", "quantity": 1}],
shipping_address=Address("Test", "City", "ST", "12345"),
payment_method="credit_card",
payment_info={},
)
assert order is not None
assembler.shutdown()


class TestNotificationComponent:
def test_send_email(self, notification_component_initialized: INotificationService) -> None:
notif = notification_component_initialized.send_email("user_001", "Test", "Hello")
assert notif.status == "sent"
assert notif.channel == "email"

def test_get_user_notifications(self, notification_component_initialized: INotificationService) -> None:
notification_component_initialized.send_email("user_001", "Subject", "Body")
notification_component_initialized.send_sms("user_001", "SMS Body")
notifs = notification_component_initialized.get_user_notifications("user_001")
assert len(notifs) == 2

def test_notifications_scoped_to_user(self, notification_component_initialized: INotificationService) -> None:
notification_component_initialized.send_email("user_001", "For Alice", "Hello")
notification_component_initialized.send_email("user_002", "For Bob", "Hi")
alice_notifs = notification_component_initialized.get_user_notifications("user_001")
assert len(alice_notifs) == 1
assert alice_notifs[0].title == "For Alice"


# ======================================================================
# Fixtures
# ======================================================================

@pytest.fixture
def user_component() -> UserComponent:
return UserComponent()


@pytest.fixture
def user_component_initialized(user_component: UserComponent) -> IUserService:
registry = ComponentRegistry()
user_component.initialize(registry)
registry.start_all()
return registry.resolve(IUserService)


@pytest.fixture
def product_component() -> ProductComponent:
return ProductComponent()


@pytest.fixture
def product_component_initialized(product_component: ProductComponent) -> IProductService:
registry = ComponentRegistry()
product_component.initialize(registry)
registry.start_all()
return registry.resolve(IProductService)


@pytest.fixture
def payment_component() -> PaymentComponent:
return PaymentComponent()


@pytest.fixture
def payment_component_initialized(payment_component: PaymentComponent) -> IPaymentService:
registry = ComponentRegistry()
payment_component.initialize(registry)
registry.start_all()
return registry.resolve(IPaymentService)


@pytest.fixture
def shipping_component() -> ShippingComponent:
return ShippingComponent()


@pytest.fixture
def shipping_component_initialized(shipping_component: ShippingComponent) -> IShippingService:
registry = ComponentRegistry()
shipping_component.initialize(registry)
registry.start_all()
return registry.resolve(IShippingService)


@pytest.fixture
def notification_component() -> NotificationComponent:
return NotificationComponent()


@pytest.fixture
def notification_component_initialized(notification_component: NotificationComponent) -> INotificationService:
registry = ComponentRegistry()
notification_component.initialize(registry)
registry.start_all()
return registry.resolve(INotificationService)


@pytest.fixture
def system_with_components() -> IOrderService:
assembler = SystemAssembler()
registry = assembler.build()
return registry.resolve(IOrderService)

Kết luận

Component-Based Architecture là một trong những kiến trúc nền tảng nhất trong software engineering. Bằng cách phân chia hệ thống thành các component độc lập với interface rõ ràng, CBA mang lại khả năng tái sử dụng, thay thế, và mở rộng vượt trội. Đây là kiến trúc cơ sở cho hầu hết các hệ thống enterprise hiện đại — từ Spring Framework, Angular, React đến microservices.

Best Practices:

  • Thiết kế interface trước, implementation sau (Contract-First Development)
  • Component granularity: Một component nên giải quyết một business capability. Nếu component có nhiều hơn 7±2 classes, xem xét tách.
  • Dependency direction: Component cấp cao không phụ thuộc component cấp thấp — cả hai phụ thuộc abstraction (DIP).
  • Stable dependencies: Component core không phụ thuộc component concrete — inject qua interface.
  • Component testability: Mỗi component phải testable trong isolation với mock dependencies.
  • Versioning strategy: Component API phải versioned (SemVer) — breaking change = major version.
  • Document component contract: Precondition, postcondition, invariant cho mỗi public method.

Golden Rules:

  1. Component là deployment unit, không phải design unit. Interface là design unit.
  2. Package theo component, không theo layer (by feature, not by layer).
  3. Component không được có circular dependency — dùng Dependency Inversion để break cycle.
  4. Component phải có interface riêng — không export internal classes.
  5. Component registry (IoC container) là composition root — chỉ dùng ở application entry point.
  6. Component nên stateless hoặc state được quản lý rõ ràng.
  7. Component contract phải được test như một phần của CI/CD pipeline.