业务人员和开发人员对需求的时,常常会出现下面这段对话。
业务人员:我希望加一个用户注册的功能,用户要填入自己的用户电话,邮箱,还有头像,我们保存图片需要进行大小缩放,以便符合我们的要求,同时电话,邮箱要有效,还要加短信验证码。
开发人员1:好的,短信验证码我们需要写一个发短信的服务,然后,随机生成验证码,将验证码存到验证码表中和phonenumber对应起来。然后当用户注册的时候,需要前端检查电话号码,邮箱是否正确。然后后端先通过phonenumber查询到验证码,进行验证码验证,然后获得头像图片,进行图片处理,最后存到mysql中的user和account表中。
用户注册操作代码实现如下
class AccountService {
public void createAccount(String username, String avatar, String phone, String email, String password){
if(password == null){
return;
}
if(password.contains("*")){
return;
}
...
String uploadUrl = getPicture(avatar)
User user = new User(username, phone, email, uploadedUrl);
Account account = new Account(username, password);
accountRepo.save(username);
account.setPassword(password);
}
// 图片处理,使得图片大小在一个规定的值
private File processPicture(String avatar){
//下载图片
//判断图片大小是否符合标准
//当图片过大,进行图片裁剪
//当图片过小,进行图片放大
...
}
private void getPicture(String avatar){
File avatarFile = processPicture(avatar)
// uploadFile 实现逻辑
...
}
}
}
class Account{
private String username;
private String password;
}
class User{
private String username;
private String phone;
private String email;
private String uploadedUrl;
private String avatar;
}
用户注册操作代码修改
class AccountService {
private int WIDTH_PIXEL = 1024;
private int HIGHT_PIXEL = 1024;
public void createAccount(String username, String avatar, String phone, String email, String password){
if(password == null){
return;
}
if(password.contains("*")){
return;
}
...
String uploadedUrl = FileUtils.processPictureTo(avatar, WIDTH_PIXEL, HIGHT_PIXEL)
User user = new User(username, phone, email, uploadedUrl);
Account account = new Account(username, password);
accountRepo.save(username);
account.setPassword(password);
}
}
class Account{
private String username;
private String password;
}
class User{
private String username;
private String phone;
private String email;
private String uploadedUrl;
private String avatar;
}
class FileUtil {
public String processPictureTo(String avatar,WIDTH_PIXEL, HIGHT_PIXEL){
//下载图片
//判断图片大小是否符合标准
//当图片过大,进行图片裁剪
//当图片过小,进行图片放大
//上传图片
}
}
可以看到,上面的对话和代码实现,包含2类内容。技术细节和业务逻辑。
咱们再看看AccountService,主要实现注册功能的类。processPicture方法中存在了处理图片的逻辑(裁剪图片,放大图片等技术细节),也包含了业务逻辑(规定的图片大小)。当我们想从AccountService中了解业务逻辑时,我们需要读懂processPicture方法。我们需要花很长的时间去理解processPicture方法中的技术细节是在做什么(如何做图片裁剪,如何做放大图片操作)。然而,过多的技术细节展现对于了解业务逻辑没有任何帮助,反而会使人凌乱。使得代码不那么清晰。
再看修改后,我们将技术细节封装到了个新的类 FileUtil中,同时将图片缩放的大小这个业务逻辑放在AccountService中。这样,我们在读AccountService的时候,就可以更清晰的了解业务逻辑。
大脑的短期记忆无法一次容纳约7个以上的记忆项目 —— 《金字塔原理》
当一个开发人员去熟悉一个新模块的时候,首先会从整体去了解这个新模块提供的功能,然后再会去看每个功能具体的实现细节是什么。就像画画一样,一开始要画草图去确定各个部分画什么东西,然后再慢慢补充细节。所谓细节都是魔鬼,如果一开始就进入细节,就容易陷入到细节中去,很难看到全局并找到重点。
因此,从认知的角度,我们其实一开始关注的是业务逻辑,当了解到我们关心的业务逻辑后,才会去做技术细节的展开。因此,我们可以把业务逻辑的代码和具体的技术实现进行分离,这样,可以帮助我们通过整体-细节的方式进行学习。
我们可以通过问问题的方式来区分现在做的这个事儿是业务逻辑还是技术细节。
分清了业务逻辑和技术细节后,我们需要把他们进行分区。
将业务逻辑放到一个package中,将技术细节放到一个package中。
业务逻辑集中在service层中。业务无关的技术细节按照功能放到其他层。
- Controller
- TaskRequest
- TaskController
- TaskCreateRequest
- ProjectController
- ProjectResponse/ProjectVO
- Service
- Task
- TaskService
- Task
- Project
- ProjectService
- Project
- Picture
- Picture
- PictureService
- Repository
- Processor
- Utils
- EdocUtils
- PicutreUtils
- Config
- LogConfig
- HealthCheckConfig
- adapter
- inbound(外部对本服务的访问,UI,controller)
- rest
- resources
- TaskRequest
- rpc
- outbound(本服务访问三方系统,db,中间件,服务)
- persist
- application(用例,用例可以调用多个domain里面的service)
- usecase
- TaskUseCase
- ProjectUseCase
- domain(业务模型)
- tasks
- Task
- TaskService
- projects
- Project
- ProjectService
- config(配置信息)
- LogConfig
- HealthCheckConfig