一、没有DDD前的项目开发遇到的问题
1. 业务代码较分散的问题
比如常见的操作员,无论是code first还是db first,代码中都会有Operator类,并且此类中只有一些get,set之类的方法,用于和数据库表中的字段映射。而相关的操作都会放到OperatorService的BLL层的服务类中。
示例如下:
public class Operator
{
public string Id{get;set;}
public string Code{get;set;}
public string Password{get;set;}
...
}
public class OperatorService
{
private DbContext _context;
...
public void AddOperator(Operator user)
{
if(IsCodeExists(user.Code)){throw new ArgumentException("登录代码不能重复");}
user.password = Encrypt(user.password);//登录密码必须加密存储
_context.Add(user);
_context.Save();
}
public void UpdateOperator(Operator user)
{
if(IsCodeExists(user.Code,user.Id)){throw new ArgumentException("登录代码不能重复");}
user.password = Encrypt(user.password);//登录密码必须加密存储
_context.Entry(user).State=EntryState.Modified;
_context.Save();
}
public void DeleteOperator(Operator user)
{
_context.Operators.Remove(user);
_context.Save();
}
public bool Login(string code,string password)
{
var encryptedPassword = Encrypt(password);
var user = _context.Operators.FirstOrDefault(w=>w.Code == code && w.Password==encryptedPassword);
return user != null;
}
}
从上面可以看出,加密密码在好多方法中都有调用,假如现在除了加密存储外,还要求密码强度检测,则也需要同时修改增加和修改操作员,增加对IsValidPassword方法的调用。
这样随着功能的增多,这个服务层的代码越来越多,并且每一次功能变动的调整都需要修改好多方法,并且可能还不止修改一个Service。
造成这个问题的主要原因就是Operator类是一个贫血模型,本来应该是它自己的一些功能方法泄露到Service中了,导致Service中的代码重复。
2. 微服务拆分只能凭借经验,没有可行性方法
大家都知道,随着项目复杂度的提升,可能没有任何一个人可能完全了解项目的全部功能实现,每次增加修改功能时,都是胆战心惊的,担心修改后可能引发其他问题,每次修改的速度也会受到影响。此时需要对项目微服务改造,进行项目拆分,但微服务并没有提供切实可行的拆分方法,只能是凭经验。
而DDD也是由于微服务的流行而流行开来的,他就是用来拆分项目的一套切实可行的方法论。
3. 需求沟通与开发代码之间存在翻译转换问题
举一个电商的例子,电商中有商品,商品在用户购买时会展示很多属性,比如名称,单价,描述,而用户下单后,进行物流发货时,又需要另外的属性,比如包装尺寸,重量。
根据上述描述,我们可能设计出来的商品是这样的,商品(名称,单价,描述,包装尺寸,重量…)
现在收到一个需求,说商品展示时,需要显示商品的重要给用户,以突出商品的轻便性,但商品中已经有一个重量是用于物流的,并且这两个重量还是不一样的(一个只是商品本身的,一个是包含包装的),所以可能会再增加一个展示重量,如:商品(…,展示重量)
以后收到需求说商品重量时,我们都需要和对方确认是展示重量还是重量,存在一个翻译的过程。
这是由于业务用语和开发代码之间的用语没有统一导致的,当这样的转换太多或者很频繁时,沟通损耗就会很大,导致的结果可能就是互相不理解。
二、DDD如何解决上述问题的?
1. 通过领域模型解决业务代码分散的问题
public class Operator
{
public string Id{get; private set;}
public string Code{get;private set;}
//密码必须加密存储,作为领域模型的方法,不再泄露给服务层
public string Password{get;set{Password = Encrypt(value);};}
...
}
public class OperatorService
{
private DbContext _context;
...
public void AddOperator(Operator user)
{
//由于判断代码重复的,涉及到数据库中的其他操作员的数据查询,所以不能转移到模型中,仍然保留在服务层中
if(IsCodeExists(user.Code)){throw new ArgumentException("登录代码不能重复");}
_context.Add(user);
_context.Save();
}
public void UpdateOperator(Operator user)
{
if(IsCodeExists(user.Code,user.Id)){throw new ArgumentException("登录代码不能重复");}
_context.Entry(user).State=EntryState.Modified;
_context.Save();
}
public void DeleteOperator(Operator user)
{
_context.Operators.Remove(user);
_context.Save();
}
public bool Login(string code,string password)
{
var encryptedPassword = Encrypt(password);
var user = _context.Operators.FirstOrDefault(w=>w.Code == code && w.Password==encryptedPassword);
return user != null;
}
}
2. 通过战略设计,战术设计进行从需求到落地的全过程
DDD提供了战略设计,用来识别业务全景和主线业务,并且和领域专家一起进行子领域拆分,并且评估出核心子域,通用子域和支撑子域,并且进一步细化每一个子域中的业务流程,拆分为限界上下文,并且约定限界上下文之间的关系(可以将限界上下文理解为一个微服务),并且进一步的针对每一个限界上下文细化内部的领域模型,通过实体,聚合根,值对象等来实现业务,从而保证需求和代码实现的一致性
3. 通过与领域专家建立统一语言解决沟通时的翻译转换问题
上述提到了在整个设计过程中,都必须要有领域专家的参与,并且形成统一域名,在实现时,对每一个限界上下文,实体,方法等的命名都必须体现其业务意义,这样就不需要进行翻译,一说就知道是什么意思,沟通将会非常顺畅。
三、如何进行DDD领域建模?
既然DDD能解决这些问题,那么到底什么是DDD,能够在哪些场景中使用DDD,哪些场景不适用呢?可点击传送门进行了解。