对于我想在下一个(Laravel PHP)项目中实现的存储库和服务/用例模式(DDD设计的一部分),我需要一些帮助。
一切似乎都清楚了。DDD 中只有一个令人困惑的部分是来自存储库的数据结构。人们似乎选择了存储库应该返回的数据结构(数组或实体),但这都有缺点。其中之一是回顾我过去经历的表现。一种是您没有简单数据结构(数组或简单对象属性)的接口。
我将首先解释我在以前的项目中的经验。这个项目有缺陷,但我从新项目中学到并希望看到一些好的优势,但解决了一些设计错误。
以往的经验
过去,我使用 Kohana 框架和 Doctrine 2 ORM(数据映射器模式)构建了一个以 API 为中心的网站。流程如下所示:
网站控制器 → 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 接口,由于所有验证等原因,我将不得不复制很多这些控制器。但如果我要创建一个新客户端,则不需要重复。所以至少解决了一个问题:-)
我的管理面板完全耦合到了学说 2 存储库/实体管理器,以加快开发速度(有点)。为什么?因为我的 API 具有仅针对网站具有特殊功能的胖控制器(特殊验证、邮寄注册等)。我将不得不重做工作或重构很多。因此决定直接使用实体以仍然有某种清晰的方式来编写代码,而不是重写我所有的 API 控制器并将它们移动到服务(例如站点和管理员)。时间是修复我的设计错误的一个问题。
对于我的下一个项目,我希望所有代码都通过我自己的自定义存储库和服务。一个流动的好分离。
新项目(使用 DDD 思想)和数据结构的困境
虽然我喜欢以 API 为中心的想法,但我不希望我的下一个项目以 API 为中心,因为我认为相同的功能应该在没有 HTTP 协议的情况下可用。我想使用 DDD 思想设计核心。
但我喜欢这个想法,它使用一个仅作为 API 说话并返回简单数组的层。任何新端口的完美基础,包括我自己的前端。我的想法是将我的服务类视为 API 接口(返回数组数据),进行验证等。我可以拥有专门用于网站的服务(注册)和管理员或后台进程使用的普通服务。在某些管理情况下,简单的 CRUD 编辑无论如何都不需要服务,我可以直接使用存储库。控制器会很薄。有了这个,创建一个真正的 REST API 只需使用与我的前端控制器类相同的服务来创建新控制器。
对于像业务规则这样的内部逻辑,拥有实体(清晰的接口)而不是来自存储库的数组会很有用。通过这种方式,我可以从定义一些基于属性执行一些逻辑的方法中受益。但是如果我使用 Doctrine2 并且我的存储库总是返回实体,我的应用程序将遭受很大的性能打击!
当使用像 Doctrine 2 这样的数据模式模式(现在或将来)时,一种数据结构确保性能但没有清晰的接口,另一种确保清晰的接口但性能不佳。此外,我最终可能会得到两种令人困惑的数据类型。
我在想类似于这个流程的东西:
Controller(瘦)→ UserService(包括验证)→ UserRepository(只是存储)→ Eloquent ORM
为什么用 Eloquent 而不是 Doctrine2?因为我想坚持一下 Laravel 框架和社区中的共同点。所以我可以从第三方模块中受益,例如生成管理界面或基于模型的类似模块(绕过我的 DDD 规则)。除了使用第三方模块之外,我会设计我的核心内容,因此切换应该总是很容易,并且不会影响数据结构的选择或性能。
Eloquent 是一个 activerecord 模式。所以我很想将这些数据转换为像 Doctrine2 实体一样的 POPO。但是不...如上所述,使用doctrine2 真实模型会使系统变得非常胖。所以我再次回到简单的数组。知道这一点将适用于未来的任何其他实现。
但是感觉不好总是依赖数组。尤其是在创建内部业务规则时。开发人员必须猜测数组上的值,在他的 IDE 中没有自动完成功能,不能像实体类那样有特殊方法。但是用两种方式处理数据也让人感觉很糟糕。或者我太完美了;)我想要一个清晰的数据结构!
构建接口和 POPO 将意味着大量重复工作。我需要将 Eloquent 模型(只是一个表映射器,而不是实体)转换为实现此接口的实体对象。都是额外的工作。最终我的最后一层就像一个 API,从而再次将其转换为数组。这也是额外的工作。数组似乎又是一笔交易。
读入 DDD 和 Hexagonal 似乎很容易。好像很合乎逻辑!但实际上,我在试图坚持 OOP 原则的过程中遇到了一个简单的问题。我想使用数组,因为它是 100% 确定我不依赖于任何模型选择和从我的 ORM 中查询关于性能等的选择的唯一方法,并且在转换为视图或 API 的数组时没有重复的工作。但是对于用户数组的外观并没有明确的约定。我想使用这些模式加速我的项目,而不是减慢它们的速度:-) 所以不是有很多转换器的选项。
现在我阅读了很多主题。一个使符合像 Doctrine2 这样的适当实体的 POPO 和接口可以返回,但要为 Eloquent 做所有额外的工作。切换到 Doctrine2 应该相当容易,但会影响性能如此糟糕,或者需要将 Doctrine2 数组数据转换为这些自己的实体接口。其他人选择返回简单的数组。
有人说服人们使用 Doctrine2 而不是 Eloquent,但他们忽略了 Doctrine2 很重并且您确实需要将数组结果用于只读操作的事实。
我们将存储库设计为可更改的,对吗?不是因为它只是设计上的“好”。那么,如果它对性能或重复工作有如此大的影响,我们怎么能依赖完整的实体呢?即使仅使用 Doctrine2(耦合),由于其性能也会出现同样的问题!
所有 ORM 实现都将能够返回数组,因此那里没有重复的工作。很棒的表演。但我们错过了明确的合同。而且我们没有数组或类属性的接口(作为一种解决方法)......呃;)
我只是错过了我们的编程语言中缺少的部分吗?简单数据结构上的接口??
制作所有数组并让高级业务逻辑与这些数组对话是否明智?因此没有具有清晰接口的类。任何预先计算的数据(通常由 Entity 方法返回)都将位于定义 Service 类的数组键中。如果不明智,考虑到上述所有因素,还有什么选择?
如果有人在考虑性能、不同的 ORM 实现等方面在这个“领域”有丰富经验的人能告诉我他/她是如何处理这个问题的,我将不胜感激?
提前致谢!