这是我决定创建的系列文章的第一篇,旨在解释我如何组织我的 symfony 应用程序以及如何尝试编写尽可能面向领域的代码。
下面,您可以找到我将在所有系列部分中使用的流程图。在每篇文章中,我将重点关注该图的具体部分,并尝试分析所涉及的流程并检测哪些部分属于我们的领域以及如何使用外部层与其他部分解耦。
在第一部分中,我们将重点关注数据提取和验证过程。对于数据提取过程,我们假设请求数据的格式为 JSON。
基于我们知道请求数据位于 JSON 请求负载中的事实,提取请求负载(在本例中,提取意味着从 JSON 负载中获取数组)就像使用 php json_decode 函数一样简单。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
为了验证数据,我们需要三个元素:
对于第一个,我们将创建一个 DTO(数据传输对象)来表示传入的数据,我们将使用 Symfony 验证约束属性来指定如何验证此数据。
readonly class UserInputDTO { public function __construct( #[NotBlank(message: 'Email cannot be empty')] #[Email(message: 'Email must be a valid email')] public string $email, #[NotBlank(message: 'First name cannot be empty')] public string $firstname, #[NotBlank(message: 'Last name cannot be empty')] public string $lastname, #[NotBlank(message: 'Date of birth name cannot be empty')] #[Date(message: 'Date of birth must be a valid date')] public string $dob ){} }
如您所见,我们在最近创建的 DTO 中定义了输入数据验证规则。这些规则如下:
对于第二个,我们将使用 Symfony 标准化器组件,通过它我们能够将请求传入数据映射到我们的 DTO 中。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request, SerializerInterface $serializer): JsonResponse { $requestData = json_decode($request->getContent(), true); $userInputDTO = $serializer->denormalize($requestData, UserInputDTO::class); } }
如上所示,denormalize 方法完成这些工作,并且只需一行代码,我们就可以用传入的数据填充 DTO。
最后,为了验证数据,我们将依赖 Symfony 验证器服务,该服务将接收我们最近非规范化的 DTO 实例(它将携带传入数据),并根据 DTO 规则验证数据。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
到目前为止,我们已经将提取和验证数据的过程分为四个部分:
现在的问题是:这些部分中哪些属于我们的领域?
为了回答这个问题,我们来分析一下涉及的流程:
1.- 提取数据:这部分仅使用“json_decode”函数将传入的数据从 json 转换为数组。它不添加业务逻辑,因此这不属于该域。
2.- DTO:DTO 包含与输入数据关联的属性以及如何验证它们。这意味着 DTO 包含业务规则(验证规则),因此它属于域。
3.- 反规范化数据:这部分只是使用基础设施服务(框架组件)将数据反规范化为对象。这不包含业务规则,因此这不属于我们的域。
4.- 验证数据:与反规范化数据过程相同,验证数据过程也使用基础设施服务(框架组件)来验证传入数据。这不包含业务规则,因为它们是在 DTO 中定义的,因此它也不会是我们域的一部分。
分析完最后一点后,我们可以得出结论,只有 DTO 才是我们域的一部分。那么,我们如何处理其余的代码呢?
就我个人而言,我喜欢将这种过程(提取、反规范化和验证数据)包含到应用程序层或服务层中。为什么?我们来介绍一下应用层。
简而言之,应用层负责编排和协调,将业务逻辑留给领域层。此外,它充当领域层和外部层(例如表示层(UI)和基础设施层)之间的中介。
从上面的定义开始,我们可以将 Extracting、Denormalizing 和 Validating 流程包含到应用程序层的服务中,因为:
完美,我们将创建一个应用程序服务来管理此流程。我们要怎么做呢?我们如何管理责任?
单一职责原则(SRP)规定每个类应该只负责应用程序行为的一部分。如果一个类有多重职责,那么理解、维护和修改就会变得更加困难。
这对我们有何影响?我们来分析一下。
到目前为止,我们知道我们的应用程序服务必须提取、反规范化并验证传入的数据。知道了这一点,就很容易提取出以下职责:
我们应该将这些职责分成 3 个不同的服务吗?我不这么认为。让我解释一下。
正如我们所见,每个职责都由基础设施功能或组件管理:
由于应用程序服务可以将这些职责委托给基础设施服务,因此我们可以创建更抽象的职责(处理数据)并将其分配给应用程序服务。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
如上所示,DataProcessor 应用程序服务使用 json_decode 函数以及 Symfony 规范器和验证器服务来处理输入请求并返回新的且经过验证的 DTO。所以我们可以说 DataProcessor 服务:
您可能已经注意到,当验证过程发现错误时,DataProcessor 服务会抛出 Symfony ValidationException 。在本系列的下一篇文章中,我们将学习如何应用业务规则来构建错误并最终将其呈现给客户。
我知道我们可以删除 DataProcessor 服务并使用 MapRequestPayload 作为服务应用程序层来提取、反规范化和验证数据,但是,考虑到本文的上下文,我认为这样更方便这样写。
在第一篇文章中,我们重点关注从流程图中提取和验证数据过程。我们列出了此过程中涉及的任务,并且了解了如何检测哪些部分属于该域。
知道哪些部分属于域,我们编写了一个应用程序层服务,连接域规则的基础设施服务并协调提取和验证数据过程。
在下一篇文章中,我们将探讨如何定义业务规则来管理异常,我们还将创建一个域服务,负责将输入 DTO 转换为持久实体。
以上是创建专注的领域应用程序。 Symfony 方法(第 1 部分)的详细内容。更多信息请关注PHP中文网其他相关文章!