Clean Architecture中的Controller

6
我正在尝试在Laravel应用程序中应用uncle Bob的Clean Architecture。我关心的是:正如uncle Bob所描述的那样,控制器应该属于第三个圆:接口适配器(从内向外)。这意味着控制器仅依赖于用例圆(第二个圆),不应了解第四个圆中的任何内容。
但是,在某些框架中,控制器必须扩展基类(例如AbstractController类)。它还需要接收Request对象,并有时返回Response对象,因此这种情况下破坏了Clean Architecture的依赖规则,因为它了解了外部圆中的框架。
我是否理解错误?如果没有,是否有任何解决方案可以不违反依赖规则
我的控制器看起来像这样:
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use User\UseCase\FetchUsers;
use User\UseCase\FetchUsersRequest;

class UserController extends Controller
{
    public function index(Request $request, FetchUsers $fetchUsersUseCase)
    {
        $useCaseRequest = new FetchUsersRequest(
            // extract data from Request
        );

        $useCaseResponse = $fetchUsersUseCase->handle($useCaseRequest);

        return [
            'users' => $useCaseResponse->users,
        ];
    }
}
3个回答

0

AbstractController属于第三个圈子。因此,您不会破坏任何依赖关系。如果在用例圈子中有数据传输对象(DTO)用于将数据传输到第三个圈子,则您不会破坏任何依赖关系。

为了实现这一点,您应该为所有请求和响应创建DTO,将实体映射到DTO,并共享DTO而不是实体。

例如:您有一个名为Name的字符串变量的User实体。您有一个控制器将从用例圈子中获取用户。

解决方案:创建一个名为UserDto的DTO,其中包含一个字符串变量(您可以称之为Name)。控制器知道UserDto但不知道User entity


1
是的,我已经有一个DTO了。但是控制器在第三层中提到了Laravel(通过使用一些Illuminate类),这是否违反了依赖规则? - duy.ly
如果您在第二个圆圈中定义DTO,则不会违反任何依赖规则。 - cokceken
@cokceken 抱歉,我知道已经过去了4年,但是用户实体到用户DTO的映射应该在哪里进行?我认为你应该在用例中注入一个映射器,然后用例将返回DTO,对吗? - kibe
嘿@kibe :) 我认为一个映射器或通用映射器类(例如使用反射匹配名称)也包含一小部分关于您业务的逻辑。因此,它应该在用例圆圈中,并且应该对其进行一些单元测试。如果您决定将其放入用例圆圈中,则与在另一个业务逻辑类内部使用“业务逻辑”类相同,如果考虑单一责任,则应该可以接受。 - cokceken

0
  1. 控制器应该属于第三个圈子:接口适配器。

正确。

  1. 这意味着控制器仅依赖于用例圈子(第二个),不应该了解第四个圈子中的框架的任何信息。

正确。

  1. 但是在某些框架中,控制器必须扩展一个基类(例如,一个AbstractController类)。

我对此没有问题。只需在接口适配器层中使用 "AbstractController类",以及其他控制器。

  1. 控制器还需要接收一个请求对象,并有时返回一个响应对象,所以这有点违反了干净架构的依赖规则,因为它知道外部圈子中的框架。

这是一个有趣的说法。为了澄清事情,请参考主要来源,即书籍《Clean Architecture: A Craftsman's Guide to Software Structure and Design》:

主持人、视图和控制器都属于接口适配器层。 这个圈内的代码不应该知道任何关于数据库的事情。 接口适配器层的软件是一组适配器,将数据从最方便用例和实体的格式转换为最方便某些外部机构(如数据库或网络)的形式。 请注意控制流程:它从控制器开始,通过用例移动,然后在展示器中执行结束。 例如,假设用例需要调用展示器。这个调用不能直接进行,因为那样会违反依赖规则:内圈中的名称不能被内圈提及。所以我们让用例调用内圈的接口,并让外圈的展示器来实现它。 相同的技术用于跨越架构中的所有边界。我们利用动态多态性创建源代码依赖关系,以抵制控制流的方向,以便无论控制流向何处,都能符合依赖规则。 输入发生在控制器中,然后交互器将其处理成结果。然后展示器对结果进行格式化,视图显示这些展示。 框架和驱动程序层是所有细节的地方。网络是一个细节。数据库是一个细节。我们将这些东西放在外面,以免造成太大的伤害。 在用例交互器和数据库之间是数据库网关。这些网关是多态接口,包含应用程序对数据库执行的每个创建、读取、更新或删除操作的方法。 穿越边界的数据通常由简单的数据结构组成。 您可以使用基本的结构体或简单的数据传输对象。或者数据可以只是函数调用中的参数。或者您可以将其打包到哈希映射中,或者构建成对象。重要的是,隔离的简单数据结构被传递过边界。我们不想欺骗并传递实体对象或数据库行。我们不希望数据结构具有违反依赖规则的任何依赖关系。

enter image description here

我认为我从书中收集了与“控制器”主题相关的所有最重要的信息。现在让我们使用这个指导来分析你的问题。
正如你在干净架构中所看到的,控制器不必“接收请求对象”或“返回响应对象”。它可以定义自己的请求模型,以一种简单且与框架无关的方式表示用户输入。然后,控制器可以将框架的请求对象转换为自己的请求模型,并将其传递给属于用例圈的用例交互器。用例交互器可以执行业务逻辑并返回一个响应模型,这是另一个简单且与框架无关的数据结构。然后,控制器可以将响应模型转换为与框架兼容的响应对象,并将其返回给用户。这样,控制器充当了框架和用例交互器之间的适配器,将数据和请求在它们之间进行转换,而不会对框架产生任何依赖。
所以我认为你忽略了的是,在接口适配器层中,有用于与外层通信的“网关”接口,以避免层之间的耦合。
简而言之,通过使用简单的数据对象、抽象接口及其在外层的实现,您可以实现一个干净的架构,而不会违反依赖规则。

0

有点晚了,但是你可以在用例圆圈中创建一个特定控制器的接口(在清洁架构中称之为输入端口)。

然后你可以在第三个圆圈中实现这个接口,例如:

// In the use-cases circle
interface UserControllerInterface(){

    public function index(Request $request, FetchUsers $fetchUsersUseCase);

}

// In the third circle
class UserController extends Controller implements UserControllerInterface{

    public function index(Request $request, FetchUsers $fetchUsersUseCase){
        $useCaseRequest = new FetchUsersRequest(
            // extract data from Request
        );

        $useCaseResponse = $fetchUsersUseCase->handle($useCaseRequest);

        return [
            'users' => $useCaseResponse->users,
        ];
    }
}

这样你就不会违反纪律。


你违反了规定,因为第三层知道框架。框架应该在最外层。 - IamDOM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接