如何在领域实体中应用Feature Flag且不引入额外依赖?
我们用领域对象封装业务逻辑,有时为了适配新系统需求,需要变更实体行为。常见的做法是在方法或属性里加判断Feature Flag的if语句,示例C#代码如下:
public class Entity : IEntity { public void SomeMethod() { if (Feature.NewRequirement()) { DoTheNewStuff(); } else { DoTheOldStuff(); } } }
Feature Flag用来决定新行为的启用时机,必要时可以随时关闭。通常在生产环境稳定运行数周或数月后,会移除这个开关,只保留新实现。
但这种做法存在明显问题:
- Feature Flag作为静态依赖:Feature类是静态类,依赖存储开关配置的缓存,导致单元测试难度大,共享状态容易引发测试不稳定,甚至无法并行执行测试。
- 注入Feature Flag的矛盾:虽然可以把Feature Flag作为构造函数参数或方法参数传入实体,但我们希望领域对象尽可能纯净,不依赖基础设施层的东西。而且多数情况下,实体要么由聚合根创建,要么通过ORM从数据库加载,注入参数的操作并不方便。
以下是几种能最小化依赖、避免实体内部静态调用的解决方案:
方案1:基于策略模式分离行为
把不同版本的业务逻辑抽成独立的策略类,实体本身只负责调用策略,策略的选择由上层服务根据Feature Flag决定,完全隔离实体与Feature Flag的依赖。
首先定义策略接口及实现:
public interface ISomeMethodStrategy { void Execute(Entity entity); } public class OldSomeMethodStrategy : ISomeMethodStrategy { public void Execute(Entity entity) { entity.DoTheOldStuff(); } } public class NewSomeMethodStrategy : ISomeMethodStrategy { public void Execute(Entity entity) { entity.DoTheNewStuff(); } }
调整实体,将具体行为设为内部方法,对外提供接受策略的入口:
public class Entity : IEntity { public void SomeMethod(ISomeMethodStrategy strategy) { strategy.Execute(this); } internal void DoTheOldStuff() { // 旧业务逻辑实现 } internal void DoTheNewStuff() { // 新业务逻辑实现 } }
上层应用层服务根据Feature Flag选择策略并传入实体:
public class EntityService { private readonly IFeatureFlagService _featureFlagService; public EntityService(IFeatureFlagService featureFlagService) { _featureFlagService = featureFlagService; } public void ProcessEntity(Entity entity) { ISomeMethodStrategy strategy = _featureFlagService.IsEnabled("NewRequirement") ? new NewSomeMethodStrategy() : new OldSomeMethodStrategy(); entity.SomeMethod(strategy); } }
测试时只需直接传入对应策略即可,完全不需要处理静态依赖问题。
方案2:利用聚合根/工厂控制实体行为
如果实体由聚合根创建,可以把Feature Flag的判断逻辑放在聚合根的创建方法中,根据开关结果给实体注入不同的行为实现,实体仅负责持有并执行行为。
示例代码:
public class EntityAggregateRoot { private readonly IFeatureFlagService _featureFlagService; public EntityAggregateRoot(IFeatureFlagService featureFlagService) { _featureFlagService = featureFlagService; } public Entity CreateEntity() { Action behavior = _featureFlagService.IsEnabled("NewRequirement") ? (Action)DoTheNewStuff : DoTheOldStuff; return new Entity(behavior); } private void DoTheOldStuff() { // 旧逻辑 } private void DoTheNewStuff() { // 新逻辑 } } public class Entity : IEntity { private readonly Action _someMethodBehavior; public Entity(Action someMethodBehavior) { _someMethodBehavior = someMethodBehavior; } public void SomeMethod() { _someMethodBehavior(); } }
这种方式把Feature Flag的处理放在基础设施/应用层的工厂逻辑里,实体保持纯净,不依赖任何外部开关。
方案3:通过领域事件间接切换行为
把需要切换的逻辑转化为领域事件,实体只负责发布事件,根据Feature Flag选择不同的事件处理器执行对应逻辑,完全解耦实体与开关逻辑。
实体定义及事件发布:
public class Entity : IEntity { public event EventHandler<SomeMethodExecutedEventArgs> SomeMethodExecuted; public void SomeMethod() { OnSomeMethodExecuted(new SomeMethodExecutedEventArgs(this)); } protected virtual void OnSomeMethodExecuted(SomeMethodExecutedEventArgs e) { SomeMethodExecuted?.Invoke(this, e); } } public class SomeMethodExecutedEventArgs : EventArgs { public Entity Entity { get; } public SomeMethodExecutedEventArgs(Entity entity) { Entity = entity; } }
上层服务根据Feature Flag订阅对应处理器:
public class EntityEventHandler { private readonly IFeatureFlagService _featureFlagService; public EntityEventHandler(IFeatureFlagService featureFlagService) { _featureFlagService = featureFlagService; } public void Subscribe(Entity entity) { if (_featureFlagService.IsEnabled("NewRequirement")) { entity.SomeMethodExecuted += HandleNewRequirement; } else { entity.SomeMethodExecuted += HandleOldRequirement; } } private void HandleOldRequirement(object sender, SomeMethodExecutedEventArgs e) { // 旧逻辑处理 } private void HandleNewRequirement(object sender, SomeMethodExecutedEventArgs e) { // 新逻辑处理 } }
总结
以上三种方案都能让领域实体保持纯净,不依赖Feature Flag的静态类或基础设施,同时实现行为切换。等Feature Flag不再需要时,只需移除对应的策略、委托或事件处理器,保留最终版本的逻辑即可,对实体的改动极小。
内容的提问来源于stack exchange,提问作者Daggen

