金融工具估值场景中Domain Service作为数据提供者的DDD合规性探讨
遵循DDD原则的金融工具估值实现方案
核心职责边界分析
首先明确DDD中领域实体的核心定位:Instrument作为领域实体,应聚焦自身固有业务属性与核心行为规则,而非持有价格、汇率这类频繁变化的外部动态数据。因此你提到的第一种方案确实不合理——价格和汇率属于外部依赖数据,不属于Instrument的固有状态,强行绑定会导致实体职责过载,还会引发数据同步问题(比如价格更新时需修改所有关联Instrument实例)。
第二种方案的规范修正与落地
你的第二种思路方向正确,但需要明确领域服务与基础设施服务的边界,解决你担心的状态问题:
1. 区分两类服务的职责
- 无状态领域服务:负责封装跨实体/值对象的业务逻辑,本身不存储任何数据。比如
InstrumentValuationService,仅接收Instrument、价格、汇率等参数,执行估值计算规则。 - 有状态基础设施服务:比如
IPriceProvider、IFxRateProvider,属于基础设施层,负责从数据库、外部接口等数据源获取动态数据。这类服务可以缓存数据(存在状态),但领域层只依赖其抽象接口,不关心具体实现。
2. 两种可行的实现方式
方式一:让Instrument接收外部数据执行估值
将估值逻辑封装在Instrument内部,外部动态数据由应用层传入:
public abstract class Instrument { public Currency BaseCurrency { get; protected set; } // 仅保留工具固有属性,如ID、类型等 public abstract decimal Valuate(decimal currentPrice, decimal fxRate, Currency targetCurrency); } // 股票工具示例实现 public class Stock : Instrument { public override decimal Valuate(decimal currentPrice, decimal fxRate, Currency targetCurrency) { // 股票专属估值逻辑:当前价格 × 汇率 return currentPrice * fxRate; } }
应用层协调数据获取与估值执行:
public class ValuationAppService { private readonly IPriceProvider _priceProvider; private readonly IFxRateProvider _fxRateProvider; public ValuationAppService(IPriceProvider priceProvider, IFxRateProvider fxRateProvider) { _priceProvider = priceProvider; _fxRateProvider = fxRateProvider; } public decimal CalculateInstrumentValue(Instrument instrument, Currency targetCurrency) { var price = _priceProvider.GetPrice(instrument.Id); var fxRate = _fxRateProvider.GetConversionRate(instrument.BaseCurrency, targetCurrency); return instrument.Valuate(price, fxRate, targetCurrency); } }
方式二:用无状态领域服务封装复杂估值逻辑
如果估值逻辑涉及多实体协作,或不属于单个Instrument的职责,可将逻辑抽离到领域服务:
// 领域层定义服务接口 public interface IInstrumentValuationService { decimal Valuate(Instrument instrument, decimal currentPrice, decimal fxRate, Currency targetCurrency); } // 领域层实现服务 public class InstrumentValuationService : IInstrumentValuationService { public decimal Valuate(Instrument instrument, decimal currentPrice, decimal fxRate, Currency targetCurrency) { return instrument switch { Stock => currentPrice * fxRate, Bond bond => (bond.FaceValue * bond.CouponRate) * fxRate, _ => throw new NotSupportedException("不支持的工具类型") }; } }
应用层调用时,同样先通过基础设施服务获取数据,再传入领域服务完成计算。
3. 关键DDD原则落地总结
- 实体职责单一:
Instrument仅维护自身固有属性,不承载外部动态数据; - 依赖倒置:领域层依赖数据服务的抽象接口,具体实现由基础设施层提供,应用层负责注入;
- 领域服务无状态:领域服务仅做逻辑运算,数据获取交给基础设施层处理。
内容的提问来源于stack exchange,提问作者Fede
相关产品推荐
相关产品推荐

