最近一直在学习DDD相关内容,通过近几个月的学习也确实感觉到我们现有项目的一些缺陷。
目前项目项目架构
项目框架:YII2
1.在controller 层得到对应版本号映射对应的service,每个版本对应一个service
public function __construct($id, $module, $config = [])
{
parent::__construct($id, $module, $config);
//获取对应service版本
$servicePath = 'myService';
$retServicePath = $this->getServiceVersion($servicePath);
//我的service
$this->service = new $retServicePath();
}
2.版本间共用方法通过commonService调用
3.版本对应的service承载业务逻辑及数据查询
namespace app\service\v1\v1_0_0\myService
//...
public function someThingCreate($params)
{
if (condition1) {
throw new BusinessException('condition1 不满足');
}
if (condition2) {
throw new BusinessException('condition2 不满足');
}
//...
//数据查询
$repositoryOne = OneRepository::getOne($condition);
if (!$repositoryOne) {
throw new BusinessException('找不到数据');
}
//....
//开启事务
$transaction = \Yii::$app->db->beginTransaction();
try {
//添加two
$two= TwoRepository::create($params);
//添加three
$three= ThreeRepository::create($params);
$transaction->commit();
} catch (\Throwable $e) {
//...
}
}
4.repository承载所有的对mysql 数据库的操作
namespace app\repository\db\myRepository;
public static function oneCreate($attributes)
{
$one= new one();
$one->setAttributes($attributes);
$one->save();
return $one;
}
5.model建立关联及验证相关关系
class One extends \yii\db\ActiveRecord
{
/**
* {@inheritdoc}
*/
public static function tableName()
{
return 'One';
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
//...
];
}
/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
//...
];
}
public function getTwo()
{
return $this->hasOne(Two::class, ['id' => 'one_id']);
}
}
1.版本迭代需要同事修改历史版本的controller与新的controller,从controller层找到服务层只能找对应版本的目录service,不能通过IDE跳转,如下只能跳转到1.0.0的版本
use app\service\v1\v1_0_0\myService
/**
* @var myService
*/
protected $service;
2.业务代码的清晰度不够
3.可测性
从整体架构来说没有太大的变化,主要的变更变更包含
1.controller与service跟随版本走
2.把对应的use case 抽象成一个域对象,域对象包含行为和属性,域对象不直接依赖于数据库,通过interface repositorySpecification的注入查询数据对数据的验证与查询
3.查询类的语句不封装在repository 直接在service层通过Repository调用查询
我们用一个案例来解析说明,因涉及项目安全问题,已隐藏部分逻辑
1.通过YII2 创建对应版本
模块化,
版本控制,
语义版本化
1.valueObject 将隐性的概念显性化,集中验证的重要的对象属性
namespace app\domain\activityCategory\valueObject;
use app\helper\Assert;
final class CategoryName
{
private $name;
public static function fromString(string $param): self
{
Assert::stringNotEmpty($param, '%s 不能为空');
Assert::maxLength($param, 12,'%s 不能超过12个字符');
$CategoryName = new self();
$CategoryName->name = $param;
return $CategoryName;
}
public function toString(): string
{
return $this->name;
}
public function __toString(): string
{
return $this->name;
}
private function __construct()
{
}
}
2.实体
能够创建唯一标签的域对象,这些有标识的概念有长期存在的特性。无论概念中的数据发生多少次变化,它们的标识总是相同。区别于我们的我们表的数据映射,没有必然的直接的联系,比如activity 这个实体我实际可以分为activity activity_content activity_address 等几个表
namespace app\domain\activityCategory;
use app\domain\activityCategory\specification\CategorySpecificationInterface;
use app\domain\activityCategory\valueObject\CategoryDescription;
use app\domain\activityCategory\valueObject\CategoryName;
use app\domain\activityCategory\valueObject\CategorySort;
use app\helper\Assert;
class ActivityCategory
{
private $id;
private $name;
private $description;
private $sort;
private $created_at;
private $updated_at;
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* @return mixed
*/
public function getName()
{
return $this->name;
}
/**
* @param mixed $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* @return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* @param mixed $description
*/
public function setDescription(string $description): void
{
$this->description = $description;
}
/**
* @return mixed
*/
public function getSort()
{
return $this->sort;
}
/**
* @param mixed $sort
*/
public function setSort(string $sort): void
{
$this->sort = $sort;
}
/**
* @return mixed
*/
public function getCreatedAt()
{
if (is_null($this->created_at)) {
$this->setCreatedAt(time());
}
return $this->created_at;
}
/**
* @param mixed $created_at
*/
public function setCreatedAt($created_at): void
{
$this->created_at = $created_at;
}
/**
* @return mixed
*/
public function getUpdatedAt()
{
if (is_null($this->updated_at)) {
$this->setUpdatedAt(time());
}
return $this->updated_at;
}
/**
* @param mixed $updated_at
*/
public function setUpdatedAt($updated_at): void
{
$this->updated_at = $updated_at;
}
public static function create(
CategoryName $name,
CategoryDescription $categoryDescription,
CategorySort $sort,
CategorySpecificationInterface $specification
): self
{
if($specification->isUniqueName($name)){
Assert::reportInvalidArgument('分类名称重复');
}
$category = new self();
$category->setName($name);
$category->setDescription($categoryDescription);
$category->setSort($sort);
return $category;
}
}
3.specification
实体不直接调用数据层,通过specificationInterface 来查询及验证对应的数据
namespace app\domain\activityCategory\specification;
use app\domain\activityCategory\valueObject\CategoryId;
use app\domain\activityCategory\valueObject\CategoryName;
interface CategorySpecificationInterface
{
public function isUniqueName(CategoryName $name): bool;
public function isCategoryExits(CategoryId $id): bool;
}
下面是github 地址,有许多不足的地方,希望大家能指正
github 地址