微操征霸
351.42M · 2026-02-04
在DDD(领域驱动设计)实践中,一个常见的问题是: Service层是否应该返回Result结果?Result结果应该只在Controller里返回吗?
这是一个关于分层架构和领域纯粹性的重要问题。让我们通过实际项目重构来探讨这个话题。
在我们的项目中,存在两种Result类型:
// Service接口混合使用了两种Result
public interface JobActionService {
void LaunchCurrentJobAsync();
OperationResult<String, String> LaunchJob(long
id);
Result PauseCurrentJob(); //
Service层返回HTTP Result
Result ResumeCurrentJob(); //
Service层返回HTTP Result
OperationResult<String, String>
TerminateCurrentJob();
}
// Service实现直接返回HTTP Result
@Override
public Result PauseCurrentJob() {
if (RunningParameter.jobStatus == null) {
return Result.fail("任务未开启"); //
直接返回HTTP Result
}
RunningParameter.jobStatus = 2;
return Result.ok();
}
这种设计违反了DDD的分层原则,Service层被HTTP层的概念污染了。
很多人认为Service层不应该返回Result,但实际上需要区分:
public class OperationResult<T, E> {
private T value; // 成功时的领域值
private E error; // 失败时的领域错误
private boolean success;
}
这与以下概念是等价的:
实际上,函数式编程与DDD非常契合:
// DDD值对象
public class Money {
private final BigDecimal amount;
private final Currency currency;
}
// 函数式风格:操作返回新对象而不是修改状态
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(this.amount.add(other.
amount), this.currency);
}
// 领域服务:纯函数,无副作用
public class PricingService {
public OperationResult<Price, PricingError>
calculatePrice(
Product product,
Customer customer
) {
if (product.isOutOfStock()) {
return OperationResult.failure
(PricingError.OUT_OF_STOCK);
}
BigDecimal discount = customer.
getDiscountRate();
Price price = product.getBasePrice().
applyDiscount(discount);
return OperationResult.success(price);
}
}
Controller层 (HTTP)
↓ 调用
Application层 (用例编排)
↓ 调用
Domain层 (业务逻辑)
↓ 使用
OperationResult<T, E> (领域结果类型)
package cn.south.fmos.client.domain.enums;
import lombok.Getter;
@Getter
public enum JobActionError {
JOB_NOT_STARTED("任务未开启"),
JOB_ALREADY_STOPPED("任务已停止,无需暂停"),
JOB_ALREADY_RUNNING("任务正在运行"),
JOB_CANNOT_RESUME("任务已停止,无法重启");
private final String message;
JobActionError(String message) {
this.message = message;
}
}
步骤2:修改Service接口
public interface JobActionService {
void LaunchCurrentJobAsync();
OperationResult<String, String> LaunchJob(long
id);
OperationResult<String, JobActionError>
PauseCurrentJob(); // 使用
OperationResult
OperationResult<String, JobActionError>
ResumeCurrentJob(); // 使用
OperationResult
OperationResult<String, String>
TerminateCurrentJob();
}
步骤3:修改Service实现
@Override
public OperationResult<String, JobActionError>
PauseCurrentJob() {
if (RunningParameter.jobStatus == null) {
return OperationResult.failure
(JobActionError.JOB_NOT_STARTED);
}
if (RunningParameter.jobStatus == 0) {
return OperationResult.failure
(JobActionError.JOB_ALREADY_STOPPED);
}
RunningParameter.jobStatus = 2;
return OperationResult.success("任务已暂停
"); // 返回成功消息
}
@Override
public OperationResult<String, JobActionError>
ResumeCurrentJob() {
if (RunningParameter.jobStatus == null) {
return OperationResult.failure
(JobActionError.JOB_NOT_STARTED);
}
if (RunningParameter.jobStatus == 1) {
return OperationResult.failure
(JobActionError.JOB_ALREADY_RUNNING);
}
if (RunningParameter.jobStatus == 0) {
return OperationResult.failure
(JobActionError.JOB_CANNOT_RESUME);
}
LaunchCurrentJobAsync();
return OperationResult.success("任务已恢复");
}
步骤4:修改Controller层
@PostMapping("/pause")
public Result pause() throws CustomException {
OperationResult<String, JobActionError> result
= jobActionService.PauseCurrentJob();
return result.fold(
success -> Result.ok
(success), // 使用Service返回
的消息
error -> Result.fail(error.getMessage())
);
}
@PostMapping("/resume")
public Result resume() throws CustomException {
OperationResult<String, JobActionError> result
= jobActionService.ResumeCurrentJob();
return result.fold(
success -> Result.ok
(success), // 使用Service返回
的消息
error -> Result.fail(error.getMessage())
);
}
最初我们尝试使用 OperationResult<Void, JobActionError> ,但遇到了问题:
// 这样会抛出NullPointerException
return OperationResult.success(null);
原因: OperationResult.success(T value) 方法使用了 Objects.requireNonNull(value) 。
// 使用String类型,返回成功消息
OperationResult<String, JobActionError>
PauseCurrentJob();
// Service实现返回具体消息
return OperationResult.success("任务已暂停");
// Controller直接使用Service返回的消息
return result.fold(
success -> Result.ok(success),
error -> Result.fail(error.getMessage())
);
public <R> R fold(Function<T, R> successMapper,
Function<E, R> failureMapper) {
return success ? successMapper.apply(value) :
failureMapper.apply(error);
}
fold 方法已经帮我们 解包 了 OperationResult :
return result.fold(
success -> Result.ok(success), //
success已经是String,不需要getValue()
error -> Result.fail(error.getMessage()) //
error是JobActionError,需要getMessage()
);
层级 使用类型 职责 Controller层 Result HTTP响应封装,负责将领域结果转换为API响应 Service层 OperationResult<T, E> 领域操作结果,表达业务成功/失败,使用领域错误类型 Domain层 JobActionError 领域错误枚举,定义业务错误类型
在DDD实践中,Service层 可以 返回Result,但要注意: