DDD Politics

DDD Politics

Policy

In Domain-Driven Design (DDD), a Policy is a design pattern used to represent a rule or constraint within the domain model. It encapsulates business rules, decisions, or strategies that guide how a certain operation or behavior should be executed. Policies are a way to separate complex business logic, ensuring that rules are both reusable and testable, while keeping the domain model clean and focused.

Key Characteristics of a Policy:

  1. Encapsulation of Business Rules: Policies encapsulate specific domain rules that might change over time or vary based on context (e.g., different business units or customer types).

  2. Decision-Making Logic: Policies often represent decision-making logic, answering questions like “Can this action be taken?” or “What is the appropriate behavior in this scenario?”

  3. Separation of Concerns: Policies separate the concern of enforcing or applying rules from entities or services. This keeps entities focused on representing domain concepts and services on orchestrating workflows.

  4. Composability: Policies can often be composed to form more complex rules, allowing for flexibility in how business logic is applied.

Examples of Policies in Domain-Driven Design (TypeScript)

Let’s extend our e-commerce example to include a policy that checks if an order can be placed based on business rules, such as whether the customer has enough credit or the items are in stock.

1. Defining a Simple Policy

We’ll define a policy that checks whether a customer can place an order based on their credit limit.

class CanPlaceOrderPolicy {
  canPlaceOrder(order: Order, customer: Customer): boolean {
    const totalAmount = order.getTotalAmount();
 
    // Business rule: The order total cannot exceed the customer's credit limit
    if (totalAmount > customer.getCreditLimit()) {
      return false;
    }
 
    // Business rule: All items in the order must be in stock
    for (const item of order.getItems()) {
      if (item.getQuantity() > item.getProduct().getStock()) {
        return false;
      }
    }
 
    return true;
  }
}

In this example:

  • The CanPlaceOrderPolicy encapsulates the logic for determining whether a customer can place an order.
  • The policy checks two business rules:
    1. The total order amount must not exceed the customer’s credit limit.
    2. All items must be in stock.

2. Using the Policy

Now, let’s use the policy in the order placement workflow. The policy will be used to decide whether or not the order can be placed.

async function placeOrderWithPolicy(
  orderRepository: OrderRepository,
  customer: Customer,
  products: Product[],
  quantities: number[]
): Promise<void> {
  const orderFactory = new OrderFactory();
  const order = orderFactory.createOrder(customer, products, quantities);
 
  const canPlaceOrderPolicy = new CanPlaceOrderPolicy();
 
  if (!canPlaceOrderPolicy.canPlaceOrder(order, customer)) {
    console.log(`Order cannot be placed for customer ${customer.customerId}.`);
    return;
  }
 
  console.log(`Order ${order.orderId} has been successfully placed.`);
  await orderRepository.add(order);
}
 
// Example usage:
await placeOrderWithPolicy(orderRepository, customer, [product1, product2], [2, 1]);

Here:

  • The CanPlaceOrderPolicy is used to decide if the order can be placed.
  • If the policy returns false, the workflow is interrupted, preventing the order from being placed.

3. More Complex Policy (Composing Rules)

You can also compose multiple policies to form more complex decision-making logic. Let’s assume we want to add a new rule that checks if the customer has a valid loyalty membership before they can place an order.

We can extend our CanPlaceOrderPolicy to include this new rule:

class CanPlaceOrderPolicy {
  canPlaceOrder(order: Order, customer: Customer): boolean {
    const totalAmount = order.getTotalAmount();
 
    // Business rule: The order total cannot exceed the customer's credit limit
    if (totalAmount > customer.getCreditLimit()) {
      return false;
    }
 
    // Business rule: All items in the order must be in stock
    for (const item of order.getItems()) {
      if (item.getQuantity() > item.getProduct().getStock()) {
        return false;
      }
    }
 
    // Business rule: Customer must have a valid loyalty membership
    if (!customer.hasValidLoyaltyMembership()) {
      return false;
    }
 
    return true;
  }
}

In this case, the CanPlaceOrderPolicy now checks three business rules:

  1. The total amount must not exceed the customer’s credit limit.
  2. All items must be in stock.
  3. The customer must have a valid loyalty membership.

4. Testing Policies

Since policies encapsulate business rules, they are easy to test in isolation. Here’s an example of how you might test the CanPlaceOrderPolicy:

describe('CanPlaceOrderPolicy', () => {
  it('should not allow placing order if total exceeds credit limit', () => {
    const customer = new Customer('customer123', 'Alice');
    customer.setCreditLimit(100);  // Set credit limit to $100
 
    const order = new Order('order123', 'customer123', new Date());
    order.addItem(new OrderItem('product1', 2, 60));  // Total amount: $120
 
    const canPlaceOrderPolicy = new CanPlaceOrderPolicy();
    const canPlaceOrder = canPlaceOrderPolicy.canPlaceOrder(order, customer);
 
    expect(canPlaceOrder).toBe(false);  // Credit limit exceeded
  });
 
  it('should allow placing order if all rules are satisfied', () => {
    const customer = new Customer('customer123', 'Bob');
    customer.setCreditLimit(500);  // Set credit limit to $500
 
    const order = new Order('order456', 'customer123', new Date());
    order.addItem(new OrderItem('product1', 3, 100));  // Total amount: $300
 
    const canPlaceOrderPolicy = new CanPlaceOrderPolicy();
    const canPlaceOrder = canPlaceOrderPolicy.canPlaceOrder(order, customer);
 
    expect(canPlaceOrder).toBe(true);  // All rules satisfied
  });
});

Policy Composition

In more complex systems, you might want to compose multiple policies to represent different aspects of the decision-making process. In TypeScript, this could be achieved by chaining or combining policies.

class AndPolicy<T> {
  private policies: Policy<T>[];
 
  constructor(...policies: Policy<T>[]) {
    this.policies = policies;
  }
 
  isSatisfiedBy(candidate: T): boolean {
    return this.policies.every(policy => policy.isSatisfiedBy(candidate));
  }
}
 
class CustomerHasEnoughCreditPolicy implements Policy<Order> {
  isSatisfiedBy(order: Order, customer: Customer): boolean {
    return order.getTotalAmount() <= customer.getCreditLimit();
  }
}
 
class ItemsInStockPolicy implements Policy<Order> {
  isSatisfiedBy(order: Order): boolean {
    return order.getItems().every(item => item.getQuantity() <= item.getProduct().getStock());
  }
}
 
// Composing multiple policies
const policy = new AndPolicy(
  new CustomerHasEnoughCreditPolicy(),
  new ItemsInStockPolicy()
);
 
const canPlaceOrder = policy.isSatisfiedBy(order, customer);

This allows for a flexible and modular way to define and apply business rules.

Benefits of Policies in DDD

  1. Flexibility: Policies can easily be swapped, updated, or composed to meet evolving business rules.

  2. Separation of Concerns: Policies separate rule-based logic from entities and services, ensuring that domain models stay focused on representing data and behavior.

  3. Testability: Since policies encapsulate specific business rules, they are easy to unit test in isolation.

  4. Reusability: Policies are reusable components that can be applied across multiple services or domain models, promoting consistency in rule enforcement.

  5. Consistency: Using policies ensures that business rules are applied consistently throughout the application, reducing the risk of inconsistent rule enforcement.

Conclusion

In Domain-Driven Design, a Policy is a way to encapsulate business rules and decision-making logic, keeping domain entities clean and focused on core behaviors. Policies handle logic that spans multiple entities or aggregates, or that involves business rules that may change over time.

Next

YOU HAVE GONE THROUGH!!! CONGRATZ IN LEARNING DDD!!!

Go back to main menu for DDD or visit DRD (my frontend architecture based on knoledge from DDD).

Check next:

READ

Latest readings

  • Readings are sites which will help you with detailed

  • information about given topic. Read latest ones from Learn.

AI

06-03-2026

Local Voice Assistant with Ollama
  • Build your own local voice assistant powered by Ollama.

AI

06-03-2026

AI YouTube Thumbnail Generator
  • Generate YouTube thumbnails with FastAPI and Ollama.

Architecture

05-09-2024

Graph DB usage comparison
  • Compare Neo4j and Tigergraph databases, which is easier to work with, etc.