← 中文资源

简体中文版本

领域驱动设计

领域驱动设计把建模重心放在业务领域与共享通用语言上,使代码结构与领域专家描述问题的方式对齐。

阅读英文原文

概念概述

领域驱动设计(Domain-Driven Design)指的是:领域驱动设计把建模重心放在业务领域与共享通用语言上,使代码结构与领域专家描述问题的方式对齐。

从更严格的定义看,Domain-driven design is an approach to software development that focuses effort on modelling the core domain, using a language shared by developers and domain experts as the basis for the model and the code. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。

为什么需要它

It is applied to complex business domains where the main difficulty is understanding and expressing rules, not technical infrastructure. 它与 Bounded Context、Aggregate Root、Repository Pattern 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。

常见误解

  • DDD always implies microservices
  • a rich domain model is mandatory for every part of a system

需要避免的反模式

  • applying tactical patterns such as entities everywhere without strategic modelling
  • letting database tables define the domain model

实践要点

  1. Strategic design identifies the core, supporting, and generic subdomains and draws boundaries so that each can be modelled in the terms that best fit it.
  2. The ubiquitous language must appear in code as well as conversation, because a translation gap between the two reintroduces the misunderstanding the approach tries to remove.
  3. Tactical building blocks such as entities, value objects, and aggregates implement the model inside a boundary; applying them without the strategy yields ceremony without insight.

参考来源

https://martinfowler.com/bliki/DomainDrivenDesign.html

代码示例

public class SealedClassHierarchy {

    sealed interface Event permits LoginEvent, LogoutEvent, PurchaseEvent, ErrorEvent {
        long timestamp();
        String userId();
    }

    record LoginEvent(long timestamp, String userId, String ip) implements Event {
        @Override public long timestamp() { return timestamp; }
        @Override public String userId() { return userId; }
    }

    record LogoutEvent(long timestamp, String userId) implements Event {
        @Override public long timestamp() { return timestamp; }
        @Override public String userId() { return userId; }
    }

    record PurchaseEvent(long timestamp, String userId, String item, double amount) implements Event {
        @Override public long timestamp() { return timestamp; }
        @Override public String userId() { return userId; }
    }

    record ErrorEvent(long timestamp, String userId, String message) implements Event {
        @Override public long timestamp() { return timestamp; }
        @Override public String userId() { return userId; }
    }

    static String processEvent(Event event) {
        return switch (event) {
            case LoginEvent e -> "User " + e.userId() + " logged in from " + e.ip();
            case LogoutEvent e -> "User " + e.userId() + " logged out";
            case PurchaseEvent e -> "User " + e.userId() + " bought " + e.item() + " for $" + e.amount();
            case ErrorEvent e -> "Error for " + e.userId() + ": " + e.message();
        };
    }

    public static void main(String[] args) {
        Event[] events = {
            new LoginEvent(System.currentTimeMillis(), "alice", "192.168.1.1"),
            new PurchaseEvent(System.currentTimeMillis(), "alice", "Widget", 29.99),
            new ErrorEvent(System.currentTimeMillis(), "bob", "Invalid session"),
            new LogoutEvent(System.currentTimeMillis(), "alice")
        };

        for (Event event : events) {
            System.out.println(processEvent(event));
        }
    }
}