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

新项目模块无法拆解,但大项目如何处理?

时间:2023-03-18 22:20:16 科技观察

很多同学在创建项目后就迫不及待地开始写作了。项目代码不像一些框架代码那样可以自由编写,但一般都是采用MVC模式开发的。可悲的是,Java中web开发的这些目录名还是乱七八糟的,还需要自己规划。什么Controller、Service、Dao等等,但其实这种划分方式有很多弊端!本文将先介绍两种典型的层级结构,然后借鉴一点DDD思想,说说我在项目中常用的目录结构。这篇文章非常实用,将讨论如何对大型项目进行目录划分。清晰的目录结构可以帮助其他同学轻松理解项目的功能模块,在项目中保持整体一致性也是一个很好的习惯。如果再增加一个扩展性,那么目录划分就是重中之重。有两个典型的分类,但有很多细节。1.最简单的MVC我们平时最熟悉的,就是MVC结构。这种结构很流行,写简单的项目也很方便,但是会造成严重的耦合问题和服务爆炸问题。几千或几万行代码是家常便饭。模型(model)代表应用程序的核心(如数据库记录字段)。View(视图)显示数据(数据库记录)。控制器(controller)处理输入(写入数据库记录)。在项目划分上,类似于下面的目录结构。1.1模型域在DDD中是一个非常宽泛的概念。但是,我们通常将其作为数据库对应的Java类来使用(没有错)。在实际运行中,可能还有以下几种名称,在普通项目中区别不大。您最好在项目中保持相同的含义以避免歧义。entity的意思比较明显,就是entity的意思,用的最多。比如JPA的Entity注解模型模型的意思,一般用于不同系统之间的交互。但是如果你的模型很简单,直接用entity来表示也是可以的。域的范围有点大,甚至包括域中的服务。如果您对DDD的概念不是很熟悉,那么请尝试上面的内容。对于简单的项目,我通常在项目中使用实体来表示与数据库的交互。在JPA等ORM中,也会做相关的处理。比如javax.persistence.Entity注解。您需要了解的是,SpringData实际上采取了一个折衷点,将许多东西揉在一起。1.2道道层全称为数据访问层,全称数据访问对象,属于比较底层和基础的操作。在其他一些框架中,它会被称为其他名称。mapper这个一般是Mybaits等框架生成的目录,一般是一些接口。repository仓库的意思在jpa中经常用到。Dao应该满足最小封装的原则,理论上只涉及执行一句SQL。如果有多个数据访问动作,则需要封装在Service中,用事务来管理(虽然这么说,但在DDD中repository并没有和具体的数据库打交道)。1.3service和controller没什么好说的,基本上所有重要的逻辑都在这里完成了。Service用于逻辑处理,Controller用于接口暴露。2.按职能组织大多数情况下,我们可以使用上面的划分模式来很好地完成工作。比如所有的数据处理都放在Dao层,所有的逻辑处理都放在Service层。这在小项目中还好,但是如果项目中有成百上千个Entities,这些目录下的文件就会爆炸,导致最后无法维护。另一个问题是,只是一个简单的功能可能分散在多个包下的多个文件中,难以维护大型项目。我们还有一个想法,就是按照功能来分组。就像下面的截图。我们将类似的功能放在模块下的单个文件夹中。如果功能模块比较大,我们可以在功能模块下进行分层设计。比如上图中,有一个商品服务。我们给它分配了一个目录空间goods,然后在里面划分dao、entity等目录;但是对于Service和Controller,我们只是简单的放在了外层,可以看到模块内的分配更加灵活。这样做的好处是显而易见的。功能变得非常集中,各个包的内容互不影响。3.还是不够优雅其实,即使这样划分,项目还是会面临很大的挑战(很多DDD的书都会讲很多层的交互)。分享一个我平时使用的分层模式,兼顾了高内聚和低耦合,扩展性好。config,最外层的一些全局配置,比如web配置,消息队列配置等system,全局工具和依赖函数,在DDD中叫做infrastructure(但是在非DDD实践项目中这个名字太怪异了)auth,permissions认证模块,比如JWT或者SpringSecurity,应该独立设计,这样就可以和网关分离,比如Zuulbc,在DDD中就是BoundedContext。我们也可以直接称它们为模块。这些模块有严格的界限,可以根据请求量拆分成相应的微服务。上图中crm、images、order等都可以拆分成独立的微服务。让我们看一下每个模块内的结构。类似于传统的MVC。但是,为了屏蔽变化和兼顾可扩展性,我们增加了更多的内容。Persitence,持久层,用的是JPA还是Mybatis,这个无关紧要。我们的目标是尽可能弱化持久层的实现,将领域层的变化封装起来。Persitence/dao,具体的持久层接口,比如MyBatis的Mapper文件,或者JPA的Repository领域层,具体的业务层,你可以把它看成是一堆Getter和SetterBeans。我们尽量把大部分的验证类和变化封装在这里(大致可以认为是DDD中的充血模型)controller,具体的Rest接口层。但不同的是有很多不同的请求和返回。我们把它们封装成Request和Response,用来接受提交的数据,瘦身返回的数据等应用,和传统的服务层打交道。除了在应用程序中调用Dao外,其他层没有调用Dao的api的权限,应用程序的功能也是一样的。而api的接口指的是模块之间可以相互调用的接口。除了api暴露的接口外,bc之间的类和接口默认是互不可见的utils。不常见的实用程序将放在模块内,而不是提取公共实用程序。除了解决目录问题,我们还需要规划清楚数据的流向。上层应用可以通过API接口直接调用下层服务。例如,订单系统接入商品的基本信息数据;否则无法访问,例如商品基本信息模块访问订单系统的接口。如果下层要对上层数据进行更改,只能通过消息模块发布更改,其他模块可以订阅这些更改。总结综上所述,xjjdog认为,如果你的项目可能比较大,单纯使用分层包并不是一个好习惯。这类后台管理项目大家可能不陌生,有很多好用的模板,都是简单的MVC层。处理一些外包项目,做一些一次性的工作可能没有问题,但是一旦是比较大的长期项目,这种分层的目录界面就会显示出它的缺点。这是因为:项目的短期风险是工期问题;而长期风险是扩张问题。随着访问量的增加和低耦合高内聚需求的增加,如何快速响应需求,减少BUG将是制约项目发展的最主要因素。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。我的个人微信xjjdog0,欢迎加好友进一步交流。