Skip to content
Go back

从事务脚本到领域模型:一次重构的进化之旅

Published:  at  12:00 AM

从事务脚本到领域模型:一次重构的进化之旅

引言:写API,如何优雅处理复杂业务逻辑?🤔

在后端开发,尤其是 .NET 或企业级应用领域,我们经常会问自己:**“业务逻辑到底应该放在哪里?”**刚起步时,大家往往采用事务脚本(Transaction Script),简单直观;但随着业务增长,这种方案也许会让你掉进维护的“泥潭”。今天,我们就通过一个健身追踪App的实战案例,聊聊如何优雅地完成从事务脚本到领域模型(Domain Model)的重构进阶!

一、事务脚本:简单直观,但会越写越乱

什么是事务脚本?

事务脚本模式,是一种最直接的实现方式。每个用例(如新增运动记录)都对应一个独立的方法或类,里面包含了所有业务逻辑、数据校验、数据访问等操作。

示例代码

internal sealed class AddExercisesCommandHandler(
    IWorkoutRepository workoutRepository,
    IUnitOfWork unitOfWork)
    : ICommandHandler<AddExercisesCommand>
{
    public async Task<Result> Handle(
        AddExercisesCommand request,
        CancellationToken cancellationToken)
    {
        Workout? workout = await workoutRepository.GetByIdAsync(
            request.WorkoutId,
            cancellationToken);

        if (workout is null)
        {
            return Result.Failure(WorkoutErrors.NotFound(request.WorkoutId));
        }

        List<Error> errors = [];
        foreach (ExerciseRequest exerciseDto in request.Exercises)
        {
            if (exerciseDto.TargetType == TargetType.Distance &&
                exerciseDto.DistanceInMeters is null)
            {
                errors.Add(ExerciseErrors.MissingDistance);
                continue;
            }

            if (exerciseDto.TargetType == TargetType.Time &&
                exerciseDto.DurationInSeconds is null)
            {
                errors.Add(ExerciseErrors.MissingDuration);
                continue;
            }

            var exercise = new Exercise(
                Guid.NewGuid(),
                workout.Id,
                exerciseDto.ExerciseType,
                exerciseDto.TargetType,
                exerciseDto.DistanceInMeters,
                exerciseDto.DurationInSeconds);

            workouts.Exercises.Add(exercise);
        }

        if (errors.Count != 0)
        {
            return Result.Failure(new ValidationError(errors.ToArray()));
        }

        await unitOfWork.SaveChangesAsync(cancellationToken);

        return Result.Success();
    }
}

随着业务增长会出现的问题

举个栗子,如果需要限制“每个训练最多只能有10个动作”,你可能会在事务脚本里加一堆判断,代码如下:

// 省略前面代码...
if (workouts.Exercise.Count > 10)
{
    return Result.Failure(
        WorkoutErrors.MaxExercisesReached(workout.Id));
}

业务越来越复杂时,这种“堆砌”很快让代码失控。

二、领域模型:让业务逻辑回归业务

什么是领域模型?

领域模型(Domain Model)强调用对象来承载和封装领域逻辑,数据和行为合一。它让你的代码像讲故事一样贴近业务——这也是 DDD(领域驱动设计)的核心思想。

“An object model of the domain that incorporates both behavior and data.”
—— Martin Fowler,《企业应用架构模式》

如何重构?——把业务逻辑“下沉”到领域对象

我们将核心规则从事务脚本“搬”到 Workout 领域对象:

public sealed class Workout
{
    private readonly List<Exercise> _exercises = [];

    // 构造函数和属性略

    public Result AddExercises(ExerciseModel[] exercises)
    {
        List<Error> errors = [];
        foreach (var exerciseModel in exercises)
        {
            if (exerciseModel.TargetType == TargetType.Distance &&
                exerciseModel.DistanceInMeters is null)
            {
                errors.Add(ExerciseErrors.MissingDistance);
                continue;
            }

            if (exerciseModel.TargetType == TargetType.Time &&
                exerciseModel.DurationInSeconds is null)
            {
                errors.Add(ExerciseErrors.MissingDuration);
                continue;
            }

            var exercise = new Exercise(
                Guid.NewGuid(),
                this.Id,
                exerciseModel.ExerciseType,
                exerciseModel.TargetType,
                exerciseModel.DistanceInMeters,
                exerciseModel.DurationInSeconds);

            _exercises.Add(exercise);

            if (_exercises.Count > 10)
            {
                return Result.Failure(
                    WorkoutErrors.MaxExercisesReached(this.Id));
            }
        }

        if (errors.Count != 0)
        {
            return Result.Failure(new ValidationError(errors.ToArray()));
        }

        return Result.Success();
    }
}

重构后的优势

重构后,事务脚本只需协调调用:

var exercises = request.Exercises.Select(e => e.ToModel()).ToArray();
var result = workout.AddExercises(exercises);
// 后续持久化等

三、实战建议:何时从事务脚本转向领域模型?

结论&互动:你遇到过哪些“烂尾”的业务逻辑?👀

从事务脚本到领域模型,是 .NET/企业级开发者进阶架构设计能力的重要一步。合理选型、适时重构,让你的代码既能快速响应需求,又不失优雅与可维护性。

👉 你的项目里,有哪些“难以维护”的业务逻辑?你是如何解决的?欢迎评论区一起讨论或分享你的经验!


如果觉得本文对你有帮助,记得点赞、收藏或转发给更多朋友! 🚀



Next Post
C#实用函数式编程:让代码更安全、可维护的五大技巧