分层建筑的疯狂

我曾经拜访过一个包含15个代码层的团队。 即:如果您想在网页上的数据库中显示某些数据,则该数据将通过应用程序中的15个类传递。 这些层是做什么的? 哦,没什么。 他们只是将数据从一个对象复制到另一个对象。 或者有时“访问对象层”将执行对象是否有效的检查。 或者也许将在“边界对象层”中进行检查。 它因您查看应用程序的哪一部分而有所不同。

我很困惑(有些恼火),问团队为什么他们如此构造应用程序。 答案很简单:昂贵的顾问告诉了他们,他们被聘请为组织的架构提供建议。

我问团队顾问有什么理由。 他们只是耸了耸肩。 谁知道?

今天,我经常拜访代码中包含三到五个层的团队。 当被问到为什么时,答案通常是相同的:这是他们给出的建议。 从书籍,视频或会议演讲中获取。 而且其理论充其量还是难以捉摸的。

我们为什么要构建分层应用程序?

在计算领域有句古话:计算机科学中的任何问题都可以通过添加间接层来解决。

著名的是,这是我们现代网络堆栈背后的指导原则。 在Web服务中,SOAP在HTTP之上执行方法调用。 HTTP在TCP之上发送请求并接收响应。 TCP在IP之上的两个方向上流数据。 IP通过诸如以太网之类的物理协议在网络上路由比特分组。 以太网向总线上的所有计算机广播带有目标地址的位数据包。

每一层执行的功能使高层可以抽象化复杂性,例如重新发送丢失的数据包或通过全局互连的网络路由数据包。

该类比用于争论企业应用程序体系结构中的各个层。

但是企业应用程序不像网络协议。 大多数企业应用程序中的每个层都以相同的抽象级别运行

以一个流行的示例为例:John Papa在“单页应用程序”上的视频在服务器端使用了以下层(在客户端使用了单独的层):控制器,UnitOfWork,存储库,工厂和EntityFramework。 因此,例如,CodeCamperUnitOfWork中的AttendanceRepository属性将AttendanceRepository返回到AttendanceController,后者在AttendanceRepository层中调用GetBySessionId()方法,该方法最终在EntityFramework上调用DbSet.Where(ps => ps.SessionId == sessionId)。 然后是RepositoryFactories层。 呜!

那是做什么的呢? 它根据参数过滤实体。 ?!

(这暗示了这种偏离是视频演示文稿中的讨论从底部开始,并逐渐形成控制器,而不是外部的)

在类似的Java应用程序中,我已经看到并且可以跳过这些繁琐的细节,SpeakersController.findByConference调用SpeakersService.findByConference,后者调用SpeakersManager.findByConference,后者调用SpeakersRepository.findByConference,该构造一个可怕的JPAQL查询,任何人都无法理解。 JPA返回一个@Entity,该实体已映射到数据库,并且存储库,或者可能是Manager,Service或Controller,或者可能是其中的两个或三个,将从演讲者类转换为另一个。

为什么这是个问题?

代码的成本:一个合理的推测是,开发和维护应用程序的成本会随着应用程序的大小而增长。 无价值地添加代码是浪费。

单一职责原则:在上面的示例中,SpeakerService通常包含与扬声器相关的所有功能。 因此,如果添加发言人要求您从下拉列表中选择会议,则SpeakerService通常将具有findAllConferences方法,因此SpeakersController不需要也依赖ConferenceService。 但是,这使类成为功能性的磁铁。 症状是缺乏连贯性:一类方法可以分为不同的集合,这些集合永远不会同时使用。

哑服务:“服务”是一个班级的可怕名称–服务或多或少是连贯的功能集合。 更有意义的名称将是用于存储和检索对象的服务的“存储库”,查询是根据条件(实际上是命令而不是服务)选择对象的服务,网关是与之通信的服务在另一个系统中,ReportGenerator是创建报告的服务。 当然,如果控制器从数据库中获取数据以为另一个系统生成报告,则控制器可能引用存储库,报告生成器和网关的事实应该是很正常的。

多扩展点:如果您有一个控制器,该控制器调用了服务,该控制器又调用了管理器,而该管理器又调用了存储库,并且您想添加一些验证以确保所保存的对象是一致的,那么该将其添加到何处? 您愿意打赌团队中的其他开发人员也会给出相同的答案? 您愿意打赌几个月后会给出相同的答案?

迎合最小公分母:在我们一直在使用的会议应用程序中,DaysController创建并返回可用于会议的日期。 DaysController所需的功能非常简单。 另一方面,TalksController具有更多功能。 即使这些控制器有极大不同的需求,但它们都具有相同(无聊)的类集:控制器,UnitOfWork和存储库。 除了对一致性的渴望外,DaysController没有理由不能直接使用EntityFramework。

大多数应用程序都有一些包含应用程序内容的功能垂直对象,以及许多小的支持垂直对象。 相同地对待它们只会增加工作量和维护工作量。

那么如何解决呢?

您必须做的第一件事是从外部构建应用程序。如果您的工作是返回一组对象,则可以使用.NET EntityFramework直接访问DbSet,只需将IDbSet注入控制器即可。 使用Java JPA,您可能希望使用带有finder方法的存储库来隐藏JPAQL的疯狂行为。 无需任何服务,经理,工人或任何其他东西。

您必须做的第二件事是扩展您的体系结构。 当您意识到控制器中的责任比决定处理用户请求多得多时,您必须提取新类。 例如,您可能需要PdfScheduleGenerator为您的会议创建可打印的日程表。 如果使用的是.NET实体框架,则许多人都想在IEnumerable(由IDbSet扩展)上创建一些LINQ扩展方法。

您必须做的第三件也是最重要的事情是给班级起一个能反映其职责的名称。 服务不仅应成为转储许多方法的场所。

计算机科学中的每个问题都可以通过添加一个间接层来解决,但是软件工程中的大多数问题都可以通过删除一个错位的层来解决。

让我们构建更精简的应用程序!

翻译自: https://www.javacodegeeks.com/2014/07/the-madness-of-layered-architecture.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值