【问题标题】:implement DDD in MVC Frameworks - PHP在 MVC 框架中实现 DDD - PHP
【发布时间】:2017-03-15 07:49:18
【问题描述】:

在 mvc 中,模型是一个层,它包含所有领域的业务逻辑。
在领域驱动设计中,业务逻辑可以分为各种构建块。

在领域驱动设计领域模型是。

领域模型是描述选定对象的抽象系统 知识、影响或活动(领域)领域的各个方面。 然后该模型可用于解决与该领域相关的问题

开发者已阅读领域驱动设计,或正在使用 Doctrine2 或 Hibernate, 通常更关注 DDD 中的域模型。在 mvc 框架中,模型层与 DDD 中的域模型重叠。这意味着我们可以在 mvc 框架中的模型文件夹中实现域模型

这样的实现如下所示。显示模型文件夹的结构

   Model(this can model or domain)
   | 
   |----Entities
   |    |---BlogPost.php
   |    |---Comment.php
   |    |---User.php
   | 
   |----Repositories
   |    |---BlogPostRepository.php
   |    |---CommentRepository.php
   |    |---UserRepository.php
   | 
   |----Services
   |    |---UserService.php
   | 
   |----factories
   |    |---userfactory.php
   | 
   |----dataMappers
   |    |---userDataMapper.php // this inherit from Eloquent model
   | 
   |----ValueObject


  • 我想知道我的第一个假设(可以在 mvc 框架的模型文件夹中实现域模型)是否正确?
  • entities,services,repositories等模型文件夹(如上图)中实现DDD中的所有构建块是否正确设计
  • 或您对此实施的任何其他建议。
  • 如果这是错误的,那么在 mvc 框架中实现 DDD 构建块(例如实体、服务、存储库)的正确方法是什么

【问题讨论】:

  • “DDD 中的所有构建块在模型文件夹(如上所示)中实现的设计是否正确,例如实体、服务、存储库” - 如何构建代码库取决于您.对我来说,每个有界上下文(例如博客、用户)都有一个目录更有意义,并且在这个目录中存在具体的类(用户、用户存储库,...)。这是一个口味问题,但我更喜欢按功能而不是按层进行包装..
  • MVC 更像是一种 UI 架构。它不太适合 DDD。
  • 不要比较 DDD 和 MVC,因为这与如何组织文件夹结构无关。 DDD 几乎是您设计应用程序的方式。 DDD 包含各种战略和战术模式。 MVC 只是一种战术模式

标签: php laravel design-patterns model-view-controller domain-driven-design


【解决方案1】:

在 mvc 中,模型是一个层,它包含所有领域业务 逻辑。

我怀疑 MVC 模式本身声明了域的一些特殊之处。它将模型作为一个属性包来操作,而不关心它是如何创建的以及它如何保护其不变量。

同时Onion architecture 指出,将域与应用程序服务(MVC 框架就是)隔离出来很重要。所以我喜欢将包含实体值对象域事件聚合的域层放到一个单独的模块中或顶级文件夹。

将域与 MVC 内容分开放置的另一个原因是,它可以让您更轻松地管理多个 bounded contexts,因为每个上下文都需要自己的模块/文件夹。

我建议您查看this ASP MVC project 结构。它是由著名的 DDD 专家设计的。除了域,请看一下 MVC 部分是如何组织的。它利用了近来越来越流行的feature slice 方法,我发现它非常有用。

【讨论】:

    【解决方案2】:

    虽然我对 DDD 的世界很陌生,但在逐渐将我正在处理的应用程序迁移到更面向 DDD 的结构的过程中,我也遇到了目录结构的问题。将我能够找到的并非完全概念化的信息拼凑在一起,我想出了以下简化的目录结构(存在于面向 CRUD 的 Laravel 应用程序中),这对我很有帮助:

    app/
        ProjectName/
            Core/
                Application/
                Domain/
                Infrastructure/
            User/
                Application/
                    Services/
                        CreateUserService.php
                        FindUserService.php
                        UpdateUserService.php
                Domain/
                    Role.php
                    RoleDAO.php
                    User.php
                    UserDAO.php
                    UserNotCreated.php
                    UserNotFound.php
                    UserNotUpdated.php
                    UserWasCreated.php
                    UserWasUpdated.php
                Infrastructure/
                    EloquentRoleDAO.php
                    EloquentUserDAO.php
    

    为了解决您的特定问题,存储库接口和实体被放置在应用程序每个可分离组件(例如 - 用户)下方的域文件夹中。此外,这是我放置任何域事件和异常的地方。存储库实现被放置在每个基础设施文件夹下。应用程序服务放置在应用程序目录下的服务目录中。

    撇开我自己的应用程序的混合性质(我使用依赖 ORM 的 DAO/实体、事务脚本,并避免使用值对象,仅举几例),这可能仍然有助于作为一个粗略的概念MVC 应用程序中潜在的 DDD 目录结构。

    【讨论】:

      【解决方案3】:

      我同意@Zharro 的建议。

      良好的结构如下:

      • 视图(仅包含视图部分,如树枝、html 和资产)
      • 核心(控制器、表单、监听器、帮助器)
      • BusinessLogic(包括服务)
      • 实体(实体、命令、验证器)

      1) 查看部分仅访问CoreBundle,不会改变数据库的行为。

      2) 核心部分访问BuisnessLogic和Entity

      3) BuisnessLogic 部分仅访问实体

      4) 实体部分仅访问数据库。

      【讨论】:

        【解决方案4】:

        您的第一个假设不正确,MVC 并不真正适合 DDD,更好的方法是使用 CQRS 模式。

        您的第二个假设也不正确,积木并不全在域模型文件夹中,实际上,这是您项目的一个很好的结构:

        ProjectName/
            Application/
                Blog/
                    Command/
                        CommentToBlogPostCommand.php
                        ChangeCommentContent.php
                        DescribeBlogPostCommand.php
                        NewBlogPostCommand.php
                        ...
                    Data/
                        BlogPostData.php
                        BlogPostCommentsData.php (POPO containing BlogPost infos and the comments array)
                        CommentData.php (Single comment infos)
                    BlogPostApplicationService.php
                    BlogPostQueryService.php
                    CommentApplicationService.php
                    CommentQueryService.php
                Identity/
                    Command/
                        AuthenticateUserCommand.php
                        ChangeEmailAddressCommand.php
                        ChangeUserPasswordCommand.php
                        ChangeUserPersonalNameCommand.php
                        DefineUserEnablementCommand.php
                        RegisterUserCommand.php
        
                    UserApplicationService.php (this defines the actions that can be done by your application related to user domain, injected in presentation layer responding to user events)
                    UserQueryService.php (this will usually be injected in your presentation layer)
            Domain/
                Model/
                    Blog/
                        BlogPost.php
                        BlogPostClosed.php (this could be a list of possible events)
                        BlogPostDescriptionChanged.php
                        BlogPostModeratorChanged.php
                        BlogPostReopened.php
                        BlogPostStarted.php
                        BlogPostRepository.php (interface)
                        Comment.php (this is an Entity, or Aggregate Root)
                        CommentContentAltered.php (this could be an event)
                        CommentAuthor.php (this could be a ValueObject, containing the username)
                        CommentRepository.php (interface)
                        CommentedToBlogPost.php (this could be another event when adding a comment to a blogpost)
                        ...
                    Identity/
                        ContactInformation.php (VO or Person)
                        Enablement.php (VO of User)
                        EmailAddress.php (VO of ContactInformation)
                        FullName.php (VO or Person)
                        Person.php (ValueObject of User)
                        User.php (constructor visibility might be package-protected)
                        UserFactory.php
                        UserRepository.php (this is an interface)
                        UserService.php (this is a domain service)
            Infrastructure/
                Persistence/
                    LavarelBlogPostRepository.php (this implements BlogPostRepository)
                    LavarelCommentRepository.php (this implements CommentRepository)
                    LavarelUserRepository.php (this implements UserRepository)
            Interfaces/
                ...
        

        这样你可以保留一个伪 MVC,但区别在于 View 和 Controller 在 Interfaces 包中,而 Rich Model 在 domain/model 包中。您只能通过应用程序服务来操作模型,并通过查询服务来查询模型。查询服务让您可以快速访问模型表示,并将命令发送到应用程序服务以充当控制器。

        注意,CommentAuthor 类可以是一个值对象,不包含数据库的用户 ID,而是一个唯一的用户名。由于用户聚合根来自另一个包:这对于域 PointOfView 是有意义的。我们可以称它为身份(或用户名)。理想情况下,这将映射到 User 表的唯一列,但将是 Comment 表的索引值。

        另一种选择是使用博客包中的用户作为同一概念的一部分,即博客,但 DDD 不推荐这种方法。实际上,它会建议您将 Identity 和 Access 放在单独的有界上下文中,但我猜在您正在编写的应用程序的上下文中,将用户映射为 Comment 的一部分可能是可以的。

        在基础设施层,你定义你的持久化提供者,这样,你想切换到 Doctrine 的那一天,只有这个包中的实现必须改变。

        应用层负责管理安全性、跨越事务上下文和记录高级事件。

        如果您需要一些说明,我可以为您提供有关类内部的更多见解。此外,这可能需要一些基础设施或支持框架才能使其正常工作,我正在考虑:

        • 依赖注入,
        • 在实体中可用的事件调度程序,
        • 如果您打算使用这些事件,请使用某种事件总线。

        【讨论】:

          【解决方案5】:

          我认为 MVC 中的模型是 ViewModel 和命令的组合。传入的请求被映射到命令,这些命令将被分派到“写入”层,该层检索适当的聚合并在其上调用适当的方法,然后提交事务。

          ViewModel 的存在仅用于为用户界面以可演示的格式携带数据。您可以有一个非常简单的“读取”层来查询数据库视图或表并将结果返回给客户端。这将允许您拥有一个不会通过 getter 和 setter 公开其所有状态的域模型。

          【讨论】:

          • 我同意@Zharro 关于将域模型与应用程序分离的观点。理想情况下,域模型将位于一个单独的包中,以便多个应用程序共享和使用它。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-12-28
          • 2017-05-21
          • 2012-03-13
          • 1970-01-01
          • 1970-01-01
          • 2012-04-25
          • 1970-01-01
          相关资源
          最近更新 更多