当前位置: 首页 > 科技观察

软件项目套路:从三明治到六边形

时间:2023-03-20 20:51:03 科技观察

1.软件项目套路如果你平时的工作是做各种项目(而不是产品),而且你工作的时间足够长,你自然会看到很多不同类型的item。多次切换上下文后,作为程序员,自然会有一定程度的重复感。稍微抽象一下,你会发现所有的业务系统都在做几乎相同的事情:通过一定的渠道与用户进行交互,从而接受输入(NativeApp、MobileSite、WebSite、桌面应用等),将数据进行转换用户按照一定的规则输入,然后保存(一般是关系型数据库),将业务数据以某种形式(列表、卡片、地图上的标记、时间轴等)呈现出来,稍微简化一下,你会发现大多数业务系统实际上都在管理某种形式的资源。所谓管理无非就是CRUD操作。比如知乎是对“问题”等资源的管理,LinkedIn是对“Profile”的管理,Jenkins是对构建任务的管理等等,粗略的看,这好像是同一个套路(当然,资源每个系统管理的可能不止一种,比如知乎也有时间线、直播、动态等资源管理)。这些情况甚至会给开发者一种错觉:世界上所有的信息管理系统都是一样的,区别只是技术栈和运行的业务对象。如果你写一个模板,你几乎可以自动化开发过程。事实上,一些工具已经支持通过配置文件(如yaml或json/XML)的描述生成相应代码的功能。如果是这样的话,软件开发就简单多了。你只需要知道客户业务的资源,然后编写配置文件,最后执行一条命令生成应用即可。但如果你也像我一样生活在现实世界中,赶快放弃这种完全自动化的想法吧2、复杂的业务现实世界中的软件开发是复杂的,复杂性并没有体现在具体的技术栈上。可以学习并快速掌握Java、Spring、Docker、MySQL等具体技术。软件真正复杂的部分往往是业务本身,比如航空公司的超售策略,超售后的乘客移除策略等;比如亚马逊的折扣策略、物流策略等。如何利用软件模型优雅合理地应对复杂的业务(以便在未来业务发生变化时能够更快更准确地响应)本身就很复杂。将复杂的业务规则转化为软件模型,是软件活动中非常重要的一环,也是信息传递经常被扭曲的一环。业务人员说的是A,软件开发者可能理解为Z,反之亦然。比如我租的房子买了1年的联通宽带。但是6个月后,房东要卖房子,把我赶了出去。搬家后需要通知联通帮我搬机。如果纯粹从开发者的角度出发,写出来的代码可能是这样的:简单的值对象。相比之下,在与领域专家交流后,写出的代码会是这样的:为了提高可读性,这样的代码也方便与领域专家的交流和讨论。Eric将统一的语言视为在领域驱动设计中实现DDD的先决条件。3.层次结构(三明治)计算机科学中的所有问题都可以通过另一个层次的间接来解决,当然间接太多的问题除外。--上面提到的DavidWheeler,业务系统的外在表现就是资源管理,而且,现实世界中的业务系统往往需要管理多种资源。这些资源也相互参照、交织。例如看板系统中的泳道、价值流、卡片等;公司、学校、个人、研究机构、项目、项目成员等在LinkedIn中,往往存在嵌套、依赖等关系。为了管理庞大的资源类型和复杂的引用关系,人们很自然地将做同样事情的代码放在一个统一的地方。将不同职责的事物归类是人类处理复杂问题的自然方式,将复杂庞大的问题分解降级为可解决的问题,然后分而治之。(图片来自:http://t.cn/RSNienv)比如在实践中,显示部分的代码只负责渲染数据,应用部分的代码只负责序列化/反序列化,组织和协调业务服务数据访问层负责屏蔽底层关系数据库的差异,为上层提供数据。这就是分层架构的由来:上层的代码直接依赖相邻的下层,一般不依赖间接的下层,层间通过设计良好的API进行通信(依赖通常是单向的).从现代的角度来看,等级结构的出现看似理所当然、自然而然,但实际上它是经过多次演进的。以JavaEE世界为例,早期人们会将负责请求处理、文件IO、业务逻辑、结果生成的应用程序放在servlet中;后来,他们发明了JSPs,可以被web容器翻译成servlets,这样就可以获取数据和展示了。更好的分离(当然这中间也有一些弯路,比如滥用JSTL和taglib导致大量逻辑泄露到表现层);数据存储从JDBC发展到各种ORM框架,最后到JPA的统一。如果用Spring-Boot写的一个RESTful后端和SSH(Spring-Struts-Hibernate)流行时代的后端对比,除了代码量会少很多,层次结构基本不会太大。区别。但是当时SSH中的复杂配置,比如大量的XML,变成了代码中的注解,容器被构建到应用程序中,一些配置演变成约定俗成。一般来说,应用层基本保留:PresentationlayerapplicationLayer数据访问层在某些场景下,一个service层也可能被划分为applicationlayer。4、前后端分离随着智能设备的爆发,移动端成为表现层的主力军。如何让应用轻松适配新的表现层成为新的挑战。这种新的挑战推动了前后端分离的方式,即后端只提供数据(JSON或XML),前端应用程序显示数据。甚至在很多情况下,前端会成为一个独立的应用,有自己的MVC/MVP,只需要有一个HTTP后端就可以独立工作。前后端分离可以很好的解决多终端消费者的问题。现在后端应用不区分前端消费者是谁。它可以是通过4G网络连接的iOS上的本机应用程序,也可以是iMac桌面。Chrome浏览器,也可以是Android上的猎豹浏览器。甚至可以是另一个后台应用:总之,只要能消费HTTP协议的文本即可!这不得不说是一个非常大的进步。一旦后端应用基本稳定,频繁变化的用户界面不会影响后端发布计划,手机用户体验的提升与后端API设计无关。似乎一切都变好了。5.业务和基础设施分离但是,如果有一个消费者(业务系统)根本不使用HTTP协议怎么办?比如使用消息队列或者自定义的Socket协议进行通信,应用程序如何处理这种Scenario?这种情况就像你见过这样一个函数:httpService(request,response);作为程序员,自然会做一个抽象,将协议作为参数传递:service(request,response,protocol);协议可以在服务外构建并注入到应用程序中,这样代码就可以适配很多协议(比如消息队列,或者其他自定义的Socket协议)。例如:publicinterfaceProtocol{voidtransform(Requestrequest,Responseresponse);}publicclassHTTPimplementsProtocol{}publicclassMyProtocolimplementsProtocol{}publicclassService{publicService(Protocolprotocol){this.protocol=protocol;}publicvoidtransform(request,response.protocolst){//romnesslogic)}}同样,同样的原则可以用于数据持久化。对于这样的代码:persisteToDatabase(data);修改后会变成:persistenceTo(data,repository);应用依赖倒置的原则,我们会这样写:publicclassDomainService{publicBusinessLogic(Repositoryrepository){this.repository=repository}publicvoidperform(){//performbusinesslogicrepository.save(record);}}Repository可能有多种实现。根据不同的需求,我们可以自由切换各种实现:publicclassInMemoryRepositoryimplementsRepository{}publicclassRDBMSRepositoryimplementsRepository{},从而使业务逻辑与外围传输协议、持久化机制、安全、审计等隔离开来,应用程序不再依赖于特定的传输details,persistencedetails,这些具体的实现细节又会依赖于应用。通过剥离传统上构建在层次结构中的数据库访问层、通信机制等部分,可以将应用程序简单地分为内部和外部两部分。内部是业务的核心,是DDD(DomainDrivenDesign)中强调的领域模型(包括领域服务、建立业务概念的模型等);外部类似于RESTfulAPI、SOAP、AMQP,或者数据库、内存、文件系统、自动化测试。这种架构风格被称为六边形架构,也称为端口适配器架构。6.六边形架构(端口适配器)六边形架构最早由AlistairCockburn提出。它在DDD社区得到了发展和推广,而后在IDDD(《实现领域驱动设计》)一书中,作者进行了更深入的探讨。(图片来自:slideshare.net)简而言之,在六边形架构风格中,应用程序内部(中间的橙色六边形)包含业务规则、基于业务规则的计算、领域对象、领域事件等。这部分是企业应用的核心:比如网店可以给什么样的商品打折,给那类用户80%的折扣;依赖的Stage应该如何处理。外部的也是我们最熟悉的,比如REST、SOAP、NoSQL、SQL、MessageQueue等,都是通过一个端口连接起来的,然后内外之间有一层适配器,这负责将来自不同端口的数据进行转换并翻译成领域内可识别的概念(领域对象、领域事件等)。在内部,它不关心数据从哪里来,数据如何存储,输出时是JSON还是XML。事实上,它对调用者一无所知。它可以处理的数据已经是适配器转换的域对象。六边形架构的优点:业务领域边界更清晰,扩展性更好,支持测试友好。实施DDD更容易。增加一种新的数据库支持,或者扩展RESTful应用支持SOAP,我们只需要定义一套port-adapters就可以了,不需要触及业务逻辑部分,不会影响现有的端口适配器。由于业务之外的一切都属于外围,因此应用程序是真正运行在Web容器中还是运行在Java进程中并不重要。这时候自动化测试就会容易很多,因为测试的重点:业务逻辑和复杂的计算是一个简单的对象,不需要容器、数据库等环境问题。单元级测试可以覆盖大部分业务场景。这种架构模型甚至可能会影响团队的组成。对业务有深刻理解的业务专家和技术专家共同完成核心业务领域的建模和编码,而外围可以交给新人或外包。在许多情况下,从开发人员的角度所做的假设事后证明是错误的。人们在预测软件未来的演进方向时,往往会做出很多错误的决定。比如关系型数据库的选择,前端框架的选择,中间件的选择等等,六边形架构可以帮助我们避免这些。7.小结软件的核心复杂性在于业务本身。我们需要非常熟悉业务本身才能正确地为业务建模。通过统一的语言,我们可以写出一个表达意思,便于业务人员交流的模型。另一方面,模型应尽可能与基础设施(如JSON/XML、数据库存储、通信机制等)分离。这样就很容易使用mock的方式来解耦模型和基础设施,更容易测试和修改。其次,我们的领域模型也更加独立和精简,在适应新的需求时修改起来会更容易。【本文为专栏作家《ThoughtWorks》原创稿件,微信公众号:Thinkworker,转载请联系原作者】点此查看该作者更多好文