Bỏ qua

Common DDD Pitfalls - Những Lỗi Thường Gặp

1. Anemic Domain Model - "Cơ Thể Không Có Máu"

Vấn Đề: Domain Objects Chỉ Là Data Container

Ví dụ sai:

// Anemic Order - chỉ có data, không có behavior
class Order {
    id: string;
    customerId: string;
    items: OrderItem[];
    status: string;
    totalAmount: number;

    // Chỉ có getter/setter, không có logic
}

// Logic bị đẩy xuống Service
class OrderService {
    createOrder(customerId: string, items: OrderItem[]) {
        // Tính toán ở đây thay vì trong Order
        let total = 0;
        for (let item of items) {
            total += item.price * item.quantity;
        }

        // Validation ở đây thay vì trong Order
        if (total > 100000000) {
            throw new Error("Order too large");
        }

        // Tạo order
        const order = new Order();
        order.customerId = customerId;
        order.items = items;
        order.totalAmount = total;
        order.status = "PENDING";

        return this.orderRepository.save(order);
    }
}

Giải Pháp: Rich Domain Model

Ví dụ đúng:

// Rich Order - có behavior và business logic
class Order {
    private constructor(
        private readonly id: OrderId,
        private readonly customerId: CustomerId,
        private items: OrderItem[],
        private status: OrderStatus,
        private totalAmount: Money
    ) {}

    // Factory method với validation
    static create(customerId: CustomerId, items: OrderItem[]): Order {
        if (items.length === 0) {
            throw new Error("Order must have at least one item");
        }

        const total = Money.sum(items.map(item => item.totalPrice()));

        if (total.isGreaterThan(Money.fromVND(100000000))) {
            throw new Error("Order amount exceeds limit");
        }

        return new Order(
            OrderId.generate(),
            customerId,
            items,
            OrderStatus.PENDING,
            total
        );
    }

    // Business methods
    confirm(): void {
        if (!this.status.canTransitionTo(OrderStatus.CONFIRMED)) {
            throw new Error("Cannot confirm order in current status");
        }
        this.status = OrderStatus.CONFIRMED;
    }

    addItem(item: OrderItem): void {
        const newTotal = this.totalAmount.add(item.totalPrice());
        if (newTotal.isGreaterThan(Money.fromVND(100000000))) {
            throw new Error("Adding item would exceed order limit");
        }
        this.items.push(item);
        this.totalAmount = newTotal;
    }
}

// Service chỉ orchestrate
class OrderService {
    createOrder(customerId: CustomerId, items: OrderItem[]): Order {
        const order = Order.create(customerId, items);  // Logic trong domain
        return this.orderRepository.save(order);
    }
}


2. God Objects/Aggregates - "Vua Quá Khổ"

Vấn Đề: Aggregate Quá Lớn, Chứa Quá Nhiều Thứ

Ví dụ sai:

// Customer God Object - chứa mọi thứ
class Customer {
    id: CustomerId;
    name: string;
    email: string;

    // Profile info
    dateOfBirth: Date;
    gender: string;
    phoneNumber: string;

    // Address info  
    addresses: Address[];
    defaultShippingAddress: Address;
    defaultBillingAddress: Address;

    // Order history
    orders: Order[];
    totalOrderCount: number;
    totalSpent: Money;

    // Loyalty info
    loyaltyPoints: number;
    loyaltyTier: string;
    loyaltyExpiry: Date;

    // Payment info
    paymentMethods: PaymentMethod[];
    defaultPaymentMethod: PaymentMethod;

    // Preferences
    emailNotifications: boolean;
    smsNotifications: boolean;
    marketingConsent: boolean;

    // Support info
    supportTickets: SupportTicket[];
    lastSupportInteraction: Date;

    // Analytics
    lastLoginDate: Date;
    sessionCount: number;
    pageViews: number;

    // Giảm giá
    availableCoupons: Coupon[];
    usedCoupons: Coupon[];
}

Vấn đề: - Aggregate quá lớn, khó maintain - Vi phạm Single Responsibility Principle
- Performance issue khi load/save - Nhiều team cùng sửa → conflict

Giải Pháp: Chia Nhỏ Thành Nhiều Bounded Context

Ví dụ đúng:

// Customer Context - chỉ thông tin cơ bản
class Customer {
    constructor(
        private readonly id: CustomerId,
        private name: CustomerName,
        private email: Email,
        private registrationDate: Date
    ) {}

    updateProfile(name: CustomerName, email: Email): void {
        this.name = name;
        this.email = email;
    }
}

// Order Context - riêng biệt
class OrderHistory {
    constructor(
        private readonly customerId: CustomerId,
        private orders: Order[]
    ) {}

    addOrder(order: Order): void {
        this.orders.push(order);
    }

    getTotalSpent(): Money {
        return Money.sum(this.orders.map(o => o.totalAmount));
    }
}

// Loyalty Context - riêng biệt  
class LoyaltyAccount {
    constructor(
        private readonly customerId: CustomerId,
        private points: number,
        private tier: LoyaltyTier
    ) {}

    earnPoints(amount: number): void {
        this.points += amount;
        this.updateTier();
    }
}


3. Quá Nhiều Bounded Contexts - "Chia Nhỏ Quá Mức"

Vấn Đề: Micro-Contexts

Ví dụ sai:

📁 user-name-context/
📁 user-email-context/
📁 user-phone-context/
📁 user-address-context/
📁 order-total-context/
📁 order-status-context/
📁 order-items-context/

Vấn đề: - Quá nhiều context nhỏ → overhead - Network calls nhiều - Inconsistency giữa contexts - Team không biết context nào chịu trách nhiệm gì

Giải Pháp: Bounded Context Có Ý Nghĩa

Ví dụ đúng:

📁 customer-management/     ← Quản lý thông tin khách hàng
📁 order-processing/        ← Xử lý đơn hàng
📁 inventory-management/    ← Quản lý kho
📁 payment-processing/      ← Thanh toán
📁 shipping-logistics/      ← Vận chuyển

Nguyên tắc chia Bounded Context: 1. Business Capability: Mỗi context giải quyết 1 vấn đề business 2. Team Ownership: 1 team chịu trách nhiệm 1-2 contexts 3. Data Autonomy: Mỗi context có database riêng 4. Communication: Contexts giao tiếp qua events/APIs


4. Repository Leakage - "Rò Rỉ Kỹ Thuật"

Vấn Đề: Domain Biết Về Database

Ví dụ sai:

// Domain service biết về database query
class OrderDomainService {
    calculateCustomerDiscount(customerId: string): number {
        // SAI: Domain service gọi trực tiếp database
        const orders = this.database.query(`
            SELECT * FROM orders 
            WHERE customer_id = ? AND status = 'COMPLETED'
            ORDER BY created_date DESC LIMIT 10
        `, [customerId]);

        if (orders.length > 5) {
            return 0.1; // 10% discount
        }
        return 0;
    }
}

// Aggregate biết về persistence details
class Order {
    save(): void {
        // SAI: Domain object tự save
        database.execute(`
            INSERT INTO orders (id, customer_id, total) 
            VALUES (?, ?, ?)
        `, [this.id, this.customerId, this.total]);
    }
}

Giải Pháp: Repository Pattern Đúng Cách

Ví dụ đúng:

// Domain Interface - không biết về database
interface OrderRepository {
    save(order: Order): void;
    findById(id: OrderId): Order | null;
    findCompletedOrdersByCustomer(customerId: CustomerId): Order[];
}

// Domain Service sử dụng interface
class OrderDomainService {
    constructor(private orderRepo: OrderRepository) {}

    calculateCustomerDiscount(customerId: CustomerId): number {
        // Dùng domain method, không biết về SQL
        const completedOrders = this.orderRepo.findCompletedOrdersByCustomer(customerId);

        if (completedOrders.length > 5) {
            return 0.1;
        }
        return 0;
    }
}

// Infrastructure Implementation - biết về database
class SqlOrderRepository implements OrderRepository {
    save(order: Order): void {
        // Infrastructure lo database
        this.database.query(`INSERT INTO orders...`);
    }

    findCompletedOrdersByCustomer(customerId: CustomerId): Order[] {
        const rows = this.database.query(`SELECT * FROM orders WHERE...`);
        return rows.map(row => this.toDomainObject(row));
    }
}


5. Distributed Monolith - "Monolith Ряn Rải"

Vấn Đề: Microservices Với Tight Coupling

Ví dụ sai:

graph TD
    A[Order Service] -->|Direct HTTP| B[Customer Service]
    A -->|Direct HTTP| C[Inventory Service] 
    A -->|Direct HTTP| D[Payment Service]
    A -->|Direct HTTP| E[Shipping Service]

    B -->|Direct HTTP| C
    C -->|Direct HTTP| D
    D -->|Direct HTTP| E
    E -->|Direct HTTP| A

Vấn đề: - Services phụ thuộc lẫn nhau - Không thể deploy riêng lẻ - Cascading failures - Performance bottleneck

Giải Pháp: Event-Driven Architecture

Ví dụ đúng:

graph TD
    A[Order Service] -->|OrderPlacedEvent| F[Event Bus]
    F -->|Event| B[Customer Service]
    F -->|Event| C[Inventory Service]
    F -->|Event| D[Payment Service]
    F -->|Event| E[Shipping Service]

    B -->|CustomerUpdatedEvent| F
    C -->|StockReservedEvent| F
    D -->|PaymentProcessedEvent| F
    E -->|ShipmentCreatedEvent| F


6. Wrong Abstraction - "Trừu Tượng Sai"

Vấn Đề: Generic Repository/Service

Ví dụ sai:

// Generic repository quá abstract
interface GenericRepository<T> {
    save(entity: T): void;
    findById(id: string): T;
    findAll(): T[];
    delete(id: string): void;
    query(sql: string): T[];  // Leak implementation detail
}

class OrderRepository extends GenericRepository<Order> {
    // Inherited generic methods không express domain concepts
}

// Generic service
class GenericDomainService<T> {
    create(entity: T): T { /* generic logic */ }
    update(entity: T): T { /* generic logic */ }
    delete(id: string): void { /* generic logic */ }
}

Giải Pháp: Domain-Specific Interfaces

Ví dụ đúng:

// Domain-specific repository
interface OrderRepository {
    save(order: Order): void;
    findById(orderId: OrderId): Order | null;

    // Domain-specific queries
    findPendingOrdersByCustomer(customerId: CustomerId): Order[];
    findOrdersToShipToday(): Order[];
    findHighValueOrders(minAmount: Money): Order[];
    countOrdersInLastMonth(customerId: CustomerId): number;
}

// Domain-specific service
interface OrderDomainService {
    // Express business operations
    placeOrder(customerId: CustomerId, items: OrderItem[]): Order;
    cancelOrder(orderId: OrderId, reason: string): void;
    calculateShipping(order: Order, address: Address): Money;
    applyDiscount(order: Order, coupon: Coupon): void;
}


7. Event Sourcing Overuse - "Lạm Dụng Event Sourcing"

Vấn Đề: Dùng Event Sourcing Cho Mọi Thứ

Ví dụ sai:

// Dùng Event Sourcing cho simple CRUD
class CustomerProfileEventStore {
    events: Event[] = [
        { type: "NameChanged", data: { name: "John" } },
        { type: "NameChanged", data: { name: "Johnny" } },
        { type: "EmailChanged", data: { email: "john@gmail.com" } },
        { type: "EmailChanged", data: { email: "johnny@gmail.com" } },
        // 1000 events chỉ để update profile...
    ];
}

Vấn đề: - Overhead lớn cho simple operations - Query phức tạp - Performance issues - Overkill cho most use cases

Giải Pháp: Chọn Đúng Tool Cho Đúng Job

Event Sourcing cho: - Financial transactions (cần audit trail) - Complex business processes - Temporal queries (state tại thời điểm X)

Traditional storage cho: - Simple CRUD operations - Reference data (countries, categories) - User profiles, settings


8. Poor Testing Strategy - "Test Sai Cách"

Vấn Đề: Test Implementation Thay Vì Behavior

Ví dụ sai:

// Test implementation details
test('Order should have items array', () => {
    const order = new Order();
    expect(order.items).toBeInstanceOf(Array);  // Test implementation
    expect(order.items.length).toBe(0);         // Test implementation
});

// Test database operations
test('OrderRepository should call database.save', () => {
    const mockDb = jest.mock(Database);
    const repo = new OrderRepository(mockDb);

    repo.save(order);

    expect(mockDb.save).toHaveBeenCalled();  // Test implementation
});

Giải Pháp: Test Business Behavior

Ví dụ đúng:

// Test business behavior
test('Should not allow order with total exceeding limit', () => {
    const expensiveItems = [
        OrderItem.create(productId, 1000, Money.fromVND(100000000))
    ];

    expect(() => {
        Order.create(customerId, expensiveItems);
    }).toThrow('Order amount exceeds limit');
});

// Test domain logic
test('Should calculate correct discount for loyal customers', () => {
    const loyalCustomer = CustomerId.of('loyal-customer');
    const mockRepo = createMockRepository([
        // Setup 6 completed orders for this customer
    ]);
    const service = new OrderDomainService(mockRepo);

    const discount = service.calculateCustomerDiscount(loyalCustomer);

    expect(discount).toBe(0.1);  // Test business rule
});


9. Premature Performance Optimization

Vấn Đề: Tối Ưu Hóa Quá Sớm

Ví dụ sai:

// Cache everything
class OrderService {
    private cache = new Map();

    getOrder(id: string): Order {
        // Premature caching
        if (this.cache.has(id)) {
            return this.cache.get(id);
        }

        const order = this.orderRepo.findById(id);
        this.cache.set(id, order);  // Cache everything
        return order;
    }
}

// Denormalize everything
class OrderSummary {
    // Duplicate data for "performance"
    customerName: string;      // From Customer
    customerEmail: string;     // From Customer  
    productNames: string[];    // From Products
    totalAmount: number;       // Calculated
    // ... more duplicated data
}

Giải Pháp: Domain-First, Optimize Later

Ví dụ đúng:

// Start with clean domain model
class Order {
    // Focus on business logic first
    static create(customerId: CustomerId, items: OrderItem[]): Order {
        // Domain logic
    }

    calculateTotal(): Money {
        // Business calculation
    }
}

// Add performance optimizations when needed
class OrderQueryService {
    // Add read models when performance issues arise
    getOrderSummary(orderId: OrderId): OrderSummaryView {
        // Optimized for reads
    }
}


10. Checklist: Tránh Những Lỗi Này

Domain Layer

  • [ ] Domain objects có behavior, không chỉ data
  • [ ] Business rules ở trong domain, không ở service
  • [ ] Aggregates có kích thước hợp lý
  • [ ] Repository interfaces ở domain layer

Application Layer

  • [ ] Application services chỉ orchestrate
  • [ ] Không có business logic ở application layer
  • [ ] Use cases rõ ràng và đơn giản

Infrastructure Layer

  • [ ] Database logic chỉ ở infrastructure
  • [ ] Domain không biết về database/web/external APIs
  • [ ] Implementations có thể thay đổi mà không ảnh hưởng domain

Bounded Contexts

  • [ ] Mỗi context có business capability rõ ràng
  • [ ] Contexts giao tiếp qua well-defined interfaces
  • [ ] Không có shared database giữa contexts

Events

  • [ ] Events represent business facts
  • [ ] Event handlers are idempotent
  • [ ] Event schema versioning strategy

Testing

  • [ ] Test business behavior, không test implementation
  • [ ] Unit tests cho domain logic
  • [ ] Integration tests cho workflows

Kết Luận

DDD là về modeling business domain, không phải về technology.

Hầu hết các lỗi đều xuất phát từ việc: 1. Tập trung vào technology thay vì business 2. Over-engineering hoặc under-engineering 3. Không hiểu rõ domain

Remember: Start simple, understand the domain deeply, then apply DDD patterns where they add value.