本文转载自微信公众号“一言”,作者秦语。转载本文请联系一言公众号。一个系统的架构是它的高层视图,是系统的整体视图,是粗略的系统设计。架构决策是关于系统结构的决策,它会影响整个代码并确定系统其余部分的基础。除其他事项外,体系结构决定了系统的:组件组件之间的关系指导组件及其关系的设计和演化换句话说,这些设计决策随着系统演化而更难改变,它们是支持特性开发的基础。意大利面式架构我参与过的一些项目具有完全任意的结构,既不反映架构也不反映领域。如果我的问题是“这个值对象应该放在哪里?”,答案是“放在src目录下就行”。如果我的问题是“执行此逻辑的服务在哪里”,答案是“用您的IDE搜索它”。这意味着根本没有考虑如何组织代码。这里的隐患很大,因为根本没有使用包来实现模块化,高层代码关系和流向根本不遵循任何逻辑结构,会导致模块高耦合低内聚。事实上,可能根本就没有模块划分。应该属于某个模块的代码分散在整个代码库中。这样的代码库就是所谓的意大利面条代码或意大利面条架构!可维护的代码库拥有可维护的代码库意味着我们可以通过最少的代码修改获得最大的概念变化。也就是说,如果我们需要修改一个代码单元,那么其他代码单元应该尽量少修改。这带来了明显的优势:更容易修改,因为受影响的代码更少;更快的修改,因为更少的代码受到影响;出现问题的机会更少,因为修改的代码更少;封装、低耦合和高内容Poly是保持代码隔离、实现可维护代码库的核心原则。封装封装是隐藏类的内部表示和实现的过程。即实现是隐藏的,这样可以随意改变类的内部结构,而不影响使用该类的其他类的实现。低耦合Coupling关注的是代码单元之间的关系。如果一个模块的修改导致另一个模块的修改,则称两个模块是高度耦合的。如果一个模块可以独立于任何其他模块,则称该模块是松散耦合的。松散耦合的目标是通过提供一个稳定的接口来实现的,该接口有效地隐藏了其他模块的实现。低耦合的优点可维护性——修改仅限于一个模块可测试性——单元测试涉及尽可能少的模块可读性——尽可能少的需要仔细分析的类高内聚性内聚性是指衡量一个模块内功能依赖性有多强.低内聚意味着模块有几个不同的不相关的职责。高内聚意味着模块所拥有的功能在很多方面是相似的。高内聚的优点可读性——(紧密)相关的功能包含在一个模块中可维护性——仅用于在一个模块内进行调试效果以上原则适用于类,然而,它们同样适用于类的组合。类的组合通常称为包,但我们可以将其划分为更细粒度的类别。如果分组是出于纯粹功能性的考虑(比如ORM),我们就称它为模块。如果是出于域的考虑(例如AccountManagement)则称为组件。这些定义与Bass、Clements和Kazman在他们的软件架构实践一书中描述的定义一致。我们可以而且应该让包高内聚低耦合,因为这样我们可以:修改一个包而不影响其他包,减少问题;修改一个包,不修改其他包,加快交付节奏;允许团队专注于特定的包,从而导致更快、更强大和设计更好的更改;减少团队开发活动之间的依赖和冲突,提高生产力。更仔细地考虑组件之间的关系使我们能够更好地将应用程序建模为一个整体并交付更高质量的系统。概念封装我觉得如果我们的项目结构能够以某种方式同时反映架构和领域,我们的代码库的可维护性可以大大提高。事实上,现在我很确定这是唯一的方法(当我们处理大中型企业应用程序时)。当代码库组织良好时,特定代码单元只有一个地方。我们可能不知道具体位置,但一定只有一条合乎逻辑的路径可以让我们循着线索找到它。包定义将类划分为包允许我们在更高的抽象层次上思考设计。它的目标是根据一定的条件对你的应用程序中的类进行切片,然后将这些切片分发到包中。这些包之间的关系表达了??应用程序的高级组织。-RobertC.Martin1996,粒度,第3页将概念上相关的代码定义到包中,这是我们需要实现的。这些包很重要,因为它们定义了独立于其他包的概念上相关的代码单元,以及这些包之间的关系。这样做的目的是:理解代码单元之间的关系维护代码单元之间的逻辑关系实现高内聚低耦合的代码包在不影响/最小影响应用程序的情况下重构代码包替换对应用程序影响较小的代码包。分包原则我们必须遵循RobertC.Martin在1996年和1997年提出的包划分原则和其他一些原则来实现目标,主要有CCP(CommonClosurePrinciple,通用闭包原则)、CRP(CommonReusePrinciple,通用ReusePrinciple)和SDP(StableDependenciesPrinciple,稳定依赖原则)。RobertC.Martin提出的包划分原则:PackageCohesionPrincipleREP–ReuseReleaseEquivalencePrinciple:重用的粒度等同于release的粒度CCP–CommonClosurePrinciple:一起修改的类应该放在一个包中CRP–CommonReusePrinciple:一起重用的类应该放在一个包中PackageCouplingPrincipleADP–AcyclicDependencyPrinciple:一个包的依赖图中不能出现循环SDP–StableDependencyPrinciple:依赖应该朝着稳定的方向发展SAP-Stable抽象原则:抽象层次越高,稳定性越高要合理使用SDP,我们应该定义代码的概念单元(组件)和组件的分层,这样我们才能弄清楚哪些组件应该了解(依赖)其他组件。但是,如果这些组件的边界不够清晰,我们就会将本不应该相互关联的代码单元混在一起,耦合在一起成为意大利面条代码,最终变得不可维护。为了清楚地显示这些边界,我们需要将概念上相关的类放在同一个包中,就像我们将概念上相关的方法放在同一个类中一样。在包级别,我们只能区分名称在域中有意义的文件夹(例如,UserManagement、Orders、Payments等)。在最底层,也就是包中的叶子节点,我们会在必要的时候按照功能范围(例如,Entity、Factory、Repository等)进行分类。下面的问题可以帮助我们反思如何设计低耦合的组件:“如果我想删除一个业务概念,我是否可以删除它的组件根目录来删除这个业务概念的所有代码而应用程序的其余部分不是会坏吗?”如果答案是肯定的,那么我们就有了一个解耦良好的组件。例如,在一个命令总线架构中,命令和处理器不能脱离彼此工作,它们在概念上和功能上是绑定在一起的,所以如果我们需要移除这个逻辑,我们需要将它们一起移除。如果它们在同一个地方,我们可以直接删除一个文件夹(我们并不是真的要删除代码,只是用这种思维方式来帮助我们获得解耦和内聚的代码)。因此,遵循CCP和CRP原则,命令应与其处理程序放在同一文件夹中。任何代码只能存在于一个逻辑位置,这一点即使对于项目中的新手和初级开发人员来说也是显而易见的。这避免了不一致、复杂、重复的代码和开发人员的挫败感。如果我们需要寻找代码因为我们找不到它应该在的地方,和/或很难理解哪些代码与我们手头正在处理的代码相关......那么我们的项目结构很糟糕,甚至更糟糕的情况,糟糕的架构。ScreamingArchitectureScreamingArchitecture是RobertC.Martin的想法,它基本上表达了一种想法,即架构应该清楚地告诉我们系统做什么:即它的主要领域。那么源代码文件夹中出现的一级目录自然应该与领域概念相关,即顶层限界上下文(如patient、doctor、appointment等)。它们应该独立于系统使用的工具(例如,Doctrine、MySQL、Symfony、Redis等),独立于系统的功能块(例如,存储库、图形、控制器等),并且独立于交付机制(HTTP、控制台等)。您的架构应该向人们展示系统,而不是系统使用的框架。如果您正在构建医疗保健系统,新程序员看到源代码存储库的第一张图片应该是:“哦,这是一个医疗保健系统”。-RobertC.Martin2011,ScreamingArchitecture这实际上是一种更简单的方式来理解他在15年前发表的数据包分区原则,我之前已经对此进行了解释。这种分包方式也称为“按特性分包”。延展阅读2008–JohannesBrodwall–Packagebyfeature2012-JohannesBrodwall–HowChangingJavaPackageNamesTransformedmySystemArchitecture2012–sivaprasadreddy.k–按特性打包的方法好吗?2013–LahlaliIssam–从Hibernate核心实现中学习的经验教训2013–ManuPk–按功能而不是按层打包您的类2015–SimonBrown–按组件和架构对齐测试打包2015–CésarFerreira–按功能打包,而不是按层打包layers2017*–javapractices.com–按功能打包,而不是层引用来源1996–RobertC.Martin–粒度1997–RobertC.Martin–稳定性2009–500internalservererror–低耦合和高内聚是什么意思?封装原理是什么意思?2011–罗伯特·马丁–ScreamingArchitectureQinYu,Android开发者/ThoughtWorks技术教练//译者,热衷于探索软件开发的方方面面,从端到云端,从工具到实践。他喜欢通过翻译学习和分享知识。他的翻译作品包括《Kotlin实战》、《领域驱动设计精粹》、《Serverless架构:无服务器应用与AWS Lambda》、《云原生安全与DevOps保障》。
