业务场景:用户登录
假设我们有一个简单的用户登录功能:
- 用户输入用户名密码
- 验证用户名密码是否正确
- 登录成功后返回用户信息
方式一:传统MVC架构
java
// ==================== 1. Controller层 ====================
@RestController
public class UserController {
@Autowired
private UserService userService; // 依赖Service
/**
* 处理用户登录请求
*/
@PostMapping("/login")
public User login(@RequestBody LoginRequest request) {
// Controller只负责接收请求和返回响应
return userService.login(request.getUsername(), request.getPassword());
}
}
// ==================== 2. Service层 ====================
// Service接口
public interface UserService {
User login(String username, String password);
}
// Service实现
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; // :依赖Mapper接口
@Autowired
private PasswordEncoder passwordEncoder; // 依赖加密组件
@Override
public User login(String username, String password) {
// 1. 查询数据库
User user = userMapper.selectByUsername(username);
// 2. 验证用户是否存在
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 3. 验证密码是否正确
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException("密码错误");
}
// 4. 返回用户信息
return user;
}
}
// ==================== 3. Mapper层 ====================
// Mapper接口
public interface UserMapper {
User selectByUsername(String username);
}
// Mapper实现(由MyBatis自动生成)
// 这个实现会真正执行SQL:SELECT * FROM users WHERE username = ?
依赖关系:
Controller → UserService接口 → UserServiceImpl → UserMapper接口 → 数据库
方式二:DDD架构(依赖倒置)
java
// ==================== 1. 领域层(核心业务) ====================
// 领域层定义:我需要什么能力?(不关心如何实现)
/**
* 用户仓储接口 - 在领域层定义
* 作用:领域层说"我需要从某个地方获取用户数据"
*/
public interface UserRepository {
/**
* 根据用户名查找用户
*/
User findByUsername(String username);
}
/**
* 加密服务接口 - 在领域层定义
* 作用:领域层说"我需要密码加密验证的能力"
*/
public interface EncryptionService {
/**
* 验证密码是否匹配
*/
boolean matches(String rawPassword, String encodedPassword);
}
// ==================== 2. 领域服务 ====================
/**
* 用户登录领域服务
* 作用:实现纯粹的业务逻辑
*/
@Service
public class UserLoginService {
// ✅ 关键:依赖自己定义的接口,不依赖具体实现
private final UserRepository userRepository;
private final EncryptionService encryptionService;
public UserLoginService(UserRepository userRepo, EncryptionService encryptionService) {
this.userRepository = userRepo;
this.encryptionService = encryptionService;
}
/**
* 执行用户登录业务逻辑
*/
public User login(String username, String password) {
// 1. 查询用户(调用接口,不关心数据从哪里来)
User user = userRepository.findByUsername(username);
// 2. 业务验证:用户是否存在
if (user == null) {
throw new RuntimeException("用户不存在"); // 业务异常
}
// 3. 业务验证:密码是否正确
if (!encryptionService.matches(password, user.getPassword())) {
throw new RuntimeException("密码错误"); // 业务异常
}
// 4. 返回用户信息
return user;
}
}
// ==================== 3. 基础设施层(技术实现) ====================
/**
* UserRepository的实现 - 在基础设施层
* 作用:用MyBatis技术实现数据访问
*/
@Repository
public class MyBatisUserRepository implements UserRepository {
// 这里可以使用MyBatis的Mapper
@Autowired
private UserMapper userMapper;
@Override
public User findByUsername(String username) {
// 调用MyBatis Mapper执行SQL
return userMapper.selectByUsername(username);
}
}
/**
* EncryptionService的实现 - 在基础设施层
* 作用:用Spring Security技术实现密码加密
*/
@Component
public class BCryptEncryptionService implements EncryptionService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public boolean matches(String rawPassword, String encodedPassword) {
// 调用Spring Security的密码验证
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
// ==================== 4. Controller层 ====================
@RestController
public class LoginController {
@Autowired
private UserLoginService userLoginService; // 依赖领域服务
@PostMapping("/login")
public User login(@RequestBody LoginRequest request) {
// Controller直接调用领域服务
return userLoginService.login(request.getUsername(), request.getPassword());
}
}
依赖关系:
Controller → UserLoginService → UserRepository接口 ← 实现 ← MyBatisUserRepository
→ EncryptionService接口 ← 实现 ← BCryptEncryptionService
核心区别总结
| 方面 | 传统MVC | DDD方式 |
|---|---|---|
| 接口定义位置 | Service层定义业务接口 Mapper层定义数据接口 | 领域层定义业务需求接口 |
| Service依赖什么 | UserMapper(数据访问接口) |
UserRepository(业务数据接口) EncryptionService(业务能力接口) |
| 业务逻辑位置 | Service实现中,与技术代码混合 | 领域服务中,纯业务逻辑 |
| 技术实现位置 | Service中直接使用技术组件 | 基础设施层实现领域接口 |
| 可测试性 | 需要mock数据库、加密组件 | 只需mock业务接口 |
| 变更影响 | 换加密算法要改Service代码 | 换加密算法只需换基础设施实现 |
问题
"传统mvc架构下不是还有mapper层吗,依赖mapper层的接口呀。"
传统MVC确实有Mapper层接口,但关键区别在于:
传统MVC的问题:
java
// Service直接依赖数据访问技术
public class UserServiceImpl {
private UserMapper userMapper; // 依赖MyBatis的Mapper
public User login(...) {
User user = userMapper.selectByUsername(...); // 直接数据操作
// 业务逻辑与数据访问深度耦合
}
}
DDD的改进:
java
// 领域服务依赖业务接口,不依赖具体技术
public class UserLoginService {
private UserRepository userRepository; // 依赖业务接口
public User login(...) {
User user = userRepository.findByUsername(...); // 业务语义操作
// 纯业务逻辑,不知道底层用MyBatis还是JPA
}
}
核心思想:
- 传统MVC:Service → Mapper接口(仍然依赖具体的数据访问技术)
- DDD:领域服务 → 业务接口 ← 基础设施实现(业务层完全与技术解耦)
这样做的最大好处是:当需要更换数据库技术(比如从MySQL换到MongoDB)时,传统MVC要修改Service代码,而DDD只需要换一个基础设施实现,领域层完全不用动!