Cloud-Link's blog Cloud-Link's blog
首页
  • 开发资源
  • 人员动态
  • 新人训练
  • 奖惩通报
  • 通讯录
项目资产
  • 快速指南
  • 后端框架
  • 前端框架
  • 业务模块
  • 基础理论
    • 前端
    • 后端
    • 数据库
    • 工具类
  • 常用
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
关于
首页
  • 开发资源
  • 人员动态
  • 新人训练
  • 奖惩通报
  • 通讯录
项目资产
  • 快速指南
  • 后端框架
  • 前端框架
  • 业务模块
  • 基础理论
    • 前端
    • 后端
    • 数据库
    • 工具类
  • 常用
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
关于
  • 指南(guide)

    • 框架介绍
    • 模块化思想
      • 概念说明
      • 模块化
        • 约定
        • 独立
    • 快速上手
    • 创建模块
    • 安装模块
    • 编码规范
    • 开发规范
  • 后端框架(framework)

  • 前端框架(frendEnd)

  • 业务模块(modules)

  • 基础理论(fundamentals)

  • 开发框架
  • 指南(guide)
2021-09-15

模块化思想

# 概念说明

模块: 按照业务或者功能的不同拆分的系统模块,比如:后台管理模块Cms、任务调度模块Quartz、人事档案模块PersonnelFiles等

项目: 项目可以理解为一个完整的产品,它是由至少一个模块组合而成的,比如:xxxOA 系统(包含后台管理模块Cms、任务调度模块Quartz、人事档案模块PersonnelFiles等模块)、xxx CMS 系统(包含后台管理模块Cms、任务调度模块Quartz、新闻管理模块News等模块组成)

# 模块化

模块化是根据业务领域(领域驱动中的领域),将业务拆分成不同的模块,以此降低软件的复杂度,提高代码的复用。

DyEnd的设定不仅仅是一个开发框架,更是一个完整的 开发平台,包括以下特点:

1、约定:每个模块都需遵守统一的约定和规则

2、独立:每个模块要尽量做到独立,模块之间尽量避免强依赖,尽量通过设计模式来解决依赖

3、灵活:任意个模块可灵活的集成打包部署

4、便捷:模块集成、打包、升级,要做到简单、方便、傻瓜式

5、全面:不仅仅后端模块化,前端也要模块化

6、维护:每个模块的代码需要单独的仓库维护,且要能够方便的管理

7、专注:开发人员只需关心自己所负责的模块

  • # 约定

提示

所谓约定,是指模块需遵守统一的约定与规则。

有一种软件设计范式,叫约定优于配置(convention over configuration),也称作按约定编程,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。我们来看几个例子:

如 ASP.NET CORE MVC:

1、控制器都已Controller结尾,并且都放在Controllers目录下

2、视图放在对应的控制名称的目录下,且都放在Views目录下

3、配置信息都放在 appsettings.json 文件中

如 Spring Boot:

1、Maven 的目录结构。默认有 resources 文件夹,存放资源配置文件。src-main-resources,src-main-java。默认的编译生成的类都在 targe 文件夹下面。

2、spring boot 默认的配置文件必须是,也只能是 application.命名的 yml 文件或者 properties 文件,且唯一

3、application.yml 中默认属性。数据库连接信息必须是以 spring: datasource: 为前缀;多环境配置。该属性可以根据运行环境自动读取不同的配置文件;端口号、请求路径等

DyEnd 中有很多地方也是采用了这种设计范式,最明显的应该便是模块的项目结构了。我们先来看一看一个模块的项目结构,以任务调度模块为例:

上图是框架对项目结构的约定:

build : 该目录用于存放与项目编译打包有关的配置文件,比如 module.build.targets,用于生成模块描述信息文件

Application : 应用服务层,用于存放应用服务接口、实现、CRUD 相关的模型等,所有业务逻辑都会放在这个库里面

Domain : 领域层,用于存放实体、仓储接口、值类型、查询模型等数据,且按照实体来放到不同的目录下,实体全部以Entity结尾

Infrastructure : 基础设施层,用于存放仓储的实现、模块配置类等

Quartz : 任务调度层,用于存放任务调度有关的 Job 类

Web : 应用层,存放控制器、模型验证、模块初始化有关的信息等

WebHost : 应用启动器,只是用于集成模块和启动应用

框架与命名有关的约定:

实体以Entity结尾

应用服务接口和实现以Service结尾

模型映射关系在_MapperConfig.cs类中设置,且_MapperConfig.cs必须根据所属服务放在对应服务目录

模型验证类放在 Web 的Validators目录中,并且必须以Validator结尾

配置文件放在 WebHost 中的 config 目录下

遵守这些约定,团队协作代码结构清晰,代码维护也能快速定位~

  • # 独立

提示

所谓独立,是指模块之间需要尽量做到低耦合。

当业务较复杂时,模块之间总会避免不了有相互依赖,这里的依赖,大部分都是一个模块用到了另一个模块的某个实体或者仓储,比如大部分实体都会有创建人属性,当查询实体列表时,往往会返回创建人的姓名,此时就要用到Cms模块中的AccountEntity实体,这种情况还好,因为框架本身支持跨模块的表连接查询,而且Cms本身就是核心模块,基本上所有项目都会安装它。

我们再来看另外一种依赖情况,比如有一个人事档案模块,该模块包含一个员工信息实体EmployeeEntity

/// <summary>
/// 员工信息
/// </summary>
public class EmployeeEntity : EntityBase
{
    public string Name { get; set; }

    public string Sex { get; set; }

    public int Age { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11

现在需要做一个会议管理模块,包含一张会议统计信息实体MeetingStatisticsEntity,该实体专门用于保存员工的会议统计信息

/// <summary>
/// 会议统计信息
/// </summary>
public class MeetingStatisticsEntity : EntityBase
{
    /// <summary>
    /// 员工编号
    /// </summary>
    public Guid EmployeeId { get; set; }

    /// <summary>
    /// 待参加数量
    /// </summary>
    public int WaitCount { get; set; }

    /// <summary>
    /// 已参加数量
    /// </summary>
    public int AttendedCount { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面两个实体是一对一的关系,每个员工都有一条相关的统计信息数据,那么问题来了,员工对应的会议统计信息应该什么时候创建呢?

1、创建员工信息时创建

如果我们选择在创建员工信息时创建,这样就需要在人事档案模块中依赖会议管理模块,可明明是会议管理依赖了人事档案模块才对,这样就产生了强依赖,而且还是循环依赖。假如现在要做项目管理模块,也包含了类似的需求,那岂不是还要修改人事档案模块中创建员工部分的代码,不仅又多了依赖关系,万一改出了 bug,会影响所有使用人事档案模块的模块~

2、使用观察者模式创建

对于上面类似的需求,推荐采用观察者模式来解决,定义针对EmployeeEntity的观察者接口,本框架已集成了针对实体信息变更的观察者接口及实现,用法如下:

首先,在EmployeeService.cs中添加如下代码

//注入观察者处理器接口
private readonly IEntityObserverHandler _observerHandler;

//添加
public async Task<IResultModel> Add(EmployeeAddModel model)
{
    if (await _repository.Exists(model.Name))
        return ResultModel.HasExists;

    var entity = _mapper.Map<EmployeeEntity>(model);

    var result = await _repository.AddAsync(entity);
    if (result)
    {
        //执行观察者方法,该方法内会执行所有实现了IEntityObserver<EmployeeEntity>接口的观察者
        await _observerHandler.Add<EmployeeEntity>(entity.Id);
    }

    return ResultModel.Result(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

其次在需要用到员工信息的模块中,实现员工实体的观察者,比如

public class EmployeeObserver : IEntityObserver<EmployeeEntity>
{
    public Task Add(dynamic id)
    {
        throw new NotImplementedException();
    }

    public Task Update(dynamic id)
    {
        throw new NotImplementedException();
    }

    public Task Delete(dynamic id)
    {
        throw new NotImplementedException();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

系统启动时,会自动加载所有的观察者并使用单例模式注入到容器中,所以,在观察者类中,您可以注入任何您想要的服务。当有新的模块也有类似需求时,只要定义自己的EmployeeObserver就行了~

因为目前我只遇到了这一种情况,所以也没有其他依赖的例子可以讲了,不过重点是理解其中的思想,善用设计模式来解决平时遇到的一些问题

注意

上面的例子也是有约定的,因为一个模块可能会定义多个实体的观察者,所以为了统一规范,所有实体的观察者实现,放到与之有关的服务中,比如MeetingStatisticsEntity对应的EmployeeObserver,需要放在MeetingStatisticsService目录中

框架介绍
快速上手

← 框架介绍 快速上手→

Copyright © 2021-2022 用技术改变世界 | Tungray Cloud-Link
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×