使用以数据为中心的方法在Spring MVC中维护清洁体系结构

Aks*_*vig 14 java architecture model-view-controller spring-mvc mvvm

使用以数据为中心的方法在Spring MVC中维护清洁体系结构

我正在尝试为我们正在制作的新的基于Java的Web应用程序(门户类型应用程序)的前端绘制架构.我希望从第一天起就做到这一点,我想在这里开始讨论,以帮助我在我的建筑设计中实施鲍勃叔叔的清洁建筑.

这是我们的技术堆栈从上到下的快速破坏(技术不重要,结构是):

  • Oracle数据库
  • Oracle Service Bus使用WSDL公开服务
  • JAX-WS从WSDL生成Java类(我们称之为"生成的服务层")
  • 由映射到生成的数据对象的POJO组成的域模块
  • 消费者模块将"生成的服务层"暴露给前端应用程序
  • 一个基于Spring MVC的前端模块,使用FreeMarker渲染视图

关键点:

特别是,外圈中声明的内容的名称不得被内圈中的代码提及.这包括功能,类.变量或任何其他命名的软件实体.

为了坚持Bob的Clean Architecture,我和自己一起来回顾了应用逻辑的位置,即他的架构中的"用例"层.

这是我提出的方法:

第1层 - 实体

实体封装了企业范围的业务规则.

这是包含域对象的 Domain模块所在的位置,这些是自包含的对象,彼此之间的依赖性最小.只有与对象本身有关的逻辑可以存在于这些域对象上,而不是特定于用例的逻辑.

使用转换数据的服务总线通过WSDL公开对数据库的访问,而不是像JPA或Hibernate这样的ORM.因此,我们没有传统意义上的"实体"(使用Ids),而是以数据为中心的方法使该层成为数据访问层,由Consumer模块呈现给应用程序的其余部分.

第2层 - 用例

此层中的软件包含特定于应用程序的业务规则.

这是我们的应用程序的用例特有的逻辑.对此层的更改不应影响数据访问层(第1层).对GUI或框架实现(Spring MVC)的更改不应影响此层.

这是一个有点棘手的地方: 由于我们的数据访问层(在第1层)必须保持清洁应用程序逻辑,我们需要一个便于以适合用例的方式使用该层的层.我发现这个问题的一个解决方案是使用我选择调用MVC-VM的" MVVM模式 " 的变体.请参阅下面的说明."VM"部分位于此Use Case层中,由-classes 表示,这些类封装了此特定于用例的逻辑.*ViewModel

第3层 - 接口适配器

此层中的软件是一组适配器,可将数据从最方便用户和实体的格式转换为某些外部机构(如数据库或Web)最方便的格式.

这就是我们GUI的MVC架构所在的位置(我们的"MVC-VM"中的"MVC").本质上,这是当Controller-classes从-classes中获取数据*ViewModel并将其放入Spring MVC的ModelMap对象中时,这些对象由View中的FreeMarker-templates直接使用.

我看到它的方式,在我们的情况下,servicebus也属于这一层.

第4层 - 框架和驱动程序

通常,除了与下一个圆圈内部通信的胶水代码之外,您不会在此图层中编写太多代码.

这个层实际上只是我们应用程序中的配置层,即Spring配置.例如,这将指定FreeMarker用于渲染视图的位置.


模型视图ViewModel模式

MVVM有助于将图形用户界面的开发(作为标记语言或GUI代码)与业务逻辑或称为模型的后端逻辑(也称为数据模型)区分开来,以区别于视图模型).MVVM的视图模型是一个值转换器,意味着视图模型负责从模型中公开数据对象,以便轻松管理和使用这些对象.

更多关于维基百科的MVVM模式.

MVC-VM角色将在我们的应用程序中实现,如下所示:

  • 模型 - 仅由ModelMapSpring MVC中由视图模板使用的数据结构表示.
  • 查看 - FreeMarker模板
  • Controller Spring的Controller类 - 将HTTP URL请求定向到特定处理程序(以及FrontController等功能).这些类中的处理程序负责从用例层获取数据,并在显示数据(HTTP GET)时将其推送到视图模板,以及向下发送数据以进行存储(HTTP POST).这样它就可以使用Model在ViewModel和View之间充当绑定器.

  • ViewModel - 这些类负责1)以View可用的方式构建来自数据访问层的数据,2)处理来自View的数据输入."处理"意味着验证和分解数据,以便可以将其发送到堆栈中进行存储.这个层将在Spring MVC前端模块<UseCase>VMviewmodel包中形成类.

这里的一个关键组件是Spring MVC ModelMap和FreeMarker-templates 之间发生的隐式绑定.模板仅使用模型ModelMap,控制器将数据放入其可以使用的格式.这样我们可以制作这样的模板:

<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
Run Code Online (Sandbox Code Playgroud)

我为冗长的解释道歉,但我无法用更少的单词来解释这个(相对简单的)架构.

我非常感谢我在这里采取的一些意见 - 我是否走在正确的轨道上?MVC-VM是否有意义?我是否违反任何清洁建筑原则?

当然有很多解决方案,但我试图找到一个解决方案,1)没有过度设计,2)坚持鲍勃的清洁架构的原则.


更新:

我认为让我离开的关键问题是"用例"层在此应用程序中的形式.请记住,我们有一个MVC前端,可以从数据访问层获取数据.如果MVC部分适合Bob的"接口适配器",并且数据层的域模型适合Bob的"实体"层,那么我称之为实现应用程序逻辑的用例类?我很想<UseCase>Model把它们称为s并将它们放入MVC项目中,但Bob表示

这些模型可能只是从控制器传递到用例,然后从用例返回到演示者和视图的数据结构.

所以这意味着我的模型对象应该是"哑"(就像Spring中的简单Map.ModelMap)然后控制器负责将Use Case类中的数据放入这个Map结构中.

那么,我的用例类采用什么形式?怎么样<UseCase>Interactor

但总而言之,我意识到MVC-MV-thing是过度工程化(或者只是不正确) - 因为"mikalai"在这下面表明它本身只是一个两层应用程序; 数据访问层和前端MVC层.就那么简单.

Ada*_*ent 15

哇哇那么多.而且我认为你大部分已经将Uncle Bob的行话翻译成了你的Spring Java应用程序.

由于建筑主要是意见,因为你的问题有点要求......

有许多不同风格的建筑和...大多数被高估.因为大多数是相同的东西:通过间接和抽象的更高的凝聚力更松散的耦合.

重要的是MOST(恕我直言)是依赖关系.制作大量小项目而不是一个巨大的整体项目是获得"干净"架构的最佳方式.

您最重要的清洁架构技术不是"Spring MVC"技术或"Freemarker"模板语言,也不是Dobb博士的文章,其中包含框,六边形和各种其他抽象多边形的图表.

专注于您的构建和依赖管理技术.这是因为该技术将强制执行您的架构规则.

此外,如果你的代码很难测试..你可能有糟糕的架构.

专注于使您的代码易于测试和编写大量测试.

如果你这样做,很容易改变你的代码而不用担心......你甚至可以改变你的架构:)

谨防过多关注公牛#%$ @#架构规则.说真的:如果您的代码易于测试,易于更改,易于理解并且执行良好,那么您就拥有了良好的架构.没有6周到6包腹肌文章这样做(对不起鲍勃叔叔).这需要经验和时间......没有魔术计划.

所以这里是我自己的"干净"架构......我指的是指导方针:

  • 做许多小项目
  • 使用依赖关系管理(即Maven,Gradle)
  • 不断重构
  • 理解并使用某种依赖注入(Spring)
  • 写单元测试
  • 了解交叉问题(即当您需要AspectJ,元编程等时...)


Aks*_*vig 8

我的解决方案

事实证明,在Java/Spring MVC中实现Bob的"清洁架构"是非常简单的,并且需要比我最初所包含的更多的架构方面.
我实际上找不到任何在线实施的例子.

显然我的架构缺少一个单独的"用例"层模块,因为这个逻辑不应该存在于Spring MVC Web模块中(并且不能被称为" *ViewModel").Web/MVC模块只是应用程序的一个细节,应用程序逻辑应该与它完全分离,并且可以单独测试.

这个新的"用例"模块现在包含*Interactor从域模块(实体)获取数据的类.此外,需要" 请求/响应对象 "来促进MVC/Web模块和用例模块之间的通信.

我的依赖链现在看起来像这样:

Spring MVC模块 - > Use Case模块 - > Domain模块

其中每个箭头(依赖关系)都形成为边界,这意味着在箭头右侧的模块中定义了一个接口,该模块在需要时实现并在需要时注入(控制反转).

以下是我最终得到的接口(根据用例):

I<UseCase>Request- 在MVC模块中实现,在Controller中实例化 I<UseCase>Response- 在用例模块中实现,在Interactor中实例化 I<UseCase>Interactor- 在UseCase模块中实现,注入到Controller中 I<UseCase>Consumer- 在Domain模块中实现,在Interactor中注入

这个怎么运作?
Controller从HTTP请求需要的参数和打包它在RequestModel它向下传递到Interactor.所述Interactor获取其从域模块所需的数据*Consumer,并规定是在其上应用特定逻辑,然后把它在一个ResponseModel并将其发送回到Controller.在Controller随后最后干脆把在这个(现在的GUI型)简单的数据Map对象,并将其转发到Freemarker模板,然后直接使用这些数据,并呈现HTML.

A Presenter可以参与那里的最后一部分,使其成为Model-View-Presenter模式的实现,但我现在就离开了.

我的结论

严格来说,我最终得到的文件比开发早期所需的文件多.然而,随着应用程序的复杂性和大小的增长,我相信这种结构使我们能够轻松保持低耦合和高内聚.此外,Web模块现在可以轻松更换 - 它只是向用例模块发送请求并接收响应对象.此外,应用程序的每一层(域逻辑,应用程序逻辑和GUI逻辑)都是可单独测试的,只有View-part需要Web服务器才能进行测试.

感谢我在这里收到的所有建议和指示.请评论我的解决方案 - 我并不认为它是完美的.


gui*_*e31 6

因此,我们没有传统意义上的“实体”(带有Ids),而是以数据为中心的方法,使该层成为数据访问层,并由使用者模块提供给应用程序的其余部分。

在那部分我觉得有些奇怪。即使您从Web服务获得实体,为什么您的实体也没有ID?

在“清洁体系结构”方法中,实体层完全不是数据访问层。数据访问应该是体系结构中的一个细节,而不是一个中心问题。就像您自己说的,实体包含特定于域的业务规则。业务规则或行为与您获取数据的方式有很大不同。

实体是所有域逻辑发生的地方,而不是从中获取数据的地方。根据Clean Architecture的说明,您可以从网关获取持久性数据或外部数据。

我发现此问题的一种解决方案是使用我选择称为MVC-VM的“ MVVM模式”的变体。请参阅下面的说明。其中的“ VM”部分位于用例层中,由封装了该用例特定逻辑的* ViewModel-classs表示。

ViewModel显然是指视图,它是表示工件-另一个细节。用例/交互器应没有此类详细信息。相反,Interactor应该通过边界发送和接收与交付机制无关的数据结构(RequestModels和ResponseModels)。

我了解这是您的自定义模式,不涉及对表示框架的引用,但是“视图”一词只是一种误导。