精华内容
下载资源
问答
  • 代码分层

    2019-08-09 11:57:15
    说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当成透传了,这...

    背景

    说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当成透传了,这其实是很多人开发代码都没有注意到的地方,反正功能也能用,至于放哪无所谓呗。这样往往造成后面代码无法复用,层级关系混乱,对后续代码的维护非常麻烦。

    的确在这些人眼中分层只是一个形式,前辈们的代码这么写的,其他项目代码这么写的,那么我也这么跟着写。但是在真正的团队开发中每个人的习惯都不同,写出来的代码必然带着自己的标签,有的人习惯controller写大量的业务逻辑,有的人习惯在service中之间调用远程服务,这样就导致了每个人的开发代码风格完全不同,后续其他人修改的时候,一看,我靠这个人写的代码和我平常的习惯完全不同,修改的时候到底是按着自己以前的习惯改,还是跟着前辈们走,这又是个艰难的选择,选择一旦有偏差,你的后辈又维护你的代码的时候,恐怕就要骂人了。

    所以一个好的应用分层需要具备以下几点:
    方便后续代码进行维护扩展;
    分层的效果需要让整个团队都接受;
    各个层职责边界清晰

    如何进行分层

    阿里规范

    在阿里的编码规范中约束的分层如下:
    在这里插入图片描述
    开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行 网关安全控制、流量控制等。

    终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。

    Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

    Service 层:相对具体的业务逻辑服务层。

    Manager 层:通用业务处理层,它有如下特征:1. 对第三方平台封装的层,预处理返回结果及转化异常信息;2. 对Service层通用能力的下沉,如缓存方案、中间件通用处理;3. 与DAO层交互,对多个DAO的组合复用。

    DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 进行数据交互。

    阿里巴巴规约中的分层比较清晰简单明了,但是描述得还是过于简单了,以及service层和manager层有很多同学还是有点分不清楚之间的关系,就导致了很多项目中根本没有Manager层的存在。下面介绍一下具体业务中应该如何实现分层。

    优化分层

    从我们的业务开发中总结了一个较为的理想模型,这里要先说明一下由于我们的rpc框架选用的是thrift可能会比其他的一些rpc框架例如dubbo会多出一层,作用和controller层类似
    在这里插入图片描述
    最上层controller和TService是我们阿里分层规范里面的第一层:轻业务逻辑,参数校验,异常兜底。通常这种接口可以轻易更换接口类型,所以业务逻辑必须要轻,甚至不做具体逻辑。

    Service:业务层,复用性较低,这里推荐每一个controller方法都得对应一个service,不要把业务编排放在controller中去做,为什么呢?如果我们把业务编排放在controller层去做的话,如果以后我们要接入thrift,我们这里又需要把业务编排在做一次,这样会导致我们每接入一个入口层这个代码都得重新复制一份如下图所示:
    在这里插入图片描述
    这样大量的重复工作必定会导致我们开发效率下降,所以我们需要把业务编排逻辑都得放进service中去做:
    在这里插入图片描述
    Mannager:可复用逻辑层。这里的Mannager可以是单个服务的,比如我们的cache,mq等等,当然也可以是复合的,当你需要调用多个Mannager的时候,这个可以合为一个Mannager,比如逻辑上的连表查询等。如果是httpMannager或rpcMannager需要在这一层做一些数据转换

    DAO:数据库访问层。主要负责“操作数据库的某张表,映射到某个java对象”,dao应该只允许自己的Service访问,其他Service要访问我的数据必须通过对应的Service。

    分层领域模型的转换

    在阿里巴巴编码规约中列举了下面几个领域模型规约:

    DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
    DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
    BO(Business Object):业务对象。由Service层输出的封装业务逻辑的对象。
    AO(Application Object):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
    VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
    Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。

    在这里插入图片描述

    每一个层基本都自己对应的领域模型,这样就导致了有些人过于追求每一层都是用自己的领域模型,这样就导致了一个对象可能会出现3次甚至4次转换在一次请求中,当返回的时候同样也会出现3-4次转换,这样有可能一次完整的请求-返回会出现很多次对象转换。如果在开发中真的按照这么来,恐怕就别写其他的了,一天就光写这个重复无用的逻辑算了吧。
    所以我们得采取一个折中的方案:

    1、允许Service/Manager可以操作数据领域模型,对于这个层级来说,本来自己做的工作也是做的是业务逻辑处理和数据组装。

    2、Controller/TService层的领域模型不允许传入DAO层,这样就不符合职责划分了。

    3、同理,不允许DAO层的数据传入到Controller/TService。

    在这里插入图片描述

    展开全文
  • 代码分层设计

    2019-03-31 21:04:42
    今天在看源码时,代码分层设计深深吸引了我。就拿一个简单的输出举例。输出一般有三种方式stdout、socket(网络打印调试信息)、log日志。我们如何将它们揉在一块呢。 分析: 如下是它的结构图 接下...

    目录

    分层设计:    

    分析:

    使用:

     拓展:


    分层设计:    

          今天在看源码时,代码分层设计深深吸引了我。就拿一个简单的输出举例。输出一般有三种方式stdout、socket(网络打印调试信息)、log日志。我们如何将它们揉在一块呢。

    分析:

    如下是它的结构图

                                                 

    接下来我们从外到内进行分析:这是项目布局

                                                      

           这里面有两种打印方式,一种是stdout,一种是socket网络打印,而debug_manager.c就是用来管理它两的,是不是发现这样写很容易扩展呢,自己还可以添加一个localog.c(日志)。

    这与平常最大的不同是有debug_manager.c文件,我们对它进行分析分析。

    typedef struct DebugOpr {
    	char *name;                  /*打印类型 stdout、netprint*/
    	int isCanUse;             
    	int (*DebugInit)(void);      /* 调试模块的初始化函数 */
    	int (*DebugExit)(void);      /* 退出函数 */
    	int (*DebugPrint)(char *strData);  /* 输出函数 */
    	struct DebugOpr *ptNext;
    }T_DebugOpr, *PT_DebugOpr;

      stdout.c文件

    static int StdoutDebugPrint(char *strData)
    {
    	/* 直接把输出信息用printf打印出来 */
    	printf("%s", strData);
    	return strlen(strData);	
    }
    
    static T_DebugOpr g_tStdoutDbgOpr = {
    	.name       = "stdout",
    	.isCanUse   = 1,                 /* 1表示将使用它来输出调试信息 */
    	.DebugPrint = StdoutDebugPrint,  /* 打印函数 */
    };
    
    int StdoutInit(void)
    {
    	return RegisterDebugOpr(&g_tStdoutDbgOpr);
    }
    

     netprint.c文件

    static int NetDbgInit(void)
    {
    	/* socket初始化 */
    	int iRet;
    	
    	g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
    	if (-1 == g_iSocketServer)
    	{
    		printf("socket error!\n");
    		return -1;
    	}
    
    	g_tSocketServerAddr.sin_family      = AF_INET;
    	g_tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     	g_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    	memset(g_tSocketServerAddr.sin_zero, 0, 8);
    	
    	iRet = bind(g_iSocketServer, (const struct sockaddr *)&g_tSocketServerAddr, sizeof(struct sockaddr));
    	if (-1 == iRet)
    	{
    		printf("bind error!\n");
    		return -1;
    	}
    
    	g_pcNetPrintBuf = malloc(PRINT_BUF_SIZE);
    	if (NULL == g_pcNetPrintBuf)
    	{
    		close(g_iSocketServer);
    		return -1;
    	}
    
    
    	/* 创建netprint发送线程: 它用来发送打印信息给客户端 */
    	pthread_create(&g_tSendTreadID, NULL, NetDbgSendTreadFunction, NULL);			
    	
    	/* 创建netprint接收线否: 用来接收控制信息,比如修改打印级别,打开/关闭打印 */
    	pthread_create(&g_tRecvTreadID, NULL, NetDbgRecvTreadFunction, NULL);			
    
    	return 0;	
    }
    static int NetDbgExit(void)
    {
    	/* 关闭socket,... */
    	close(g_iSocketServer);
    	free(g_pcNetPrintBuf);
    	return 0;
    }
    static int NetDbgPrint(char *strData)
    {
    	/* 把数据放入环形缓冲区 */
    	int i;
    	
    	for (i = 0; i < strlen(strData); i++)
    	{
    		if (0 != PutData(strData[i]))
    			break;
    	}
    	
    	/* 如果已经有客户端连接了, 就把数据通过网络发送给客户端 */
    	/* 唤醒netprint的发送线程 */
    	pthread_mutex_lock(&g_tNetDbgSendMutex);
    	pthread_cond_signal(&g_tNetDbgSendConVar);
    	pthread_mutex_unlock(&g_tNetDbgSendMutex);
    
    	return i;
    	
    }
    static T_DebugOpr g_tNetDbgOpr = {
    	.name       = "netprint",
    	.isCanUse   = 1,
    	.DebugInit  = NetDbgInit,
    	.DebugExit  = NetDbgExit,
    	.DebugPrint = NetDbgPrint,
    };
    int NetPrintInit(void)
    {
    	return RegisterDebugOpr(&g_tNetDbgOpr);
    }

    使用:

    char strFileName[256];
    memset(strFileName,0,sizeof(strFileName));
    strcpy(strFileName,"hello");
    DebugInit();
    //在需要打印的地方,输上这句话就可以用了
    DBG_PRINTF("<7>hi %s error!\n", strFileName);

     拓展:

     有没有一种疑问,这么写的代码咋用makefile写呢,模块里面也有一个makefile文件。

    AS		= $(CROSS_COMPILE)as   #编译选项
    LD		= $(CROSS_COMPILE)ld   #链接选项
    CC		= $(CROSS_COMPILE)gcc
    CPP		= $(CC) -E             
    
    STRIP		= $(CROSS_COMPILE)strip
    OBJCOPY		= $(CROSS_COMPILE)objcopy
    OBJDUMP		= $(CROSS_COMPILE)objdump
    
    export AS LD CC CPP 
    export STRIP OBJCOPY OBJDUMP
    
    CFLAGS := -Wall -Werror -O2 -g                  
    CFLAGS += -I $(shell pwd)/include               #头文件
    
    LDFLAGS := -lm - -lpthread                      #库文件
    
    export CFLAGS LDFLAGS
    
    TOPDIR := $(shell pwd)                          #顶层目录
    export TOPDIR
    
    TARGET := Debug                                 #目标名字
    
    obj-y += main.o                                 
    obj-y += debug/
    all : 
    	make -C ./ -f $(TOPDIR)/Makefile.build
    	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
    clean:
    	rm -f $(shell find -name "*.o")
    	rm -f $(TARGET)
    distclean:
    	rm -f $(shell find -name "*.o")
    	rm -f $(shell find -name "*.d")
    	rm -f $(TARGET)

      解析:-Wall -Werror -O2 -g (Werror就是把warning 当error处理)

    -Wall:选项可以打印出编译时所有的错误或者警告信息。这个选项很容易被遗忘,编译的时候,没有错误或者警告提示,以为自己的程序很完美,其实,里面有可能隐藏着许多陷阱。变量没有初始化,类型不匹配,或者类型转换错误等警告提示需要重点注意,错误就隐藏在这些代码里面。没有使用的变量也需要注意,去掉无用的代码,让整个程序显得干净一点。下次写Makefile的时候,一定加-Wall编译选项。

    -O0: 表示编译时没有优化。

    -O1: 表示编译时使用默认优化。

    -O2: 表示编译时使用二级优化。

    -O3: 表示编译时使用最高级优化。

    展开全文
  • Laravel应用代码分层

    2020-12-07 18:14:15
    应用代码分层 我们在写应用里的代码时根据代码负责的不同任务讲其分为五大块Controller, Repository, Service, Model, View。 Model 数据模型, 数据模型面向的是数据层,在这里我们只关心数据表的问题,在 Model ...

    应用代码分层

    我们在写应用里的代码时根据代码负责的不同任务讲其分为五大块Controller, Repository, Service, Model, View

    • Model 数据模型, 数据模型面向的是数据层,在这里我们只关心数据表的问题,在 Model 中应该只定义数据与对象映射相关的属性和方法如:表名、主键名、是否让 laravel 管理时间字段等属性,以及模型关联、查询作用域等方法。其他与数据表无关的业务逻辑都不应出现在这里。
    • Repository 数据逻辑访问层,由它来对接 Model 层,理论上有一个 Model 就会有一个相应的Repository,除了做最基础的数据访问外与数据相关的逻辑也放在这里,如果一个相对复杂的数据应用到了Repository对应的Model外其他Model的数据,不要直接去访问相关Model,应该由Repository调用相关ModelRepositoryRepository是具体interface的实现,比如做订单相关的业务,应该有OrderRepositoryInterface定义Order数据交互流程中必须要实现的方法然后由OrderRepository去具体实现,之后将OrderRepositoryInterfaceOrderRepository注册到服务容器中,解析时直接使用OrderRepositoryInterface解析出具体实现,这样消费层既不需要关心数据来自哪里是Mysql还是MongoDB,也给项目提供了足够的灵活性。当数据来源从Mysql更改为MongoDB后,我们只需要重新写一个实现类OrderMongoRepository然后将服务容器里OrderRepositoryInterface的实现切换成OrderMongoRepository就好,消费层完全不需要改动。
      (Repository 是我之前一直觉得在程序设计中特别多余而现在觉得特别重要的一个 Layer,之前在 Service 中揉进去了 Repository 的职能,后续会把相关的 Example 也做一下修改)
    • Service 项目中除了数据的 CRUD 还会有图片上传、请求外部 API 获取数据、发送邮件等等其他这些功能,这些功能应该定义在Service层。
    • Controller 控制器,面向的对象是一个完整的页面或者是接口,其主要职责是作为接收请求和发送响应的中控程序,通过调度项目中的 Service、 Repository 等来完成请求、组合响应数据,并通过页面响应或者接口数据的形式将响应返回给客户端。
    • View 视图, 负责渲染 HTML 响应,使用 Laravel 自带的 blade 模版引擎,并且应该尽量减少 PHP 代码。

    总结:所以如果一个请求对应的业务逻辑相当复杂,我们可以通过 Controller 方法调用多个 Service 方法(单一逻辑)来完成这个复杂的逻辑,在 Service 方法中我们通过多个 Model 操作来实现更新、获取数据。通过这种原则来对复杂逻辑进行解耦。

    我们通过看两个例子来更好的理解代码如何分层:

    代码示例

    Example 1: 应该在哪里写 SQL 操作

    如果一个查询在项目中只会用到一次,那么我们直接在 Controller 方法里写这个查询就很好, 举例来说你想查出所有的管理员用户:

    $admins = User::where('type', 'admin')->get();
    

    现在假设你需要在不止一个 Controller 方法中用到这个查询, 你可以在UserRepository类里包装对User模型的访问:

    class UserRepository
    {
        public function getAlladmins()
        {
            return User::where('type', 'admin')->get();
        }
    }
    

    现在你可以在用到UserRepositoryController中通过依赖注入UserRepository, 然后通过这个 UserRepository 方法获取所有管理员用户:

    //Controller
    public function __construct(UserRepository $UserRepository)
    {
        $this->UserRepository = $UserRepository;
    }
    //Controller action
    public function index()
    {
        $admins = $this->UserRepository->getAllAdmins();
    }
    

    最后,假设你还需要一个查询来计算管理员的数量, 可以在把这个查询封装到 UserService 的一个方法里:

    public function getAdminNum()
    {
        return User::where('type', 'admin')->count();
    }
    

    这样写是 OK 的,但是你可能已经注意到了User::where('type', 'admin')这个片段在getAllAdmins这个查询里也有用到,所以我们可以用查询作用域来改进它(查询作用域也会让你的代码可读性变得更高):

    //UserModel
    public function scopeAdmins($query)
    {
       return $query->where('type', 'admin');
    }
    

    然后在 UserRepository 里我们可以向下面这样重写它的查询:

    //UserRepository
    public function getAllAdmins()
    {
        return User:admins()->get();
    }
    
    public function getAdminNum()
    {
        return User::admins()->count();
    }
    

    就像上面说的那样在 Model 中我们应该撇开业务逻辑,面向数据表进行抽象,只定义表相关的属性、模型关联和查询作用域, 具体怎么应用 Model 中定义的这些内容, 那是 Controller 层和 Service 层要关心的事情。

    Example2: 通过 Service 类提高代码复用率

    我们在做 CMS 系统管理内容时, 通常都会涉及到文章的上下线,置顶等操作。假设在系统中我们会用两个数据表分别存储文章和置顶, 在项目中对应两个 Model: Article 和 TopArticle。

    假设我们需要实现下线文章的功能,如果文章是置顶文章,还要把文章取消置顶,我们在 ArticleService 里包装这个操作:

    public function setArticleOffline(Article $article)
    {
       if ($article->is_top == 1) {//如果是置顶文章需要将文章取消置顶
           $this->cancelTopArticle($article);
       }
       $article->status = Article::STATUS_OFFLINE;
       $article->offline_time = date('Y-m-d H:i:s');
       $article->save();
    
       return true;
    }
    
    /**
     * 取消文章的置顶
     * @param \App\Models\Article $article
     */
    public function cancelTopArticle($article)
    {
        if (TopArticle::specificArticle($article->id)->count()) {
            //删除置顶表里的记录(待上线的置顶文章上线后置顶表中才能有相应的记录)
            TopArticle::specificArticle($article->id)->delete();
        }
        //将文章的is_top字段置为0
        $article->is_top = 0;
        $article->save();
    
        return true;
    }
    

    在 Controller 的 Action 里我们只需要负责验证接收来的参数,查询文章对象传递给 Service 来做下线操作, 这里之所以把取消置顶也单独提取出来是因为通常 CMS 系统里还有单独的文章取消置顶功能。

    //ArticleController
    public function setArticleOff(Request $request, ArticleService $artService)
    {
        ...//表单验证
    
        $article = Article::find($request->get('article_id'));
        $this->articleService->setArticleOffline($article);
    
        ...
    }
    

    除了上面讲到的主动下线文章的功能, 一般的文章系统里还会有定时上下线文章的功能, 因为我们在 ArticleService 里包装了下线文章的功能, 所以在设置定时任务时就可以再次调用 setArticleOffline 方法来完成文章下线的功能:

    //App\Console\Commands\ArticleCommand
    public function __construct(ArticleService $articleService)
    {
       $this->articleService = $articleService;
       parent::__construct();
    }
    
    public function handle()
    {
       $operation = $this->argument('operation');
       switch ($operation) {
           case 'offline':
            $this->setOffline();
            break;
        default:
            break;
    }
    
    public function setOffline()
    {
       ......//前置条件查出要下线的文章
       $this>articleService>setArticleOffline($article);
       ......
    }
    

    上面两个例子简单说明了我们把代码分层后每个层里应该写什么类型的程序,以及代码分层后在可读性、耦合性、维护成本等方面带来的收益。

    展开全文
  • Java Web应用的代码分层最佳实践。

    千次阅读 2018-05-07 10:02:47
    代码分层,对于任何一个Java Web开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。从另外一个角度来看,好的代码分层...

    代码分层,对于任何一个Java Web开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。

    从另外一个角度来看,好的代码分层架构,应该是可以很好的匹配上单一职责原则的。这样就可以降低层与层之间的依赖,还能最大程度的复用各层的逻辑。本文就来介绍下Java Web项目的代码到底应该如何分层。

    三层架构

    在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。这也是Java Web中重要的三层架构中的三个层次。区分层次的目的即为了“高内聚低耦合”的思想。

    所谓三层体系结构,是在客户端与数据库之间加入了一个“中间层”,也叫组件层。这里所说的三层体系,不是指物理上的三层,不是简单地放置三台机器就是三层体系结构,也不仅仅有B/S应用才是三层体系结构,三层是指逻辑上的三层,即把这三个层放置到一台机器上。

    数据访问层

    主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。

    业务逻辑层

    主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。

    界面层

    主要表示WEB方式。如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。

    三层架构与MVC的区别

    MVC(模型Model-视图View-控制器Controller)是一种架构模式,可以用它来创建在域对象和UI表示层对象之间的区分。

    MVC

    同样是架构级别的,相同的地方在于他们都有一个表现层,但是他们不同的地方在于其他的两个层。

    在三层架构中没有定义Controller的概念。这是最不同的地方。而MVC也没有把业务的逻辑访问看成两个层,这是采用三层架构或MVC搭建程序最主要的区别。

    3

    更加细致的分层

    随着网站的用户量的不断提升,系统架构也在不断的调整。有时候,随着业务越来越复杂,有时候三层架构好像不够用了。比如,我们的应用除了要给用户提供页面访问以外,还需要提供一些开放接口,供外部系统调用。这个接口既不属于界面层,也不应该属于业务逻辑层,因为他还可能包含一些和业务逻辑无关的处理,如权限控制、流量控制等。

    还有,随着微服务的盛行,我们应用中可能要依赖很多外部接口或第三方平台。这部分代码放下业务逻辑层和数据访问层也都不合适。

    所以,渐渐的,在三层架构的基础上,系统架构的分层变得更加复杂了。也正是因为复杂,就非常考验架构设计能力,因为层次划分的不好,很可能会影响后面的开发,给代码维护带来很大的困难。

    下图,是阿里巴巴(参考《阿里巴巴Java开发手册》)提倡的应用分层结构:

    ceng

    开放接口层

    可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行网关安全控制、流量控制等。

    终端显示层

    各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。

    Web 层

    主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

    Service 层

    相对具体的业务逻辑服务层。

    Manager 层

    通用业务处理层,它有如下特征: 1) 对第三方平台封装的层,预处理返回结果及转化异常信息; 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理; 3) 与 DAO 层交互,对多个 DAO 的组合复用。

    DAO 层

    数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。

    外部接口或第三方平台

    包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。

    事务处理

    在了解了分层之后,我们再来看一下写Java Web代码的时候,大家比较关心的一个问题,那就是涉及到数据库操作的时候,事务处理应该在哪一层控制呢?

    关于这个问题,仁者见仁,智者见智。作者认为,事务处理应该放在Service层和Manager层。

    DAO层不应该有事务,应该只是很纯的 CRUD 等比较通用的数据访问方法。一个DAO应该只处理和自己相关的操作,不要有任何组合。组合的事情交给上层。

    Service层和Manager层一般会组合多个DAO的CRUD操作,例如:在注册一个用户的时候需要往日志表里 INSERT 日志,那么就在 Service 层构造事务,在该事务中调用 Dao 层的 User.Insert () 与 Log.Insert ()。

    异常处理

    异常处理是Java中比较重要的一个话题,在《Effective Java》中有很多关于异常处理的最佳实践,这里不详细介绍了,本文主要简单说一下在应用代码分层之后,各个层次之间的异常应该如何处理,是自己捕获,还是向上一层抛出。

    首先,每一层都是可能发生异常的。由于每一层的职责都不通,处理方式也可能有差别。

    DAO层

    在 DAO 层,产生的异常类型可能有很多,可能是SQL相关的异常,也可能是数据库连接相关的异常。

    这一层的处理方式可以简单一点,直接try-catch(Exception),然后封装成DAOException抛给上一层。这一层一般不需要打印日志,交给Service或者Manager层来打印。

    try{
        CRUD
    }catch(Exception e){
        throw new DAOException(e);
    }

    Manager/Service

    首先,对于DAO层抛上来的异常一定要捕获的,并且记录日志打印现场。

    但是值得注意的是,如果是需要事务控制的方法,要注意捕获到异常之后再向上抛一个新的异常,如 TransactionRolledbackException,否则事务无法回滚。

    这两层发生的异常可以根据情况决定是继续向上抛还是自己处理掉。如果是自己可以处理的异常,就捕获,打日志,然后通过ErrorCode等方式返回给上一层。如果是自己无法处理或者不知道该如何处理的异常,就直接抛给上一层来处理。

    Web

    首先,可以明确的一点:Web层不应该再往外抛异常,因为这一层一旦抛异常,就可能会导致用户跳转到不友好的错误页面甚至看到错误信息等。

    如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。

    开放接口层

    这一层和Web层一样,不可以抛出异常。一般通过ErrorCode和ErrorMessage反馈给外部调用方。

    这一层,要自己处理好所有的异常,定义好ErrorCode,并记录好日志,便于日后排查问题。

    总结

    本文主要介绍了Java Web项目中代码分层的方案,通过分层之后可以使没一层更加专注,解除耦合。并简单介绍了一下分层之后的事务处理和异常处理的逻辑。



    展开全文
  • 代码分层思考

    2019-10-01 11:41:44
    优秀的代码都是如何分层的? 背景 说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比...
  • 一、分层策略MVC模式与代码分层策略,MVC全名是ModelViewController即模型-视图-控制器,作为一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性...
  • 一、开发模式 model1 model2 二、代码分层结构 mvc、控制层作用
  • 转载自 Java Web应用的代码分层最佳实践代码分层,对于任何一个Java Web开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和...
  • 在联系代码分层的查询练习时遇到的问题 Exception in thread "main" java.lang.NullPointerException at dao.impl.SelectInfoDaoImpl.seleectInfoByempno(SelectInfoDaoImpl.java:30) at service.impl....
  • MVC模式与代码分层策略,MVC全名是ModelViewController即模型-视图-控制器,作为一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及...
  • 作者:咖啡拿铁代码分层,对于任何一个Java开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。从另外一个角度来看,好...
  • 代码分层作用

    2018-02-02 16:40:00
    在简单的系统里面,分层是这样的 controller <-> model <-> storage(sql、nosql、cache) 所有的业务逻辑都在model上 现在讨论一个常见的场景,用户下订单要买点东西,这个业务逻辑涉及到的model类有...
  • 如何合理的设计代码分层,论代码分层的设计之道 。 分层思想,是应用系统最常见的一种架构模式,我们会将系统横向切割,根据业务职责划分。 MVC 三层架构就是非常典型架构模式,划分的目的是规划软件系统的逻辑...
  • 优秀的代码分层

    2019-08-16 19:15:55
    作者:咖啡拿铁 ...看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当成透传了,这其实是很多人开发代码都没有注意到的地方,反正功能也能用,至于...
  • 代码分层,对于任何一个Java开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。 从另外一个角度来看,好的代码分层...
  • 项目包名和分层都是按照代码来分的,不是按照业务。包名是:组织名称+代码分层
  • 出处:http://www.cppblog.com/kevinlynx/archive/2011/04/05/143430.html浅谈代码分层:构建模块化程序Author:Kevin LynxDate:4.4.2011Contact:kevinlynx at gmail dot com模块化的程序是怎样的程序?我们可以说一...
  • 一、分层策略MVC模式与代码分层策略,MVC全名是ModelViewController即模型-视图-控制器,作为一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性...
  • 1、背景 说起应用分层,大部分人都会认为这个不是很简单...的确在这些人眼中分层只是一个形式,前辈们的代码这么写的,其他项目代码这么写的,那么我也这么跟着写。但是在真正的团队开发中每个人的习惯都不同,写出来

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,321
精华内容 3,328
关键字:

代码分层