使用以数据为中心的方法在Spring MVC中维护干净的架构

gblwokeq  于 2023-04-30  发布在  Spring
关注(0)|答案(3)|浏览(140)

以数据为中心,在Spring MVC中维护干净的架构

我正在努力绘制一个新的基于Java的Web应用程序(门户类型应用程序)前端的架构图。我想从第一天开始就把它做好,我想在这里开始一个讨论,帮助我在我的架构设计中实现Uncle Bob的Clean Architecture
以下是我们技术堆栈的快速运行,从上到下(技术不重要,结构重要):

  • Oracle数据库
  • Oracle Service Bus使用WSDL公开服务
  • JAX-WS从WSDL生成Java类(我们称之为“生成的服务层”)
  • 由Map到生成的数据对象的POJO组成的Domain模块
  • 消费者模块向前端应用程序公开“生成的服务层”
  • 基于Spring MVC的前端模块,使用FreeMarker渲染视图

一个关键点:
特别是,在外部循环中声明的名称不能被内部循环中的代码提及。包括函数类。变量或任何其他命名的软件实体。
为了遵循Bob的Clean Architecture,我反复思考了一下在何处放置 * 应用程序逻辑 *,即他的架构中的“用例”层。
以下是我提出的方法:

Layer 1 -实体

实体封装企业范围的业务规则。
这就是包含 domain-objects 的Domain模块所在,这些是自包含的对象,彼此之间的依赖性最小。只有属于对象本身的逻辑可以驻留在这些域对象上,而没有用例特定的逻辑。
对数据库的访问是通过WSDL使用服务总线来公开的,而不是像JPA或Hibernate这样的ORM。正因为如此,我们没有传统意义上的“实体”(带有Id),而是采用了一种以数据为中心的方法,使这一层成为数据访问层,由Consumer模块呈现给应用程序的其余部分。

第二层用例

该层中的软件包含应用程序特定的业务规则。
这就是应用程序用例的特定逻辑所在的地方。对该层的更改不应影响数据访问层(第1层)。对GUI或框架实现(SpringMVC)的更改不应影响此层。

**这就是它变得有点棘手的地方:**由于我们的数据访问层(在第1层)必须保持应用程序逻辑的清洁,我们需要一个层,以适合用例的方式促进该层的使用。我发现的一个解决方案是使用“MVVM-pattern”的变体,我选择将其称为 MVC-VM。见下文的解释。“VM”--它的一部分存在于这个用例层中,由*ViewModel表示--封装这个用例特定逻辑的类。

三层接口适配器

该层中的软件是一组适配器,它们将数据从最适合用例和实体的格式转换为最适合外部机构(如数据库或Web)的格式。
这就是我们的GUI的MVC架构所在的地方(我们的“MVC-VM”中的“MVC”)。本质上,这是当Controller-classes从*ViewModel-classes获取数据并将其放入Spring MVC的ModelMap对象中时,这些对象直接由View中的FreeMarker-templates使用。
在我看来,服务总线在我们的例子中也属于这一层。

Layer 4 -框架和驱动

一般来说,除了与下一个圈进行通信的粘合代码外,您不会在这一层中编写太多代码。
这一层实际上只是应用程序中的一个配置层,即Spring配置。例如,这将是我们指定FreeMarker用于渲染视图的地方。

模型视图ViewModel模式

MVVM有助于将图形用户界面的开发(作为标记语言或GUI代码)与称为模型的业务逻辑或后端逻辑(也称为数据模型,以将其与视图模型区分开来)的开发明确分离。MVVM的视图模型是一个值转换器,这意味着视图模型负责从模型中公开数据对象,使得这些对象易于管理和使用。
关于MVVM模式的更多信息,请访问Wikipedia
MVC-VM角色将在我们的应用程序中实现如下:

*Model-由视图模板使用的Spring MVC中的ModelMap数据结构简单表示。
*查看- FreeMarker模板
*ControllerSpring的Controller-类,将 *HTTP URL请求 * 定向到特定的 * handler *(以及FrontController这样的函数)。这些类中的处理程序负责从用例层获取数据,并在显示数据时将其推送到视图模板(HTTP GET),以及发送数据以进行存储(HTTP POST)。通过这种方式,它本质上充当ViewModel和View之间的“绑定器”,使用Model。

*ViewModel-这些类负责1)以视图可用的方式对数据访问层的数据进行结构化,以及2)处理来自视图的数据输入。“处理”意味着验证和分解数据,以便可以将其发送到堆栈中进行存储。这一层将采用<UseCase>VM的形式-在我们的Spring MVC前端模块中的viewmodel包中的类。

这里的一个关键组件是Spring MVC中ModelMap和FreeMarker-templates之间的隐式绑定。模板仅使用模型(ModelMap s),其中控制器已将数据以其可以使用的格式放置。这样我们就可以像这样制作模板:

<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>

我为冗长的解释道歉,但是我不能用更少的话来解释这个(相对简单的)架构。
我将非常感谢对我的方法的一些投入-我在正确的轨道上吗?MVC-VM有意义吗?我是否违反了任何清洁架构原则?
当然,有很多解决方案,但我试图找到一个解决方案,1)不过度工程,2)坚持鲍勃的清洁架构的原则。

更新:

我认为在这里让我感到困惑的关键问题是“用例”层在这个应用程序中采用什么形式。请记住,我们有一个MVC前端,它从数据访问层获取数据。如果MVC部分适合Bob的“接口适配器”,而数据层的域模型适合Bob的“实体”层,那么我怎么称呼实现应用程序逻辑的用例类呢?我很想把它们叫做<UseCase>Model s,并把它们放在MVC项目中,但是根据Bob的说法
模型可能只是从控制器传递到用例,然后从用例返回到表示器和视图的数据结构。
所以这意味着我的模型对象应该是“哑”的(像一个简单的Map。Spring中的ModelMap),然后控制器负责将用例类中的数据放入Map结构中。
那么,我的用例类采用什么形式呢?<UseCase>Interactor怎么样?

但最后我意识到MVC-MV-thing是过度工程化的(或者根本不正确)-正如“mikalai”在下面指出的那样,它目前的形式基本上只是一个两层的应用程序;数据访问层和前端MVC层。就这么简单

e1xvtsh3

e1xvtsh31#

哇,这是很多。我想你已经把Bob叔叔的行话翻译成了Spring Java应用程序。
因为建筑主要是意见,因为你的问题有点像在问一个。..
有许多不同风格的建筑和。大多数都被高估了因为大多数都是一样的:更高的内聚更松的耦合通过间接和抽象。
最重要的是依赖性。制作许多小项目而不是一个巨大的整体项目是获得“干净”架构的最佳方式。
你最重要的清洁架构技术将不是“SpringMVC”技术或“Freemarker”模板语言,或另一个Dr。Dobb的文章,有盒子,六边形和其他各种抽象多边形的图表。

专注于构建和依赖管理技术。这是因为这项技术将强制执行您的体系结构规则。

如果你的代码很难测试。.你可能有糟糕的建筑。

专注于让你的代码易于测试,并编写大量的测试。

如果你这样做,你就可以很容易地修改你的代码而不用担心。..这么多,你甚至可以改变你的架构:)
注意不要过分关注#%$@#架构规则。认真:如果代码易于测试、易于更改、易于理解且性能威尔斯,那么您就拥有了一个好的体系结构。没有6周到6包abs文章这样做(对不起鲍勃叔叔)。这需要经验和时间。...没有什么神奇的子弹计划
这是我自己的“干净”建筑。我是说指导方针

  • 做很多小项目
  • 使用依赖管理(即Maven、Gradle)
  • 不断重构
  • 理解并使用某种依赖注入(Spring)
  • 编写单元测试
  • 理解横切关注点(即当您需要AspectJ、元编程等时).)
7gcisfzg

7gcisfzg2#

我的解决方案

因此,在Java/SpringMVC中实现Bob的“CleanArchitecture”是非常重要的,并且需要比我最初包含的更多的架构方面。
实际上我在网上找不到任何实现的例子。
显然,我的架构缺少了一个单独的“用例”层模块,因为这个逻辑不应该存在于SpringMVC Web模块中(也不应该被称为“*ViewModel”)。Web/MVC模块只是应用程序的一个细节,应用程序逻辑应该与它完全分离,并且可以单独测试。
这个新的“用例”模块现在包含*Interactor类,这些类从域模块(实体)获取数据。此外,需要“* 请求/响应对象 *”来促进MVC/Web模块和用例模块之间的通信。
我的依赖链现在看起来像这样:
Spring MVC模块-〉Use Case模块-〉Domain模块
其中每个箭头(依赖项)都采用 Boundary 的形式,这意味着在箭头右侧的模块中定义了一个接口,该接口在需要的地方实现,并在需要的地方注入(控制反转)。
以下是我最终使用的接口(每个用例):
I<UseCase>Request-在MVC模块中实现,在控制器中示例化I<UseCase>Response-在用例模块中实现,在交互器中示例化I<UseCase>Interactor-在用例模块中实现,注入控制器I<UseCase>Consumer-在域模块中实现,注入交互器

它是如何工作的?

Controller从HTTP请求中获取参数,并将其打包到RequestModel中,然后将其传递给InteractorInteractor从域模块*Consumer中获取所需数据,并将其应用程序特定逻辑强加于其上,然后将其放入ResponseModel中并将其发送回ControllerController最后只是将这个(现在GUI友好)简单数据放入Map对象中,并将其转发给FreeMarker模板,然后FreeMarker模板直接使用此数据并呈现HTML。
Presenter可以参与到最后一部分中,使其成为Model-View-Presenter模式的实现,但我现在不谈这个。

我的结论

严格地说,我最终得到的文件比开发早期所需的文件要多。然而,随着应用程序的复杂性和规模的增长,我相信这种结构可以让我们轻松地保持低耦合和高内聚。另外,Web模块现在很容易替换--它只是向用例模块发送请求并接收响应对象。此外,应用程序的每一层(域逻辑、应用程序逻辑和GUI逻辑)都是可单独测试的,只有View部分需要一个web服务器才能被测试。
感谢所有的建议和指针,我在这里收到.请评论我的解决方案-我并不声称它是完美的。

9lowa7mx

9lowa7mx3#

正因为如此,我们没有传统意义上的“实体”(具有Id),而是以数据为中心的方法,使该层成为数据访问层,由Consumer模块呈现给应用程序的其余部分。
我觉得这部分有点奇怪。为什么你的实体不能有ID,即使你从Web服务得到它们?
在Clean Architecture方法中,实体层确切地说不是数据访问层。数据访问应该是架构中的一个细节,而不是中心问题。正如您自己所说,实体包含特定于域的 * 业务规则 *。业务规则或行为与获取数据的方式有很大不同。

  • 实体是所有域逻辑发生的地方,而不是你从哪里获取数据 *。根据Clean Architecture,您可以从网关获取持久化或外部数据。

我发现这个问题的一个解决方案是使用“MVVM模式”的变体,我选择将其称为MVC-VM。见下文的解释。“VM”部分存在于这个用例层中,由封装这个用例特定逻辑的 * ViewModel类表示。
ViewModel清楚地指的是View,这是一个演示工件-另一个细节。用例/交互者应该没有这样的细节。相反,Interactors应该通过Boundaries发送和接收与交付机制无关的数据结构(RequestModels和ResponseModels)。
我知道这是您的定制模式,不涉及对表示框架的引用,但是“视图”这个词只是误导。

相关问题