传统MVC存在的问题
web开发中MVC已经是一个常见的结构了,然而对于稍微复杂的项目还是有一些问题:
- 业务代码写在model层,导致model越来越臃肿,新旧业务、不同场景杂糅,越来越难以维护。
- 复杂业务同时设计多个model(多张表),到底应该放在哪个model里?
- 网上有的说MVC导致代码难以测试什么的,其实并不是这样,代码写在model里和写在service里都是可以单元测试的。这里主要还是写在model里太臃肿的问题(第1点)
- 代码复用的问题。
一般model都是orm映射数据库的,所以在这里写完的代码是直接进出数据库的。那么问题来了,如果我在不同的场景对数据进行不同处理呢?比如:自己可以看自己的手机号,别人看到的是加密后的手机号。为了安全问题,需要在后端就加密好再传到前端,那么这个加密我就只能写在controller里。那么需要在另一个接口看到自己的手机号呢?是不是得再写一遍?当然这只是一个很简单的场景,有更好的例子欢迎补充。
更合理的代码分层
接下来说说为了解决上面的问题,我个人在代码结构上总结的方法:
通过添加service层、repository模式、transform层,结合原有的MVC,来保障代码的复用、隔离、稳定、可测。
下面按照重要性倒序介绍~
1.【必须】MVC
这里重新界定一下:
- controller层用于接收用户数据、进行简单的数据处理。至于数据校验,有的框架在controller里,比如laravel的表单验证,而yii2是直接将验证规则放在了model里,所以不一概而论。
- model层不再写任何业务逻辑,仅作为数据库访问层(DAO)使用,包括:
- 数据表字段映射
- 数据表关联关系映射
2.【必须】service业务层
这个service不是框架中抽象的service(不是service provider的service,也不是service locator的service),而是指实际业务的那个service。比如,处理订单的业务层,就是OrderService.
service层用于写业务逻辑,比如电商网站下订单功能,本质上就是订单表增加一条数据,然而业务上却连接着库存、购物车、销量等等等等。这种涉及了多表操作的,单独放在哪个表里都不合适。那么就放在service层,在OrderService中写一个createOrder()方法,保证所有的下单操作都调用这个方法即可。
3.【推荐】repository查询层
这一层封装了查询数据的方法,介于model和controller中间,使用repository封装好常用的查询,会让controller感觉不到model的存在。
使用前:查询时经常要select字段,或者做字段判断,用字符串手写,但是字符串这个东西,用起来一时爽,重构火葬场。当我需要修改某字段或修改查询条件时,只能全文ctrl f + 人工识别,对着自己写的一行行代码无语凝噎。
使用后:因为提前封装在了repository层,所以之后查询、select用这一层的方法,重构就根本不是问题。
当然了,如果项目或者功能比较简单,没有太大的必要用这一层.
4.【推荐】transform层
这一层主要是对model层取出的数据做一些转换,controller调用repository层获得原始数据库数据,再交给transform层根据业务要求转换数据。
关于过度设计
这个问题还是要回到实际项目来说,如果是简单的项目,MVC就足够了,但是如果是电商类复杂一些的项目,做更合理的代码分层是非常必要的。
首先,这种设计能够应对所有复杂情况,其次,每层的职责分明,就算是刚上手项目的程序员了解了也能够快速掌握。否则把所有业务揉在一坨if else里,读起来就像拆积木,维护的难度更大。
这样的优点
- 代码可测部分更全面,写完了心里更有底
现在的代码结构除了controller都可以测试,当然不必每个都写单元测试,但是针对一些复杂的情况,比如搜索附近的美食信息这个功能,是关键字搜索+距离排序,我就为repository层对应的方法写一个测试实例,就算以后业务变动也能够保障这个方法没有问题。 - 代码结构更加清晰,职责更加单一。
面向对象开发的原则之一就是职责单一,一个类只负责一个职责。
想象有一天,产品经理突发奇想让你开发一个奇葩的新功能,这时候,有两个选择在你面前:1.把这些奇葩功能继续放在之前的类里,让它们和正常功能傻傻分不清楚;2.建一个QiPaService专门做这些需求,等到产品经理清醒过来,一键delete就完事。 - 把更多的注意力关注在业务实现上,而不是怎么代码怎么写上。
脱离实际业务谈开发等同于纸上谈兵,学会了《电商开发》课程实际上手时也还是会遇到问题,因为业务一直有新的需求,功能也需要迭代。所以我们的重点就是把业务封装在service这个黑盒里,在快速开发阶段也不必太过纠结service实现的是否完美,等到项目稳定的时候再来进行优化重构即可(然而大多数功能听不到优化就删改了)。
这样的缺点
虽然我自己一直在用这一套代码结构,但是我还是不得不承认它的一些问题:
- 降低性能(虽然在我看来可以忽略不计)
- 代码结构复杂,代码工作量大
写得代码越多就越累,也越容易犯错,想象我们每写一个功能都要建一个controller、一个model、一个repostiroy、一个transform、一个service是一种什么样的体验?
我个人很讨厌重复无意义的事情,所以在经历了一段时间上述的折磨后,我找到了很好的解决方案,将在下一篇介绍下我的独(tou)家(lan)方法~
最后,文中涉及的内容都是我的个人总结,可能会有一些问题,欢迎交流指正!