一、概述

在大模型应用开发中,传统的三层架构(Controller-Service-Dao)难以应对复杂业务场景的领域边界划分、业务规则沉淀等问题。本文以「电商智能客服系统」为例,结合领域驱动设计(DDD) 思想,基于Spring Boot 3.2.0、JDK21、LangChain4j和通义千问,构建高内聚、低耦合的企业级智能客服系统,同时兼顾大模型集成、领域规则沉淀、可扩展性等核心诉求。
在这里插入图片描述在这里插入图片描述

核心技术栈

  • 框架:Spring Boot 3.2.0(适配JDK21)
  • 大模型:通义千问(qwen-turbo)
  • 大模型开发框架:LangChain4j 0.32.0
  • 设计模式:领域驱动设计(DDD)
  • 语言:Java 21(Record、文本块、虚拟线程等特性)

二、DDD核心概念适配(智能客服场景)

在智能客服场景中,我们先明确DDD核心概念的映射关系,确保设计落地有依据:

DDD核心概念 智能客服场景映射
限界上下文 客服交互上下文(核心域):包含客服对话、AI回答、订单查询等核心能力
聚合根 聊天会话(ChatSession):包含会话ID、用户ID、对话记录等,是客服交互的核心聚合根
值对象 用户提问(UserQuery)、客服回答(CustomerAnswer)、订单信息(OrderInfo):无唯一标识,属性不可变
领域服务 客服领域服务(CustomerDomainService):封装核心业务规则(如对话记忆、AI回答生成)
应用服务 客服交互应用服务(CustomerInteractionAppService):协调领域层与基础设施层,处理用户请求
仓储 聊天会话仓储(ChatSessionRepository):定义聚合根的持久化接口,基础设施层实现
基础设施层 通义千问客户端、内存仓储实现、配置管理等
接口层 REST API接口,接收前端/客户端请求

三、工程目录结构(DDD规范)

严格遵循DDD「分层+分包」原则,工程目录结构如下(按职责划分,符合企业级规范):

com.ecommerce.customer.service (根包,对应客服交互限界上下文)
├── CustomerServiceApplication.java  // 应用启动类
├── application/                     // 应用层:协调领域层,处理用例
│   ├── command/                     // 命令对象(写操作)
│   │   └── CustomerChatCommand.java // 客服对话命令
│   ├── dto/                         // 数据传输对象
│   │   ├── ChatRequestDTO.java      // 对话请求DTO
│   │   └── ChatResponseDTO.java     // 对话响应DTO
│   └── service/                     // 应用服务
│       └── CustomerInteractionAppService.java
├── domain/                          // 领域层:核心业务逻辑,领域模型
│   ├── aggregate/                   // 聚合根
│   │   └── ChatSession.java         // 聊天会话聚合根
│   ├── event/                       // 领域事件(可选,这里简化)
│   ├── repository/                  // 仓储接口(领域层定义,基础设施层实现)
│   │   └── ChatSessionRepository.java
│   ├── service/                     // 领域服务:封装跨聚合/值对象的业务规则
│   │   └── CustomerDomainService.java
│   └── valueobject/                 // 值对象:无唯一标识,不可变
│       ├── CustomerAnswer.java
│       ├── OrderInfo.java
│       └── UserQuery.java
├── infrastructure/                  // 基础设施层:外部依赖实现、仓储实现
│   ├── config/                      // 配置类
│   │   └── DddSmartServiceConfig.java
│   ├── client/                      // 外部服务客户端(通义千问)
│   │   └── QwenAiClient.java
│   ├── persistence/                 // 仓储实现
│   │   └── InMemoryChatSessionRepository.java
│   └── tool/                        // 外部工具集成(订单查询)
│       └── OrderQueryTool.java
├── interfaces/                      // 接口层:对外暴露的接口(REST/Feign等)
│   └── rest/                        // REST接口
│       ├── CustomerServiceController.java
│       └── request/                 // 接口请求模型
│           └── ChatRequest.java
└── common/                          // 公共层:通用工具、异常、常量
    ├── constant/                    // 常量
    │   └── CustomerServiceConstant.java
    ├── exception/                   // 自定义异常
    │   ├── ApplicationException.java
    │   └── DomainException.java
    └── util/                        // 通用工具
        └── StringUtils.java

四、核心配置文件

4.1 pom.xml(Maven依赖,适配JDK21+Spring Boot 3.2.0)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- Spring Boot 3.2.0 父工程 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.ecommerce</groupId>
    <artifactId>customer-service-ddd</artifactId>
    <version>1.0.0</version>
    <name>CustomerServiceDDD</name>
    <description>DDD模式智能客服系统(Spring Boot 3.2 + LangChain4j + 通义千问)</description>

    <!-- 核心配置 -->
    <properties>
        <java.version>21</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <langchain4j.version>0.32.0</langchain4j.version>
        <commons-lang3.version>3.14.0</commons-lang3.version>
    </properties>

    <dependencies>
        <!-- Spring Boot 核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- LangChain4j 核心 + 通义千问适配器 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-dashscope</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <!-- 工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.2.0</version>
            </plugin>
            <!-- JDK21 编译插件 -->
            <plugin>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

4.2 application.yml(配置文件,敏感信息隔离)

# 服务器配置
server:
  port: 8080
  servlet:
    context-path: /customer-service
  tomcat:
    uri-encoding: UTF-8

# 通义千问配置(基础设施层用)
dashscope:
  api-key: ${DASHSCOPE_API_KEY:sk-266219f347304b41b9249ce6becd35850} # 环境变量优先
  model: qwen-turbo
  temperature: 0.6
  max-tokens: 1000
  top-p: 0.8
  # 对话记忆配置(领域层用)
  memory:
    window:
      size: 10

# 日志配置(DDD规范:分层日志隔离)
logging:
  charset:
    console: UTF-8
    file: UTF-8
  level:
    root: INFO
    com.ecommerce.customer.service.domain: DEBUG      # 领域层日志
    com.ecommerce.customer.service.application: INFO  # 应用层日志
    com.ecommerce.customer.service.infrastructure: WARN # 基础设施层日志
    com.ecommerce.customer.service.interfaces: INFO   # 接口层日志
    com.alibaba.dashscope: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{60} - %msg%n"

五、分层核心代码实现

5.1 公共层(common)

5.1.1 自定义异常(DomainException.java)
package com.ecommerce.customer.service.common.exception;

/**
 * 领域异常:领域层业务规则违反时抛出
 */
public class DomainException extends RuntimeException {
    public DomainException(String message) {
        super(message);
    }

    public DomainException(String message, Throwable cause) {
        super(message, cause);
    }
}
5.1.2 应用异常(ApplicationException.java)
package com.ecommerce.customer.service.common.exception;

/**
 * 应用异常:应用层处理失败时抛出
 */
public class ApplicationException extends RuntimeException {
    public ApplicationException(String message) {
        super(message);
    }

    public ApplicationException(String message, Throwable cause) {
        super(message, cause);
    }
}
5.1.3 通用工具(StringUtils.java)
package com.ecommerce.customer.service.common.util;

import org.apache.commons.lang3.StringUtils;

/**
 * 字符串工具类(封装第三方工具,领域层解耦)
 */
public class StringUtils extends StringUtils {
    /**
     * 脱敏处理(如API Key、手机号)
     */
    public static String desensitize(String content, int keepLength) {
        if (isEmpty(content)) {
            return "";
        }
        if (content.length() <= keepLength) {
            return content;
        }
        return content.substring(0, keepLength) + "****";
    }
}

5.2 领域层(domain)

5.2.1 值对象(UserQuery.java,JDK21 Record)
package com.ecommerce.customer.service.domain.valueobject;

import java.time.LocalDateTime;

/**
 * 用户提问值对象:无唯一标识,不可变
 */
public record UserQuery(
        String content,       // 提问内容
        LocalDateTime createTime  // 提问时间
) {
    // 工厂方法:保证值对象合法性(DDD值对象规则)
    public static UserQuery create(String content) {
        if (com.ecommerce.customer.service.common.util.StringUtils.isBlank(content)) {
            throw new com.ecommerce.customer.service.common.exception.DomainException("用户提问内容不能为空");
        }
        return new UserQuery(content, LocalDateTime.now());
    }
}
5.2.2 值对象(CustomerAnswer.java)
package com.ecommerce.customer.service.domain.valueobject;

import java.time.LocalDateTime;

/**
 * 客服回答值对象
 */
public record CustomerAnswer(
        String content,        // 回答内容
        LocalDateTime createTime   // 回答时间
) {
    // 工厂方法
    public static CustomerAnswer create(String content) {
        return new CustomerAnswer(content, LocalDateTime.now());
    }
}
5.2.3 值对象(OrderInfo.java)
package com.ecommerce.customer.service.domain.valueobject;

import java.time.LocalDateTime;

/**
 * 订单信息值对象(支撑域:订单查询)
 */
public record OrderInfo(
        String orderId,        // 订单号
        String productName,    // 商品名称
        String size,           // 尺码
        String status,         // 订单状态
        LocalDateTime createTime  // 创建时间
) {
    // 工厂方法:校验订单号合法性
    public static OrderInfo create(String orderId, String productName, String size, String status, LocalDateTime createTime) {
        if (com.ecommerce.customer.service.common.util.StringUtils.isBlank(orderId)) {
            throw new com.ecommerce.customer.service.common.exception.DomainException("订单号不能为空");
        }
        return new OrderInfo(orderId, productName, size, status, createTime);
    }
}
5.2.4 聚合根(ChatSession.java)
package com.ecommerce.customer.service.domain.aggregate;

import com.ecommerce.customer.service.domain.valueobject.CustomerAnswer;
import com.ecommerce.customer.service.domain.valueobject.UserQuery;
import com.ecommerce.customer.service.common.exception.DomainException;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 聊天会话聚合根(DDD核心:聚合根,包含业务规则)
 * 聚合边界:ChatSession + UserQuery + CustomerAnswer
 */
public class ChatSession {
    // 聚合根ID(唯一标识)
    private final String sessionId;
    // 用户ID(外部聚合根ID,关联用户域)
    private final String userId;
    // 对话记录(聚合内的实体/值对象)
    private final List<DialogueRecord> dialogueRecords;
    // 创建时间
    private final LocalDateTime createTime;
    // 最后更新时间
    private LocalDateTime lastUpdateTime;

    // 内部值对象:对话记录(聚合内私有)
    private record DialogueRecord(
            UserQuery userQuery,
            CustomerAnswer customerAnswer
    ) {}

    // 聚合根工厂方法(DDD规范:聚合根只能通过工厂方法创建)
    public static ChatSession create(String userId) {
        if (com.ecommerce.customer.service.common.util.StringUtils.isBlank(userId)) {
            throw new DomainException("用户ID不能为空");
        }
        return new ChatSession(
                UUID.randomUUID().toString(),
                userId,
                new ArrayList<>(),
                LocalDateTime.now(),
                LocalDateTime.now()
        );
    }

    // 私有构造器(禁止外部new)
    private ChatSession(String sessionId, String userId, List<DialogueRecord> dialogueRecords, LocalDateTime createTime, LocalDateTime lastUpdateTime) {
        this.sessionId = sessionId;
        this.userId = userId;
        this.dialogueRecords = dialogueRecords;
        this.createTime = createTime;
        this.lastUpdateTime = lastUpdateTime;
    }

    // 领域行为:添加对话记录(封装业务规则)
    public void addDialogue(UserQuery userQuery, CustomerAnswer customerAnswer) {
        // 业务规则:对话记录数量不超过记忆窗口大小
        int memoryWindowSize = Integer.parseInt(System.getenv("DASHSCOPE_MEMORY_WINDOW_SIZE") != null 
                ? System.getenv("DASHSCOPE_MEMORY_WINDOW_SIZE") 
                : "10");
        if (dialogueRecords.size() >= memoryWindowSize) {
            // 移除最早的记录(FIFO)
            dialogueRecords.remove(0);
        }
        dialogueRecords.add(new DialogueRecord(userQuery, customerAnswer));
        this.lastUpdateTime = LocalDateTime.now();
    }

    // 领域行为:获取历史对话(供领域服务使用)
    public List<String> getHistoryDialogue() {
        List<String> history = new ArrayList<>();
        for (DialogueRecord record : dialogueRecords) {
            history.add("用户:" + record.userQuery().content());
            history.add("客服:" + record.customerAnswer().content());
        }
        return history;
    }

    // 聚合根属性访问器(DDD规范:仅暴露必要的getter)
    public String getSessionId() {
        return sessionId;
    }

    public String getUserId() {
        return userId;
    }

    public LocalDateTime getLastUpdateTime() {
        return lastUpdateTime;
    }
}
5.2.5 仓储接口(ChatSessionRepository.java)
package com.ecommerce.customer.service.domain.repository;

import com.ecommerce.customer.service.domain.aggregate.ChatSession;

import java.util.Optional;

/**
 * 聊天会话仓储接口(领域层定义,基础设施层实现)
 * DDD规范:仓储接口属于领域层,实现属于基础设施层
 */
public interface ChatSessionRepository {
    /**
     * 保存/更新聚合根
     */
    ChatSession save(ChatSession chatSession);

    /**
     * 根据用户ID查询会话
     */
    Optional<ChatSession> findByUserId(String userId);
}
5.2.6 领域服务(CustomerDomainService.java)
package com.ecommerce.customer.service.domain.service;

import com.ecommerce.customer.service.domain.aggregate.ChatSession;
import com.ecommerce.customer.service.domain.repository.ChatSessionRepository;
import com.ecommerce.customer.service.domain.valueobject.CustomerAnswer;
import com.ecommerce.customer.service.domain.valueobject.UserQuery;
import com.ecommerce.customer.service.common.exception.DomainException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 客服领域服务(DDD规范:封装跨聚合/值对象的业务规则)
 * 核心职责:生成AI回答、维护对话会话
 */
@Service
public class CustomerDomainService {
    private static final Logger log = LoggerFactory.getLogger(CustomerDomainService.class);
    // 依赖仓储接口(领域层仅依赖接口,解耦基础设施实现)
    private final ChatSessionRepository chatSessionRepository;
    // 依赖AI客户端(基础设施层实现,通过构造器注入)
    private final com.ecommerce.customer.service.infrastructure.client.QwenAiClient qwenAiClient;
    // 依赖订单查询工具(基础设施层)
    private final com.ecommerce.customer.service.infrastructure.tool.OrderQueryTool orderQueryTool;

    // 构造器注入(DDD+Spring规范)
    public CustomerDomainService(ChatSessionRepository chatSessionRepository,
                                 com.ecommerce.customer.service.infrastructure.client.QwenAiClient qwenAiClient,
                                 com.ecommerce.customer.service.infrastructure.tool.OrderQueryTool orderQueryTool) {
        this.chatSessionRepository = chatSessionRepository;
        this.qwenAiClient = qwenAiClient;
        this.orderQueryTool = orderQueryTool;
    }

    /**
     * 核心领域行为:处理用户提问,生成回答
     */
    public CustomerAnswer handleUserQuery(String userId, UserQuery userQuery) {
        // 1. 获取/创建用户会话(聚合根操作)
        ChatSession chatSession = chatSessionRepository.findByUserId(userId)
                .orElseGet(() -> ChatSession.create(userId));
        log.debug("领域服务:处理用户[{}]会话,会话ID:{}", userId, chatSession.getSessionId());

        // 2. 构建上下文(历史对话+当前提问)
        List<String> historyDialogue = chatSession.getHistoryDialogue();
        String prompt = buildPrompt(historyDialogue, userQuery.content());

        // 3. 调用AI生成回答(基础设施层能力)
        String aiAnswer = qwenAiClient.generateAnswer(prompt);
        CustomerAnswer customerAnswer = CustomerAnswer.create(aiAnswer);

        // 4. 更新会话(聚合根领域行为)
        chatSession.addDialogue(userQuery, customerAnswer);
        chatSessionRepository.save(chatSession);

        return customerAnswer;
    }

    /**
     * 领域规则:构建AI Prompt(封装Prompt工程)
     */
    private String buildPrompt(List<String> historyDialogue, String currentQuery) {
        // 系统提示词(领域规则)
        String systemPrompt = """
                你是某电商平台的智能客服,需遵守以下规则:
                1. 语气友好、专业,使用中文回复;
                2. 优先回答商品退换货、物流时效等通用问题:
                   - 退换货政策:签收后7天内无理由退货,质量问题运费商家承担,非质量问题用户承担;
                   - 物流时效:江浙沪次日达,其他地区3-5天,偏远地区7天;
                3. 若用户询问订单,调用订单查询工具获取详情;
                4. 记住用户上下文,支持多轮对话;
                5. 无法回答的问题引导联系人工客服(400-123-4567)。
                """;

        // 拼接历史对话+当前提问
        StringBuilder promptBuilder = new StringBuilder(systemPrompt);
        promptBuilder.append("\n【历史对话】:\n");
        historyDialogue.forEach(line -> promptBuilder.append(line).append("\n"));
        promptBuilder.append("\n【当前提问】:").append(currentQuery);

        return promptBuilder.toString();
    }
}

5.3 基础设施层(infrastructure)

5.3.1 通义千问客户端(QwenAiClient.java)
package com.ecommerce.customer.service.infrastructure.client;

import com.ecommerce.customer.service.common.exception.ApplicationException;
import com.ecommerce.customer.service.common.util.StringUtils;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.dashscope.QwenChatModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 通义千问AI客户端(基础设施层:外部服务集成)
 * DDD规范:基础设施层封装外部依赖,领域层不感知具体实现
 */
@Component
public class QwenAiClient {
    private static final Logger log = LoggerFactory.getLogger(QwenAiClient.class);

    // 从配置文件注入参数
    @Value("${dashscope.api-key}")
    private String apiKey;
    @Value("${dashscope.model:qwen-turbo}")
    private String modelName;
    @Value("${dashscope.temperature:0.6}")
    private Float temperature;
    @Value("${dashscope.max-tokens:1000}")
    private Integer maxTokens;
    @Value("${dashscope.top-p:0.8}")
    private Float topP;

    // 构建AI模型(懒加载)
    private ChatLanguageModel qwenChatModel;

    /**
     * 生成AI回答(对外暴露的能力)
     */
    public String generateAnswer(String prompt) {
        try {
            // 初始化模型(懒加载)
            if (qwenChatModel == null) {
                initQwenModel();
            }
            // 调用LangChain4j生成回答
            return qwenChatModel.generate(dev.langchain4j.message.ChatMessage.user(prompt)).content();
        } catch (Exception e) {
            log.error("通义千问调用失败,prompt:{}", prompt.substring(0, Math.min(prompt.length(), 100)), e);
            throw new ApplicationException("AI回答生成失败:" + e.getMessage());
        }
    }

    /**
     * 初始化通义千问模型(基础设施层内部实现)
     */
    private void initQwenModel() {
        // 校验API Key
        if (StringUtils.isBlank(apiKey)) {
            throw new ApplicationException("通义千问API Key未配置");
        }
        log.info("初始化通义千问模型:{},API Key:{}", modelName, StringUtils.desensitize(apiKey, 6));

        // 构建模型(LangChain4j)
        qwenChatModel = QwenChatModel.builder()
                .apiKey(apiKey)
                .modelName(modelName)
                .temperature(temperature)
                .maxTokens(maxTokens)
                .topP(topP)
                .build();
    }
}
5.3.2 订单查询工具(OrderQueryTool.java)
package com.ecommerce.customer.service.infrastructure.tool;

import com.ecommerce.customer.service.domain.valueobject.OrderInfo;
import com.ecommerce.customer.service.common.exception.DomainException;
import dev.langchain4j.agent.tool.Tool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 订单查询工具(基础设施层:外部工具集成)
 * 模拟对接真实订单系统,生产环境可替换为MyBatis/Feign
 */
@Component
public class OrderQueryTool {
    private static final Logger log = LoggerFactory.getLogger(OrderQueryTool.class);
    // 模拟订单数据库
    private static final Map<String, OrderInfo> ORDER_DB = new ConcurrentHashMap<>();

    // 初始化测试数据
    static {
        ORDER_DB.put("ORD123456", OrderInfo.create(
                "ORD123456",
                "男士纯棉T恤",
                "XL",
                "已发货",
                LocalDateTime.now().minusDays(2)
        ));
        ORDER_DB.put("ORD789012", OrderInfo.create(
                "ORD789012",
                "女士连衣裙",
                "M",
                "待发货",
                LocalDateTime.now()
        ));
    }

    /**
     * LangChain4j工具方法(供AI调用)
     */
    @Tool(value = "查询订单信息", description = "根据订单号查询订单详情,参数为订单号(如ORD123456)")
    public OrderInfo queryOrder(String orderId) {
        log.info("订单查询工具:查询订单号{}", orderId);
        if (StringUtils.isBlank(orderId)) {
            throw new DomainException("订单号不能为空");
        }
        OrderInfo orderInfo = ORDER_DB.get(orderId);
        if (orderInfo == null) {
            throw new DomainException("订单不存在:" + orderId);
        }
        return orderInfo;
    }
}
5.3.3 仓储实现(InMemoryChatSessionRepository.java)
package com.ecommerce.customer.service.infrastructure.persistence;

import com.ecommerce.customer.service.domain.aggregate.ChatSession;
import com.ecommerce.customer.service.domain.repository.ChatSessionRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 内存版聊天会话仓储实现(基础设施层)
 * 生产环境可替换为Redis/MongoDB/MySQL实现
 */
@Repository
public class InMemoryChatSessionRepository implements ChatSessionRepository {
    private static final Logger log = LoggerFactory.getLogger(InMemoryChatSessionRepository.class);
    // 内存存储(模拟持久化)
    private final Map<String, ChatSession> sessionMap = new ConcurrentHashMap<>();

    @Override
    public ChatSession save(ChatSession chatSession) {
        log.debug("仓储实现:保存会话,用户ID:{},会话ID:{}", chatSession.getUserId(), chatSession.getSessionId());
        sessionMap.put(chatSession.getUserId(), chatSession);
        return chatSession;
    }

    @Override
    public Optional<ChatSession> findByUserId(String userId) {
        log.debug("仓储实现:查询用户{}的会话", userId);
        return Optional.ofNullable(sessionMap.get(userId));
    }
}
5.3.4 配置类(DddSmartServiceConfig.java)
package com.ecommerce.customer.service.infrastructure.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * DDD配置类(基础设施层)
 * 启用配置属性绑定,整合外部依赖
 */
@Configuration
@EnableConfigurationProperties
public class DddSmartServiceConfig {
    // 基础设施层配置:如AI模型、仓储、工具等由Spring自动装配
}

5.4 应用层(application)

5.4.1 命令对象(CustomerChatCommand.java)
package com.ecommerce.customer.service.application.command;

/**
 * 客服对话命令(应用层:写操作命令对象)
 * DDD规范:命令对象封装写操作参数
 */
public record CustomerChatCommand(
        String userId,       // 用户ID
        String userQuery     // 用户提问内容
) {
    // 命令校验
    public void validate() {
        if (StringUtils.isBlank(userId)) {
            throw new com.ecommerce.customer.service.common.exception.ApplicationException("用户ID不能为空");
        }
        if (StringUtils.isBlank(userQuery)) {
            throw new com.ecommerce.customer.service.common.exception.ApplicationException("用户提问不能为空");
        }
    }
}
5.4.2 DTO(ChatRequestDTO.java)
package com.ecommerce.customer.service.application.dto;

/**
 * 对话请求DTO(应用层:数据传输对象)
 */
public record ChatRequestDTO(
        String userId,
        String userQuery
) {}
5.4.3 DTO(ChatResponseDTO.java)
package com.ecommerce.customer.service.application.dto;

/**
 * 对话响应DTO
 */
public record ChatResponseDTO(
        String answer,
        String sessionId,
        long responseTime // 响应耗时(毫秒)
) {}
5.4.4 应用服务(CustomerInteractionAppService.java)
package com.ecommerce.customer.service.application.service;

import com.ecommerce.customer.service.application.command.CustomerChatCommand;
import com.ecommerce.customer.service.application.dto.ChatRequestDTO;
import com.ecommerce.customer.service.application.dto.ChatResponseDTO;
import com.ecommerce.customer.service.domain.service.CustomerDomainService;
import com.ecommerce.customer.service.domain.valueobject.UserQuery;
import com.ecommerce.customer.service.common.exception.ApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * 客服交互应用服务(应用层:协调领域层,处理用户用例)
 * DDD规范:应用服务不包含业务规则,仅做协调、参数转换、事务管理
 */
@Service
public class CustomerInteractionAppService {
    private static final Logger log = LoggerFactory.getLogger(CustomerInteractionAppService.class);
    // 依赖领域服务(应用层依赖领域层)
    private final CustomerDomainService customerDomainService;

    // 构造器注入
    public CustomerInteractionAppService(CustomerDomainService customerDomainService) {
        this.customerDomainService = customerDomainService;
    }

    /**
     * 处理客服对话请求(核心应用服务)
     */
    public ChatResponseDTO handleChat(ChatRequestDTO requestDTO) {
        long startTime = System.currentTimeMillis();
        try {
            // 1. 转换为命令对象
            CustomerChatCommand command = new CustomerChatCommand(
                    requestDTO.userId(),
                    requestDTO.userQuery()
            );
            // 2. 命令校验
            command.validate();
            log.info("应用服务:处理用户{}的对话请求,提问:{}", command.userId(), command.userQuery());

            // 3. 转换为领域对象
            UserQuery userQuery = UserQuery.create(command.userQuery());

            // 4. 调用领域服务(核心业务逻辑)
            var customerAnswer = customerDomainService.handleUserQuery(command.userId(), userQuery);

            // 5. 构建响应DTO(应用层转换)
            long responseTime = System.currentTimeMillis() - startTime;
            return new ChatResponseDTO(
                    customerAnswer.content(),
                    // 此处简化,实际应从会话聚合根获取
                    "SESSION-" + command.userId(),
                    responseTime
            );
        } catch (Exception e) {
            log.error("应用服务:处理对话请求失败", e);
            throw new ApplicationException("对话处理失败:" + e.getMessage());
        }
    }
}

5.5 接口层(interfaces)

5.5.1 请求模型(ChatRequest.java)
package com.ecommerce.customer.service.interfaces.rest.request;

/**
 * REST接口请求模型(接口层:对外暴露的请求模型)
 */
public record ChatRequest(
        String userId,
        String userQuery
) {}
5.5.2 REST控制器(CustomerServiceController.java)
package com.ecommerce.customer.service.interfaces.rest;

import com.ecommerce.customer.service.application.dto.ChatRequestDTO;
import com.ecommerce.customer.service.application.dto.ChatResponseDTO;
import com.ecommerce.customer.service.application.service.CustomerInteractionAppService;
import com.ecommerce.customer.service.interfaces.rest.request.ChatRequest;
import com.ecommerce.customer.service.common.exception.ApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * 客服服务REST控制器(接口层:对外暴露API)
 * DDD规范:接口层仅做参数接收、转换,调用应用服务
 */
@RestController
@RequestMapping("/api/v1/chat")
public class CustomerServiceController {
    private static final Logger log = LoggerFactory.getLogger(CustomerServiceController.class);
    // 依赖应用服务(接口层依赖应用层)
    private final CustomerInteractionAppService customerInteractionAppService;

    // 构造器注入
    public CustomerServiceController(CustomerInteractionAppService customerInteractionAppService) {
        this.customerInteractionAppService = customerInteractionAppService;
    }

    /**
     * 客服对话接口
     */
    @PostMapping
    public ResponseEntity<?> chat(@RequestBody ChatRequest request) {
        try {
            // 1. 接口请求模型转换为应用层DTO
            ChatRequestDTO requestDTO = new ChatRequestDTO(
                    request.userId(),
                    request.userQuery()
            );
            // 2. 调用应用服务
            ChatResponseDTO responseDTO = customerInteractionAppService.handleChat(requestDTO);
            // 3. 构建响应
            return ResponseEntity.ok(Map.of(
                    "code", 200,
                    "message", "success",
                    "data", responseDTO
            ));
        } catch (ApplicationException e) {
            log.error("接口层:处理对话请求失败", e);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(
                            "code", 400,
                            "message", e.getMessage(),
                            "data", null
                    ));
        } catch (Exception e) {
            log.error("接口层:系统异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of(
                            "code", 500,
                            "message", "系统内部错误",
                            "data", null
                    ));
        }
    }
}

5.6 启动类(CustomerServiceApplication.java)

package com.ecommerce.customer.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 应用启动类(DDD限界上下文入口)
 */
@SpringBootApplication
public class CustomerServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerServiceApplication.class, args);
        System.out.println("===== DDD模式智能客服系统启动成功(端口:8080) =====");
    }
}

六、部署与测试

6.1 启动应用

# 1. 配置环境变量(生产环境)
export DASHSCOPE_API_KEY=你的通义千问API Key

# 2. 打包应用
mvn clean package -DskipTests

# 3. 启动应用
java -jar target/customer-service-ddd-1.0.0.jar

6.2 接口测试(Curl)

curl -X POST http://localhost:8080/customer-service/api/v1/chat \
-H "Content-Type: application/json" \
-d '{
    "userId": "USER001",
    "userQuery": "查询订单ORD123456"
}'

6.3 预期响应

{
  "code": 200,
  "message": "success",
  "data": {
    "answer": "你查询的订单信息如下:\n订单号:ORD123456\n商品名称:男士纯棉T恤\n尺码:XL\n状态:已发货\n创建时间:2025-12-22T10:05:30",
    "sessionId": "SESSION-USER001",
    "responseTime": 500
  }
}

七、DDD落地要点总结

  1. 分层隔离:严格遵循「接口层→应用层→领域层→基础设施层」依赖方向,领域层不依赖任何外层;
  2. 聚合根设计:ChatSession作为聚合根,封装内部业务规则,仅通过工厂方法创建;
  3. 值对象不可变:使用JDK21 Record实现值对象,通过工厂方法保证合法性;
  4. 仓储解耦:领域层定义仓储接口,基础设施层实现,便于替换持久化方式;
  5. 业务规则沉淀:所有核心业务规则(如对话记忆、Prompt构建)沉淀在领域层,应用层仅做协调;
  6. 外部依赖封装:通义千问、订单查询等外部依赖封装在基础设施层,领域层无感知。

八、总结

本文基于DDD思想重构了智能客服系统,结合Spring Boot 3.2.0、JDK21、LangChain4j和通义千问,实现了「领域驱动+大模型」的企业级应用开发。核心价值在于:

  • 业务可维护性:领域层沉淀核心业务规则,脱离技术框架依赖;
  • 可扩展性:限界上下文、分层设计便于后续扩展多模型、多渠道客服;
  • 可测试性:领域层可独立测试,无需依赖外部服务;
  • 技术解耦:基础设施层封装外部依赖,便于替换AI模型、持久化方式。

该架构可直接复用至金融、政务等领域的智能问答系统,仅需调整领域层业务规则和Prompt即可快速适配。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐