我需要帮助我无法解决有关存储库和服务/用例模式( DDD的一部分)的问题 design)我想在我的下一个(Laravel PHP)项目中实现。
一切似乎都清楚了。只有DDD的一部分令人困惑的是来自存储库的数据结构。人们似乎选择了Repository应该返回的数据结构(数组或实体),但它们都有缺点。其中一个是表现我过去的经历。其中一个是你没有简单数据结构(数组或简单对象属性)的接口。
我将首先解释我之前项目的经验。这个项目有缺陷,但是我学到了很多优点,并希望在我的新项目中看到,但解决了一些设计错误。
以前的经验
过去,我使用Kohana框架和Doctrine 2 ORM(数据映射器模式)构建了一个API Centric网站。流程看起来像这样:
网站控制器→API客户端(HMVC调用)→API控制器→自定义存储库→Doctrine 2 ORM本机存储库/实体管理器
我的自定义存储库使用Doctrine2 DQL返回普通数组。 Doctrine2推荐用于只读操作的数组结果数据。是的,这使我的网站变得轻松。 API控制器刚刚将数组数据转换为JSON。就这么简单。
过去,我公司创建的项目完全依赖于加载的Doctrine2实体,这是我们因性能而后悔的事情。
我的REST API支持的查询如
/api/users?include_latest_adverts=2&include_location=true
在用户资源上。 API控制器将include_location
传递给直接包含位置关系的存储库。控制器读取latest_adverts=2
并调用广告存储库以获取每个用户的最新2个广告。返回数组。
例如第一个用户数组:
[
name
avatar
adverts [
advert 1 [
name
price
]
advert 2 [
….
]
]
]
事实证明这非常成功。我的整个网站都在使用API。添加新客户端非常容易,因为API已经完全在生产中使用oauth。整个网站就在它上面运行。
但这种设计也存在缺陷。我的控制器仍然包含很多用于验证,邮件,参数或过滤器的逻辑,例如has_adverts=true
,以便仅为用户提供广告。这意味着如果我创建了一个新的端口,就像一个全新的CLI接口,由于所有的验证等,我将不得不复制很多这些控制器。但如果我要创建一个新的客户端,则不会重复。所以至少解决了一个问题: - )
我的管理面板与doctrine2存储库/实体管理器完全耦合,以加快开发(某种程度)。为什么?因为我的API只有胖网络控制器,只有网站的特殊功能(特殊验证,邮寄注册等)。我不得不重做工作或重构很多。所以决定直接使用实体仍然有一些明确的编写代码的方式,而不是重写我的所有API控制器并将它们移动到服务(例如站点和管理员)。时间是解决我的设计错误的一个问题。
对于我的下一个项目,我希望所有代码都通过我自己的自定义存储库和服务。一个良好分离的流程。
新项目(使用DDD创意)和数据结构的困境
虽然我喜欢以API为中心的想法,但我不希望我的下一个项目在核心中以API为中心,因为我认为在没有HTTP协议的情况下应该可以使用相同的功能。我想用DDD的想法来设计核心。
但是我喜欢这个想法,使用一个刚刚被称为API的图层并返回简单的数组。任何新端口的完美基础,包括我自己的前端。我的想法是将我的服务类视为API接口(返回数组数据),进行验证等。我可以专门为网站(注册)和管理员或后台进程使用的普通服务提供服务。在某些管理案例中,无论如何都不需要服务进行简单的CRUD编辑,我可以直接使用存储库。控制器非常薄。通过这种方式创建一个真正的REST API只需要使用我的前端控制器类所使用的相同服务来创建新的控制器。
对于像业务规则这样的内部逻辑,拥有实体(清晰的接口)而不是来自存储库的数组会很有用。这样我就可以从定义一些基于属性做出某些逻辑的方法中受益。但是,如果我将使用Doctrine2并且我的存储库将始终返回实体,我的应用程序将遭受重大性能损失!
一种数据结构可确保性能,但没有明确的界面,另一种数据结构可确保在使用Doctrine 2(现在或将来)等数据模式模式时,界面清晰但性能不佳。另外,我最终可能会遇到两种令人困惑的数据类型。
我在想与此流程类似的东西:
控制器(瘦)→UserService(包括验证)→UserRepository(只是存储)→Eloquent ORM
为什么雄辩而不是Doctrine2?因为我想要了解Laravel框架和社区中的常见内容。所以我可以从第三方模块中受益,例如基于模型生成管理界面或类似模块(绕过我的DDD规则)。除了使用第三方模块,我会设计我的核心内容,因此切换应该总是很容易,不会影响数据结构选择或性能。
Eloquent是一种活跃的记录模式。所以我很想将这些数据转换为POPO,就像Doctrine2实体一样。但是没有......如上所述,使用doctrine2真实模型会使系统非常胖。所以我再次回到简单的数组。知道这对将来和任何其他实施都有用。
但总是依靠数组感觉很糟糕。特别是在创建内部业务规则时。开发人员必须猜测数组上的值,在他的IDE中没有自动完成,没有像Entity类中那样的特殊方法。但是,制作两种处理数据的方法也很糟糕。或者我太完美了;)我想为所有人提供一个清晰的数据结构!
构建接口和POPO将意味着许多重复工作。我需要将一个Eloquent模型(只是一个表映射器,而不是实体)转换为实现此接口的实体对象。一切都是额外的工作。最后我的最后一层就像一个API,从而再次将它转换为数组。这也是额外的工作。数组似乎再次成为交易。
阅读DDD和Hexagonal似乎很容易。看起来很逻辑!但实际上,我在努力坚持OOP原则的这个简单问题上挣扎。我想使用数组,因为这是100%确定的唯一方法我不依赖于任何模型选择和查询我的ORM关于性能等的选择,并且在转换为视图或API的数组时没有重复的工作。但是对于用户阵列的外观并没有明确的合同。我想使用这些模式来加速我的项目,而不是减慢速度:-)所以不能选择有很多转换器。
现在我读了很多主题。一个人制作POPO&符合Doctrine2等正确实体的接口可以返回,但是Eloquent的所有额外工作都可以返回。切换到Doctrine2应该相当容易,但会影响性能如此糟糕,或者需要将Doctrine2数组数据转换为这些自己的实体接口。其他人选择返回简单数组。
一个人说服人们使用Doctrine2而不是Eloquent,但他们忽略了Doctrine2很重的事实,你真的需要使用数组结果进行只读操作。
我们设计的存储库是可以改变的吗?不是因为它只是设计的“好”。那么,如果它对性能或重复工作产生如此大的影响,我们怎么能依赖完整的实体呢?即使仅使用Doctrine2(耦合),由于其性能,也会出现同样的问题!
所有ORM实现都能够返回数组,因此没有重复的工作。很好的表现。但我们错过了明确的合同。我们没有数组或类属性的接口(作为解决方法)......呃;)
我是否只是错过了编程语言中的缺失块?简单数据结构的接口??
制作所有阵列并使用高级业务逻辑与这些阵列通信是否明智?因此没有具有明确接口的类。任何预先计算的数据(通常由Entity方法返回)都将在Service类中定义的数组键中。如果不是明智的话,考虑到上述所有问题,还有什么选择?
如果考虑到性能,不同ORM实施等在这个“领域”具有丰富经验的人能够告诉我他/她如何处理这个问题,我真的很感激吗?
提前致谢!
答案 0 :(得分:1)
我认为你正在处理的是类似于我正在努力的事情。我认为最好的解决方案是: