应用服务&领域服务
领域层(Domain):负责表达业务概念,业务状态信息以及业务规则,是业务软件的核心。
应用服务:表达用例和用户故事的主要手段 eg:购物车结算=》应用行为
领域服务 实现了全部业务逻辑并且通过各种校验手段保证业务正确性
总结:
领域事件
当用户在购物车点击结算时,生成待付款订单,若支付成功,则更新订单状态为已支付,扣减库存,并推送捡货通知信息到捡货中心。 订单支付成功就是一个领域事件
建模领域事件
使用领域事件来解耦方法:当然是封装不变,应对万变。上例 不变的是订单支付成功这个事件,变化的是针对这个事件不同处理手段。
抽象事件源 事件源应该至少包含事件发生的时间和触发事件的对象。 ``` public interface IEventData { ///
///
- 抽象事件处理
针对事件处理 提取IEventHandler接口
///
} 事件处理要与事件源进行绑定,所以我们再来定义一个泛型接口
///
/// <summary>
/// 事件处理器实现该方法来处理事件
/// </summary>
/// <param name="eventData"></param>
void HandleEvent(TEventData eventData);
}
- 领域事件的发布和订阅
- 领域事件的发布可以使用`发布-订阅模式`来实现。比较常见的实现法师就是事件总线
- 事件总线实现的要点
- 事件总线维护一个事件源与事件处理的映射字典;
- 通过单例模式,确保事件总线的唯一入口;
- 利用反射或依赖注入完成事件源与事件处理的初始化绑定
- 提供统一的事件注册、取消注册和触发接口
- 事件总线的接口定义
public interface IEventBus {
void Register < TEventData > (IEventHandler eventHandler);
void UnRegister < TEventData > (Type handlerType) where TEventData: IEventData;
void Trigger < TEventData > (Type eventHandlerType, TEventData eventData) where TEventData: IEventData;
} 在应用服务和领域服务中,我们都可以直接调用Register方法来完成领域事件的注册,调用Trigger方法来完成领域事件的发布。
```
最终一致性
事件存储
聚合
- 在DDD中,聚合也可以用来表示整体与部分的关系。领域对象的显示分组(订单+订单项)
- 封装业务
- 保证聚合内领域对象的数据一致性
聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。
一致性边界
工厂
工厂模式的实现四种方式
封装内部结构
} public class Basket {
public void Add (Product product) {
if (Contains (product))
GetItemFor (product).IncreaseItemQuantitBy (1);
else {
var rate = TaxRateService.ObtainTaxRateFor (product.Id,
country.Id);
var item = new BasketItem (rate, product.Id, product.price);
_items.Add (item);
}
引入一个工厂对象来封装购物车子项的创建,包括获取正确的税率 namespace DomainModel {
public class Basket {
// ......
public void Add (Product product) {
if (Contains (product))
GetItemFor (product).IncreaseItemQuantitBy (1);
else
_items.Add (BasketItemFactory.CreateItemFor (product,
deliveryAddress));
}
}
public class BasketItemFactory {
public static void CreateBasketFrom (Product product, Country country) {
var rate = TaxRateService.ObtainTaxRateFor (product.Id, country.Id);
return new BasketItem (rate, product.Id, product.price);
}
}
} 引入工厂模式后,购物车的职责单一了,且隔离了来自购物车子项的变化,比如当税率变化时,或购物车子项需要其他信息创建时,都不会影响到购物车的相关逻辑。
- 还用工厂的好处
- 工厂将领域对象的使用和创建分离。
- 通过使用工厂类,可以隐藏创建复杂领域对象的业务逻辑。
- 工厂类可以根据调用者的需要,创建相应的领域对象。
- 工厂方法可以封装聚合的内部状态。
> 仓储
- 特性 :仓储用来存储和删除聚合,但同时提供针对聚合的显式查询以及汇总。
- 存储集合
- 删除集合
- 仓储与数据访问层的区别
- 仓储限定了只能通过聚合根来持久化和检索领域对象,以确保所有改动和不变性由聚合处理。
- 仓储通过隐藏聚合持久化和检索的底层技术实现领域层的的持久化无关性(即领域层不需要知道如何持久化领域对象)。
- 仓储在数据模型和领域模型定义了一个边界。
- 仓储举例
简单的仓储定义 public interface ICustomerRepository {
Customer FindBy(Guid id);
void Add(Customer customer);
void Remove(Customer customer);
}
- 仓储的要点
- 是保持你的领域模型和技术持久化框架的独立性,这样你的领域模型可以隔离来自底层持久化技术的影响。如果没有仓储这一层,你的持久化基础设施可能会泄露到领域模型中,并影响领域模型完整性和最终一致性
- 仓储方法需要明确
仓储是原则上是领域模型与持久化存储之间明确的契约,仓储定义的接口方法不仅仅是CURD方法。它是领域模型的扩展,并以领域专家所理解的术语编写。仓储接口的定义应该根据应用程序的用例需求来创建,而不是从类似CURD的数据访问角度来构建。
- 泛型仓储
- 举例
namespace DomainModel {
public interface IRepository<T> where T : EntityBase {
T GetById (int id);
IEnumerable<T> List ();
IEnumerable<T> List (Expression<Func<T, bool>> predicate);
void Add (T entity);
void Delete (T entity);
void Edit (T entity);
}
public abstract class EntityBase {
public int Id { get; protected set; }
}
}
泛型仓储实现 namespace Infrastructure.Persistence {
public class Repository<T> : IRepository<T> where T : EntityBase {
private readonly ApplicationDbContext _dbContext;
public Repository (ApplicationDbContext dbContext) {
_dbContext = dbContext;
}
public virtual T GetById (int id) {
return _dbContext.Set<T> ().Find (id);
}
public virtual IEnumerable<T> List () {
return _dbContext.Set<T> ().AsEnumerable ();
}
public virtual IEnumerable<T> List (Expression<Func<T, bool>> predicate) {
return _dbContext.Set<T> ()
.Where (predicate)
.AsEnumerable ();
}
public void Insert (T entity) {
_dbContext.Set<T> ().Add (entity);
_dbContext.SaveChanges ();
}
public void Update (T entity) {
_dbContext.Entry (entity).State = EntityState.Modified;
_dbContext.SaveChanges ();
}
public void Delete (T entity) {
_dbContext.Set<T> ().Remove (entity);
_dbContext.SaveChanges ();
}
}
} ```
模块