代码洁癖必看:如何用 DDD 让你的半导体测试机业务对象“活”起来?
从 MVC 到 DDD:不仅仅是包名变了,而是“对象”活了

引言

在半导体测试机(Probe/Final Test Equipment)开发中,业务逻辑越来越复杂:测试配方、晶圆批次、Die 测试规则、机台状态机、良率计算、异常中断等相互交织。传统的 MVC(贫血模型)很快就会变成“上帝类 Service”,几千行 if-else 堆在一起,改一个测试规则就要动半套系统。

DDD(领域驱动设计)就是给测试机代码的一把“手术刀”。它让 TestJob、Recipe、Wafer 等业务对象真正“活”起来,自己负责自己的规则,Service 只负责“指挥”。

下面全部用 C# + .NET 8(ASP.NET Core + EF Core)重写,贴合半导体测试机真实场景,并给出 比订单例子多 3 倍的详细案例


一、核心心法:从“找说明书”到“问当事人”

MVC(贫血模型)
TestJobEntity 只存 Id、Status、TotalDieCount 等字段,所有判断逻辑都在 TestJobService 里。

DDD(充血模型)
TestJob 类自己拥有 Abort()ChangeRecipe()CalculateYield() 等方法,状态机、业务不变量全部封装在领域对象内部。

半导体专属例子

  • 测试机正在跑 300mm 晶圆(已执行 40%),突然收到“中止”指令。
    MVC:Service 里 200 行 if-else 判断机台状态、已测试 Die 数、是否在 Probe 卡针中…
    DDD:testJob.Abort() 自己判断 if (CurrentStage == Stage.Probing) throw new BusinessException("探针接触中禁止中止"),逻辑只改这里。

二、架构连连看:DDD 与 MVC 的映射关系(半导体版)

层次 DDD 命名 MVC 对应 职责增强点(测试机场景)
接口层 Interfaces / Controllers Controller 只做 DTO 校验、Swagger、WebSocket 推送机台状态
应用层 Application Services Service(轻) 指挥官:编排领域对象,不写任何业务 if
领域层 Domain Entity(重) 核心:TestJob、Recipe 包含所有测试规则、状态机、良率算法
基础层 Infrastructure DAO / Repository 插件化:支持 EF Core、Dapper、OPC-UA、SECS/GEM 协议切换

三、为什么 DDD 的包变多了?(半导体测试机痛点解耦)

  1. 业务逻辑与流程解耦(Application vs Domain)
    → 测试规则(“Probe 卡针压力不能超过 5g”)永远在 TestJob 里,ApplicationService 只管“取出来 → 调用方法 → 保存”。

  2. 业务逻辑与硬件/存储解耦(Domain vs Infrastructure)
    → 领域层根本不知道你是连 EF Core、Redis 还是 SECS/GEM 通讯协议。以后机台从 KLA 换成 Teradyne,Domain 层一行代码都不用改!


四、模型的三大成员(半导体测试机战术建模)—— 更详细案例

1. 实体 (Entity) —— 有唯一标识 + 生命周期

例子TestJob(测试作业)、Wafer(晶圆)

public class TestJob : Entity<long> // 继承带 Id 的基类
{
    public long Id { get; private set; }
    public string JobNo { get; private set; }   // 业务唯一标识
    public TestJobStatus Status { get; private set; }
    public List<DieTestResult> DieResults { get; private set; } = new();
    
    // 半导体专属业务方法
    public void RecordDieTest(int dieX, int dieY, bool pass, double current)
    {
        if (Status == TestJobStatus.Completed) 
            throw new BusinessException("已完成作业禁止记录");
        
        DieResults.Add(new DieTestResult(dieX, dieY, pass, current));
        RecalculateYield(); // 内部自动更新良率
    }
}
2. 值对象 (Value Object) —— 无 ID + 不可变(半导体最常用!)

例子TestRecipe(测试配方)、ProbeCardPressure(探针压力)、DieCoordinate(Die 坐标)

public record TestRecipe // C# 9+ record 天生不可变
{
    public string RecipeName { get; init; }
    public double Temperature { get; init; }   // ℃
    public double Voltage { get; init; }       // V
    public int MaxOverdrive { get; init; }     // μm

    // 业务规则:创建时校验
    public TestRecipe(string name, double temp, double volt, int od)
    {
        if (temp < -40 || temp > 150) throw new ArgumentException("温度超范围");
        RecipeName = name;
        Temperature = temp;
        // ...
    }
}

不可变性实战(改配方不是 set,而是整体替换):

public void ChangeRecipe(TestRecipe newRecipe)
{
    if (Status != TestJobStatus.Created)
        throw new BusinessException("仅创建状态可换配方");
    _recipe = newRecipe; // 整体替换!
}
3. 聚合根 (Aggregate Root) —— 组织管理者(测试机最重要!)

例子TestJob 是聚合根,DieTestResultProbeCard 是内部成员。

禁令:任何地方都不允许直接 dieRepository.Update(die),必须走 testJob.RecordDieTest(...)

更多半导体不变量示例

  • 晶圆批次(Lot)总 Die 数必须与实际记录一致(一致性校验)
  • 探针卡针压超过 5g 时自动报警并拒绝继续测试
  • 已完成作业不允许再添加任何 Die 测试记录

五、模型转换:每一层都有自己的“脸”(半导体版)

  • DTO(接口层):TestJobCreateDto 只含前端需要的字段
  • Domain Entity(领域层):TestJob(带所有业务方法)
  • PO / EF Entity(基础设施层):TestJobPO 与数据库表 1:1(包含 CreatedAt、UpdatedAt、MachineIp 等)

六、🚀 DDD 业务落地实战(半导体测试机 4 个完整案例)

案例 1:创建测试作业(最常用)

领域层(TestJob.cs)

public class TestJob : AggregateRoot<long>
{
    private readonly List<DieTestResult> _dieResults = new();
    private TestRecipe _recipe;

    public TestJob(string jobNo, TestRecipe recipe, int totalDieCount)
    {
        if (string.IsNullOrEmpty(jobNo)) throw new BusinessException("JobNo 不能为空");
        if (totalDieCount <= 0) throw new BusinessException("至少需要 1 个 Die");
        
        JobNo = jobNo;
        _recipe = recipe;
        TotalDieCount = totalDieCount;
        Status = TestJobStatus.Created;
    }

    public void StartTesting()
    {
        if (Status != TestJobStatus.Created)
            throw new BusinessException("仅创建状态可启动");
        Status = TestJobStatus.Running;
        // 可触发领域事件 TestJobStartedDomainEvent
    }

    public void Abort()
    {
        if (Status == TestJobStatus.Completed)
            throw new BusinessException("已完成作业禁止中止");
        if (Status == TestJobStatus.Running && CurrentProbeStage == ProbeStage.Contacting)
            throw new BusinessException("探针接触中禁止中止");
        
        Status = TestJobStatus.Aborted;
        // 记录中止原因等
    }
}

应用层(TestJobApplicationService.cs)

public class TestJobApplicationService
{
    private readonly ITestJobRepository _repo;
    private readonly ITestMachineClient _machineClient; // OPC-UA 或 SECS/GEM

    [Transactional]
    public async Task CreateAndStartAsync(TestJobCreateDto dto)
    {
        var recipe = new TestRecipe(dto.RecipeName, dto.Temp, dto.Volt, dto.OD);
        var job = new TestJob(dto.JobNo, recipe, dto.TotalDieCount);
        
        job.StartTesting();                    // 业务规则在这里执行
        await _repo.SaveAsync(job);
        
        await _machineClient.SendStartCommand(job); // 通知测试机硬件
    }

    public async Task AbortAsync(long jobId)
    {
        var job = await _repo.GetByIdAsync(jobId);
        job.Abort();                           // 所有判断都在领域层
        await _repo.SaveAsync(job);
    }
}

基础设施层(TestJobRepository.cs)

public class TestJobRepository : ITestJobRepository
{
    private readonly TestDbContext _db;

    public async Task SaveAsync(TestJob job)
    {
        var po = _mapper.Map<TestJobPO>(job); // AutoMapper 或手动转换
        if (po.Id == 0)
            _db.TestJobs.Add(po);
        else
            _db.TestJobs.Update(po);
        await _db.SaveChangesAsync();
    }
}
案例 2:实时记录 Die 测试结果(高频场景)
// 在领域层
public void RecordMultiDieTest(List<DieCoordinate> coords, TestResult result)
{
    if (Status != TestJobStatus.Running) throw ...;
    foreach (var c in coords)
    {
        _dieResults.Add(new DieTestResult(c.X, c.Y, result.Pass, result.Current));
    }
    RecalculateYield(); // 实时更新良率
}
案例 3:探针卡压力保护(硬件安全规则)
public record ProbeCardPressure(double GramForce)
{
    public ProbeCardPressure(double gf)
    {
        if (gf > 5.0) throw new BusinessException("探针压力超标!最大 5g");
        GramForce = gf;
    }
}

聚合根中使用

public void SetProbePressure(ProbeCardPressure pressure)
{
    if (Status == TestJobStatus.Running)
        throw new BusinessException("运行中禁止修改探针压力");
    _currentPressure = pressure;
}
案例 4:良率计算与报警(复杂算法封装)
private void RecalculateYield()
{
    var passCount = _dieResults.Count(r => r.Pass);
    Yield = (double)passCount / TotalDieCount * 100;
    
    if (Yield < 85.0)
        AddDomainEvent(new LowYieldAlertEvent(this)); // 领域事件
}

七、总结:DDD 在半导体测试机上真正增强了什么?

  • 高内聚:所有测试规则(温度范围、压力限制、状态机)都在领域对象里,一眼就能看懂业务。
  • 易维护:改一个“探针卡 6g 报警”规则,只改 ProbeCardPressure 构造器,其他 4 层不动。
  • 硬件插件化:换测试机品牌(KLA → Teradyne)、换数据库(SQL Server → TimescaleDB),Domain 层零修改。
  • 单元测试极快TestJob.Abort() 测试不需要连数据库、也不需要真实测试机。

八、结语

DDD 不是为了“炫技”,而是在半导体测试机这种高可靠、强规则、硬件耦合的领域里,让你的业务对象真正成为“测试专家”——它们自己知道什么时候能启动、什么时候必须报警、什么时候坚决拒绝操作。

一句话总结
MVC 让我们把代码放对位置,而 DDD 让 TestJob、Recipe、Wafer 这些半导体测试对象“活”了过来,自己守护业务完整性。

需要我继续给出 完整项目分层目录结构 + EF Core 配置 + 领域事件 + MediatR 实现,或者再补充 “动态调整测试配方”“多机台并行测试” 等更复杂案例,随时告诉我!

DDD 在半导体测试中的高级应用
(以 ATE 上位机 / 测试执行系统 / 可靠性老化测试系统为例)

在半导体测试领域,DDD 的真正价值并不是“分层清晰”或“代码好看”,而是在极高确定性、强一致性、长生命周期、硬件深度耦合、频繁变更的测试规则下,让领域模型成为可信的业务权威,而不是文档或口头约定。

下面列出在真实半导体测试场景中,DDD 能发挥高级威力的几个典型方向(从战术到战略层面逐步展开)。

1. 复杂状态机 + 不变量守护(最核心的高级用法)

半导体测试中最复杂的往往不是计算,而是多维度状态流转 + 强一致性约束

典型例子:

  • 测试作业(TestLot / TestJob / TestFlow)状态机
  • 不同温度段(-55°C → 25°C → 150°C → Bake)
  • 不同偏置模式(HTRB / HTGB / HAST)
  • 不同失效判定规则(漏电流突变、阈值漂移、Bin 分选逻辑)
  • 安全联锁(Over Temperature Protect、Over Current Protect、Chamber Door Open)

DDD 高级做法

把状态机封装在聚合根内部,并用领域事件 + 策略模式 / 规格模式实现可组合的守卫规则。

// 聚合根
public class ReliabilityTestRun : AggregateRoot<Guid>
{
    public TestRunId RunId { get; private set; }
    public TestPhase CurrentPhase { get; private set; }  // PreCondition → Stress → Readout → Judgment
    public TemperatureZone Zone { get; private set; }
    private readonly List<IRuleGuard> _phaseTransitionGuards = new();

    public void TransitionTo(TestPhase nextPhase)
    {
        foreach (var guard in _phaseTransitionGuards)
        {
            guard.CheckTransitionAllowed(this, nextPhase);
        }

        var previous = CurrentPhase;
        CurrentPhase = nextPhase;

        AddDomainEvent(new TestPhaseTransitioned(RunId, previous, nextPhase));
    }

    // 示例:注入不同的失效判定策略
    public void ApplyJudgmentStrategy(IJudgmentStrategy strategy)
    {
        var result = strategy.Judge(this.LatestMeasurements);
        if (result == JudgmentResult.Fail)
        {
            FailWithReason(result.Reason);
        }
    }
}

更高级玩法:把规则本身变成可配置的领域对象(Rule Specification Aggregate),支持运行时热加载 / A/B 测试不同判定逻辑,而不改核心代码。

2. 多设备协同 + 分布式一致性(Saga / Process Manager)

现代 ATE 系统往往不是单机:

  • 多台 Chamber 并行跑不同 stress
  • 一台 Chamber 内多工位(multi-site)
  • 上位机 + PLC + 测试仪 + 温箱 + 电源 + 切换矩阵
  • 数据同步到 MES / SPC / Yield 系统

DDD 高级做法

使用 Process Manager(长流程管理器)或 Choreography + Saga 模式来协调跨聚合的分布式事务。

示例:HTRB 测试超时自动中止 + 记录 + 通知 + 数据归档

public class HtrbStressProcessManager : IProcessManager
{
    public void Handle(TestRunStarted @event)
    {
        // 发起定时器 Saga
        ScheduleTimeout(@event.RunId, TimeSpan.FromHours(1000));
    }

    public void Handle(StressTimeoutReached @event)
    {
        var run = _repository.GetById(@event.RunId);
        run.ForceAbort(AbortReason.Timeout);
        _repository.Save(run);

        Publish(new TestRunAborted(@event.RunId, AbortReason.Timeout));
        Publish(new ArchiveRawDataCommand(@event.RunId));
        Publish(new NotifyEngineerLowPriority(@event.RunId, "HTRB 超时中止"));
    }
}

3. 测试配方(Recipe)作为值对象家族 + 版本化

半导体测试配方极度复杂:

  • 层级嵌套(Lot Recipe → Wafer Recipe → Die Recipe → Site Recipe)
  • 参数继承 & 覆盖
  • 不同机台适配(同一份 recipe 在 Advantest / Teradyne / Cohu 上跑可能需要微调)

DDD 高级做法

把 Recipe 做成不可变值对象树 + 版本化 + 差量表达(只记录与父 Recipe 的差异)。

public record TestRecipeVersion(
    RecipeId Id,
    RecipeId? ParentId,
    ImmutableDictionary<string, ParameterValue> Overrides,
    ImmutableList<StressStep> Steps,
    DateTime EffectiveFrom,
    string Author,
    string ChangeDescription)
{
    public TestRecipeVersion MergeWithParent(TestRecipeVersion parent)
    {
        // 实现参数合并逻辑(覆盖优先)
    }
}

好处

  • 配方变更可审计、可回溯
  • 支持“黄金配方”模板 + 站点微调
  • 配方校验逻辑全部封装在值对象内部

4. 领域事件驱动的实时 SPC / Yield 分析

现代测试系统必须实时做统计过程控制(SPC)和良率异常检测。

DDD 高级做法

把每一次关键测量结果都作为领域事件发布,SPC 引擎订阅这些事件,实时更新控制图、触发异常报警。

public class LeakageCurrentMeasured : IDomainEvent
{
    public TestRunId RunId { get; }
    public SiteCoordinate Site { get; }
    public double Value_uA { get; }
    public DateTime Timestamp { get; }
}

public class SpcAnomalyDetector : IEventHandler<LeakageCurrentMeasured>
{
    private readonly ControlChart _chart;

    public void Handle(LeakageCurrentMeasured evt)
    {
        _chart.AddPoint(evt.Value_uA);
        if (_chart.IsOutOfControl())
        {
            Publish(new SpcViolationDetected(evt.RunId, "Leakage current OOC", _chart.CurrentRuleViolated));
        }
    }
}

5. 反腐化层(ACL)+ 多种测试机协议适配

真实项目中最痛苦的是:同一套业务逻辑要适配 Advantest V93000、Teradyne UltraFLEX、Cohu、NI PXI、Chroma、自研测试板……

DDD 高级做法

  • 领域层只认识统一抽象的 TestInstrument 接口
  • 在 Anti-Corruption Layer 中实现不同厂商协议的适配器(Adapter)
  • 使用领域事件解耦上层业务与底层硬件通讯
领域层(TestRun、Recipe、Measurement)
          ↓ (领域事件)
反腐化层(ATE Driver ACL)
   ↙       ↘       ↙
Advantest  Teradyne  自研板
   Driver     Driver    Driver

6. 战略设计层面:Bounded Context 划分(真实项目常见拆分)

在大型半导体测试 MES / 执行系统里,常见的 Bounded Context 划分:

Bounded Context 核心聚合根 主要语言 / 事件流 与其他 Context 关系
测试执行引擎 TestRun, TestFlow StartRun, PhaseTransitioned, MeasurementTaken Upstream → Yield/SPC
配方管理 Recipe, RecipeVersion RecipeReleased, ParameterOverridden Upstream → 执行引擎
设备资产 & 日志 Equipment, MaintenanceRecord EquipmentDown, CalibrationDue Downstream ← 执行引擎
可靠性老化测试 ReliabilityStressRun, Chamber StressStarted, AbortRequested 与执行引擎部分重叠
良率 & SPC 分析 ControlChart, YieldSnapshot SpcViolation, LowYieldAlert Downstream ← 所有测量事件
数据归档 & 追溯 RawDataArchive, Traceability ArchiveCompleted Downstream

总结一句话(高级应用的核心心法)

在半导体测试这种规则极度复杂、变更频繁、可靠性要求近乎苛刻的场景下,DDD 的最大价值在于:

业务规则的权威性从人、从文档、从 Excel、从口头约定,真正转移到可编译、可测试、可版本控制的领域模型里。

当一个老工程师离职、一个新测试规范发布、一个机台协议变更时,系统能靠模型本身而不是靠“谁记得最清楚”来保证行为正确,这才是 DDD 在半导体测试中最硬核的回报。

需要更深入的某个方向(例如完整的老化测试聚合设计、SPC 实时异常检测实现、Recipe 差量版本化算法等),可以直接告诉我。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐