Web 开发选 MVC 还是 DDD?
距离上次更新已经有 7 天了,只要停下来一天,就会有第二天,第三天,越是不写,就越不知道写什么。这就是惯性的力量,无论是勤劳还是懒惰,都会产生惯性,于是勤劳者越来越勤劳,懒惰者越来越懒惰,学霸越来越霸,学渣越来越渣。时间一长,就会觉得自己根本无法改变自己,总会回到我们习以为常的状态。所以,朋友们,一定要警惕惯性,他使我们越来越好,也会使我们越来越坏,这不,我又逼着自己更新了。
之所以停止了更新,一方面是懒惰的小人击败了勤奋,另一方面是因为时间不够用。下班后就那么点时间,用于这个就不能用于那个,而我又是一个喜欢写代码的人,一旦开始写代码,时间就飞快的流失,从晚上 8 点写到晚上 12 点,也是一眨眼的功夫,明天还要上班,我不可能再熬夜。熬夜会把第二天废掉,得不偿失。最近在学习并尝试 golang 的 Web 开发,已经入门了,从以前 Django 的 MVC 模式,也渐渐的切换到了 Golang 的 DDD 模式,感觉 DDD 更具有面向对象风格,而 MVC 更像是一种面向过程的风格。
今天展开来说,什么是 MVC,什么是 DDD,它们分别适合什么样的场景。
什么是 MVC、什么是 DDD
MVC 三层架构中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:展示层、逻辑层、数据层。熟悉 Django 的朋友可以这么映射,M 就是我们编写的 models.py 表示数据层,定义数据的存储,而 V 就是 views.py,里面存放着大量的业务逻辑,C 就是 urls.py 控制着路由的访问。前端请求首先访问 Controller,然后是 View,最后是 Model,这就是面向数据访问的过程来定义的架构。
MVC 的缺点就是虽然 M 和 V 是两份文件,但是数据和业务逻辑高度耦合的,也就是说,M 只负责了数据的定义,而数据的操作都在 V,一旦修改了 M,改 V 是真是苦不堪言,这种将数据与操作分离的特点,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。
与之对应,将数据和操作定义在一起,就是 DDD,全称叫领域驱动设计(Domain Driven Design,简称 DDD),领域驱动设计这个概念并不新颖,早在 2004 年就被提出了,不过现在又被大家重视起来,还是基于微服务的兴起,微服务就是大服务拆分为小服务嘛,这样就要做好业务模块划分,自然也就加速了领域驱动设计的盛行。
DDD 开发模式实现的代码,也是按照 MVC 三层架构分层的。Controller 层还是负责暴露 API 接口,M 层还是负责数据存取,V 层负责核心业务逻辑。它跟 MVC 的主要区别还是 M 和 V 的不同。传统的 M 只定义数据数据的结构,不定义数据的操作,而 DDD 开发模式,M 不仅定义数据的结构,还定义数据的操作。
比如 Django 的 M 和 V 可能是这样的:
M
# models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
# 数据模型的定义
class User(AbstractUser):
"""
数据的定义
"""
...
class Meta:
db_table = 'user'
verbose_name = '用户信息'
verbose_name_plural = verbose_name
M
# views.py
class UserViewSet(viewsets.ModelViewSet):
"""
数据的操作、增删改查
"""...
Golang 的 M:
// User.go
type User struct {
//数据的定义
...
}
//数据的操作、增删改查
func (u *User) BeforeSave() error {
...
}
func (u *User) Prepare() {
...
}
func (u *User) Save(db *gorm.DB) (*User, error) {
...
}
func (u *User) UpdateAUser(db *gorm.DB, uid uint32) (*User, error) {
...
}
func (u *User) DeleteAUser(db *gorm.DB, uid uint32) (int64, error) {
...
}
Golang 的 V
func (server *Server) DeleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
user := models.User{}
uid, err := strconv.ParseUint(vars["id"], 10, 32)
if err != nil {
responses.ERROR(w, http.StatusBadRequest, err)
return
}
tokenID, err := auth.ExtractTokenID(r)
if err != nil {
responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
return
}
if tokenID != 0 && tokenID != uint32(uid) {
responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
return
}
_, err = user.DeleteAUser(server.DB, uint32(uid))
if err != nil {
responses.ERROR(w, http.StatusInternalServerError, err)
return
}
w.Header().Set("Entity", fmt.Sprintf("%d", uid))
responses.JSON(w, http.StatusNoContent, "")
}
里面调用了 M 中的 DeleteAUser,以后修改 Model 时,只需要修改函数 DeleteAUser,而不需要修改 V。
注意,MVC 和 DDD 与编程语言和框架都没有关系,因为正好手边有对应的代码,就拿来用了。
MVC 和 DDD 分别适合什么样的场景?
MVC 适合简单的业务,DDD 适合复杂的业务,为什么这么说呢?
如果系统业务比较简单,简单到就是基于 SQL 的 CRUD 操作,那么根本不需要动脑子精心设计 DDD 模型,MVC 模型就足以应付这种简单业务的开发工作。因为业务比较简单,即便我们使用 DDD,那模型本身包含的业务逻辑也并不会很多,设计出来的领域模型也会比较单薄,跟 MVC 差不多,没有太大意义。
你可能会问,DDD 不就是把部分数据的操作放在了模型里面吗,为什么就适合复杂的业务呢?
不夸张地讲,MVC 模式的开发,大部分都是 SQL 驱动(SQL-Driven)的开发模式。我们接到一个后端接口的开发需求的时候,就去看接口需要的数据对应到数据库中,需要哪张表或者哪几张表,然后思考如何编写 SQL 语句来获取数据。之后就是定义 models.py 编写 views.py 中的视图函数,你可以这么理解,views.py 中就是各种 SQL 语句。而 SQL 语句是不能复用的,新接口开发即使有部分相同的逻辑,也只能重新编写视图函数。
而 DDD 开发模式下,我们需要事先理清楚所有的业务,定义领域模型所包含的属性和方法。领域模型相当于可复用的业务中间层。新功能需求的开发,都基于之前定义好的这些领域模型来完成。越复杂的系统,对代码的复用性、易维护性要求就越高,我们就越应该花更多的时间和精力在前期设计上。DDD 开发模式,正好需要我们前期做大量的业务调研、领域模型设计,所以它更加适合这种复杂系统的开发。
最后的话
平时做 Web 开发,基本上,都是使用 MVC 架构,就连 Spring 的官方 Demo 也是 MVC 模式,也就是说 MVC 仍然是主流,因为项目之前就是 MVC 架构,保持不变的成本最小。
但 MVC 是典型的面向过程风格的设计,不适合复杂的系统,比如金融类系统、账务核算系统。DDD 架构把数据和操作封装在一起,对数据的操作可以复用,是面向对象风格的设计,比较适合复杂的业务系统。
一句话,简单的系统,就用 MVC,复杂的系统就用 DDD。
如果有帮助,还请点赞、转发、在看,感谢阅读。