软件设计不是CRUD(8):低耦合模块设计实战——组织机构模块(下)

2023-12-13 22:21:23

接上文《软件设计不是CRUD(7):低耦合模块设计实战——组织机构模块(中)

5、某项目研发团队进行扩展

上文中我们介绍了如何研发一个具有较低耦合强度的组织机构模块(包括模块的SDK和模块的默认本地数据库实现),接着我们就可以在一个应用程序中使用这个组织机构模块了。实际上应用程序研发团队不只是使用这个模块,研发团队还在应用程序开发时收到了客户方的新需求。客户表示需要按照自身情况对组织机构模块功能做如下修改:

  • 增加一种组织机构类型,这种组织机构类型可以关联下级组织机构,但是下级组织机构只能是同一类型的组织机构,不能关联任何人员信息。
  • 修改目前系统中已存在的组织机构的字段信息,增加“有效时间”、“经办方式”、“预算上限”这几个特定的业务字段。
  • 新增一种项目中特定的人员类型和组织机构的关联。

好消息是,这些需求的变化并没有超过组织机构模块的业务维度范畴——组织机构类型的变化和组织机构于人员关联方式的变化,所以需求变化是在设计可控的范畴内。那么我们来看一下组织机构模块是如何支持二次开发满足以上三点需求变化的。

5.1、在应用系统中使用默认的组织机构模块实现

除非项目研发团队想全部自行实现组织机构模块,否则就需要首先在自己的应用系统中引入组织机构模块的默认实现(前者情况只需要引入simple-org-sdk即可)。

  • 应用程序Spring-Boot中的pom.xml文件引入组织机构模块的默认实现:
// ......
<dependencies>
	// ......
	<!-- 引入组织机构的默认实现 -->
	<dependency>
		<groupId>simple-org</groupId>
		<artifactId>simple-org-local-starter</artifactId>
		<version>0.0.2-SNAPSHOT</version>
	</dependency>
	// ......
</dependencies>
// ......

从这个示例场景来看,项目研发团队描述的二次开发需求完全可以在默认实现的基础上进行二次开发,而不需要将整个组织机构模块全部进行重新实现。另外基于优先实现接口,而不是优先使用继承的设计要求,应用层面上开发人员的主要二开手段,还是对org-sdk提供的接口进行实现。
在这里插入图片描述

上图把本系列第7篇、第8篇文章介绍的和将要介绍的组织机构模块实现分为两部分。其中一部分是在应用程序中所引用的simple-org-local-starter部分,这部分组织机构模块的默认实现,关键的实现细节已经在上文中(《软件设计不是CRUD(7):低耦合模块设计实战——组织机构模块(中)》)进行了介绍。作为二次开发团队只能使用simple-org-local-starter,但不能对simple-org-local-starter进行修改。有的读者就会问,既然不能对simple-org-local-starter进行修改,那怎么进行组织机构模块的扩展实现呢?这就涉及到第二部分:二次开发团队为了实现项目中特定的需求,基于org-sdk提供的接口(和org的本地默认实现)所自行实现的自定义类。下面我们详细看看二次开发团队为了实现定制需求,所进行的具体开发内容。

5.2、进行业务模型的定制开发

5.2.1、定义新的组织机构数据模型

  • 定义一个新的模型,来满足“新增一种组织机构模型”的需求(MyOrg),这也是对组织机构模块“组织机构类型”可随意扩展的效果展示:
// 这是本项目中特定使用的组织机构信息
@Getter
@Setter
public class MyOrg implements TreeOrganization {
  public static final String MY_ORG = "myorg";
  private String id;
  // 组织机构类型
  private String type = MY_ORG;
  // 业务编号
  private String code;
  // 组织机构上级组织机构编号
  private String parentCode;
  // 组织机构上级组织机构类型
  private String parentType;
  // 中文名
  // ========== 以下都是这种新的组织机构类型所特有的业务字段
  private String name;
  // 组织机构负责人
  private String main;
  // 机构级别
  private String level;
  // 级别说明
  private String levelDesc;
  // 自有资金
  private BigDecimal funds;
  // 组织机构携带的下级组织机构信息(组织机构特性字段)
  private List<? extends Organization> kids;
  // ====== 注意,该组织机构类型不能关联任何的用户信息
  @Override
  public <M extends UserMapping> List<M> getUsers() {
    return null;
  }
  @Override
  public void setUsers(List<? extends UserMapping> users) {
    throw new UnsupportedOperationException("该组织机构类型不支持关联用户");
  }
}
  • 注册这个模型(MyOrganizationModuleRegister ):定义好了新的组织机构类型,我们需要把这个组织机构类型注册到IOC容器,并保证组织机构模块主要的逻辑控制中能够识别到这个组织机构类型模型。
// 实现OrganizationModuleRegister接口,保证这个新的组织机构模型能够被正确识别、转换。
@Component
public class MyOrganizationModuleRegister implements OrganizationModuleRegister<MyOrg> {
  // 默认的组织机构类型,排序优先度很高
  @Override
  public int getOrder() {
    return 2;
  }
  @Override
  public String type() {
    return MyOrg.MY_ORG;
  }
  @Override
  public Class<? extends MyOrg> getOrgClass() {
    return MyOrg.class;
  }
  @Override
  public MyOrg transform(JSONObject json) {
    // 正式生产过程中,可能涉及到递归的下级组织机构转换,
    // 这里不进行演示,只对基本信息和可能的直属用户信息进行转换
    // 1、===== 首先进行组织机构基本信息的转换
    String code = json.getString("code");
    Validate.notBlank(code , "错误的组织机构编号信息,请检查!");
    String name = json.getString("name");
    Validate.notBlank(name , "错误的组织机构名称信息,请检查!");
    // 还有其他和业务相关的必传字段进行的边界校验
    // ......
    
    // 不处理下级组织机构,也不处理下级用户(因为不允许携带用户)
    json.remove("kids");
    json.remove("users");
    MyOrg myOrg = json.toJavaObject(MyOrg.class);
    return myOrg;
  }
  // 只允许关联组织机构为MyOrg的下级组织机构
  @Override
  public String[] enableOrgTypes() {
    return new String[] {MyOrg.MY_ORG};
  }
  // 不允许关联任何类型的用户
  @Override
  public String[] enableUserTypes() {
    return new String[] {};
  }
}

5.2.2、定义新的组织机构—用户关联模型

  • 定义一个新的模型,来满足“新增一种组织机构—用户关联”的需求(MyMappingUser)
// 这是该项目特定的一种用户和组织机构的关联
@Setter
@Getter
public class MyMappingUser implements UserMapping {
  public static final String MY_MAPPING_ORG = "mapping_user";
  private String id;
  // 唯一的用户账号信息
  private String account;
  // 用户中文姓名信息
  private String describer;
  // 用户昵称信息
  private String name;
  
  // ========= 以下是这种新的组织机构-用户关联信息中,特有的业务字段
  // 特定关联信息1
  private String field1;
  // 特定关联信息2
  private Long field2;
  // 特定关联信息3
  private BigDecimal field3;
}
  • 注册这个关联模型(MyUserMappingModuleRegister)
/**
 * 这个组织机构-用户关联类型,所以需要进行描述类型的验证和转换
 * @author yinwenjie
 */
@Component
public class MyUserMappingModuleRegister implements UserMappingModuleRegister<MyMappingUser> {
  @Override
  public int getOrder() {
    return 1;
  }
  // 指定一种类型
  @Override
  public String type() {
    return MyMappingUser.MY_MAPPING_ORG;
  }
  @Override
  public Class<? extends MyMappingUser> getMappingClass() {
    return MyMappingUser.class;
  }
  @SuppressWarnings("unchecked")
  @Override
  public List<MyMappingUser> transform(JSONArray users) {
    // 只有要求转换的用户关联信息,其type都为default的时候,才进行转换,否则报错
    Validate.isTrue(!CollectionUtils.isEmpty(users) , "传入的用户json集合不能为null");
    for (Object userItem : users) {
      JSONObject userObject = (JSONObject)userItem;
      String type = userObject.getString("type");
      String account = userObject.getString("account");
      Validate.notBlank(type , "进行转换时,用户类型必须传入");
      Validate.notBlank(account , "进行转换时,账户信息必须传入");
      // 进行特有的业务信息的验证
      // ......
    }
    // 开始进行转换
    List<MyMappingUser> userObjects = (List<MyMappingUser>)JSONArray.parseArray(users.toJSONString(), this.getMappingClass());
    return userObjects;
  }
}

5.2.3、对默认提供的组织机构数据模型进行扩展

  • 扩展组织机构默认实现中提供的组织机构模型定义,加入新的项目上的特有字段(ExtendDefaultOrg)
/**
 * 满足第二个需求变动点,在默认的组织机构上增加“有效时间”、“经办方式”、“预算上限”三个特定字段
 * 这种继承方式的问题后文还会进行详细讲解
 */
@Getter
@Setter
public class ExtendDefaultOrg extends DefaultOrg {
  // 有效时间
  private Date effectiveTime;
  // 经办方式
  private String handleMethod;
  // 预算上限
  private BigDecimal upperLimit;
}
  • 将这个扩展的模型重新注册进行注册,并调整注册中的具体类型说明
@Component
public class ExtendsOrganizationModuleRegister extends DefaultOrganizationModuleRegister {
  // 二开团队扩展DefaultOrg后,涉及到类型转换的逻辑基本不会修改,只需要指定class类型即可
  @Override
  public Class<? extends DefaultOrg> getOrgClass() {
    return ExtendDefaultOrg.class;
  } 
} 

根据经典的依赖设计原则,应该优先考虑实现接口而不是优先考虑继承。但是在实际工作中,根据特定的需求场景,技术人员是需要使用继承的。从本小节对默认的组织机构类型进行扩展的设计来看,就是使用的继承——继承原有的默认组织机构类型(DefaultOrg),并在此基础上增加特定的业务字段。

但是这种使用“继承”的方式会带来一系列问题,主要包括:

  • 由于默认的组织机构类型(DefaultOrg)是有组织机构默认的本地数据库实现所提供(simple.org.local),所以一旦产生继承关系就意味着应用程序(Spring-Boot)以后都必须依赖组织机构的本地实现(simple-org-local-starter)才能正常工作。

  • 由于技术人员依赖了组织机构模块默认的本地数据库实现,并且继承业务模型后中增加了特定的业务字段。那么数据层的模型和功能该怎么办?数据层的模型和功能是否需要重新书写,又或者数据层模型(DefaultOrgEntity)和功能(DefaultOrgRepository)在原有基础上进行继承呢?如果采用继承的方式进行扩展,那么数据层功能中原有的方法肯定不适用,因为他们都是返回的DefaultOrgEntity,而不是MyOrgEntity。所以本文的建议是,即使使用了继承体系,那么数据层的相关模型和功能也最好进行重新书写。

5.3、进行业务行为的定制开发

有了对模型的描述和定义后,我们需要对新模型的功能行为进行描述。这里的描述分为两个层面,第一个层面是为新的模型能够融入组织机构模块业已存在的功能,进行的行为描述。例如所有接入组织机构模块的节点,如果支持树形结构的展示那么都要描述自己如何查询关联的下级组织机构和下级用户信息。

第二个层面是这些新的模型所代表的定制化业务都有伴随着一些定制化的功能要求,例如项目团队自己要求创建的这种组织机构类型中,存在“机构级别”这样的业务字段,并且项目客户要求提供“按照机构级别”进行组织机构信息查询这样的定制化功能。

5.3.1、接入组织机构模块既有的行为

  • 首先为了自定义的组织机构模型能够接入既有的功能,需要实现OrganizationStrategy接口
/**
 * 保证自定义的组织机构类型,能够在组织机构既定的处理逻辑协调下,实现最基本的增、删、改、查功能
 */
@Component
public class MyOrgStrategy implements OrganizationStrategy<MyOrg> {
  // 特定的数据层服务
  @Autowired
  private MyOrgRepository myOrgRepository;
  @Override
  public String type() {
    return MyOrg.MY_ORG;
  }
  
  @Override
  public Collection<MyOrg> queryChilds(String parentType, String parentCode) {
    if(StringUtils.isAnyBlank(parentType , parentCode)) {
      return null;
    }
    // 由于本类型的组织机构允许用一种组织机构类型作为父级进行关联。
    List<MyOrgEntity> myOrgEntities = this.myOrgRepository.findByParentCodeAndParentType(parentType, parentCode);
    if(CollectionUtils.isEmpty(myOrgEntities)) {
      return Lists.newArrayList();
    }
    
    // 转换为业务模型,然后进行返回
    List<MyOrg> myOrgs = this.transform(myOrgEntities);
    return myOrgs;
  }
  
  // 其他实现代码(新增、修改等功能)略
  // ......
}
  • 然后是基于MyMappingUser模型实现UserMappingStrategy接口,以保证新的组织机构-用户关联信息,能够接入组织机构模块已有的控制逻辑。
/**
 * 保证自定义的组织机构-用户关联信息,能够在组织机构模块既定的处理逻辑协调下,
 * 实现最基本的增、删、改、查功能
 */
@Component
public class MyMappingUserStrategy implements UserMappingStrategy<MyMappingUser> {
  @Autowired
  private MyMappingUserRepository myMappingUserRepository;
  @Override
  public int getOrder() {
    return 2;
  }
  @Override
  public String type() {
    return MyMappingUser.MY_MAPPING_ORG;
  }

  @Override
  public Collection<MyMappingUser> queryUsers(String parentType, String parentCode) {
    if(StringUtils.isAnyBlank(parentType , parentCode)) {
      return Lists.newArrayList();
    }
    
    // 进行数据层的查询
    List<MyMappingUserEntity> mappingUserEntities = this.myMappingUserRepository.findByParentCodeAndParentType(parentCode, parentType);
    if(CollectionUtils.isEmpty(mappingUserEntities)) {
      return Lists.newArrayList();
    }
    // 然后进行转换和返回
    return this.transform(mappingUserEntities);
  }
  
  // 其它方法略
  // ......
}

5.3.2、定制化功能的开发

从需求上来看,项目上的开发人员还需要为新定制的组织机构模型,增加一个特定的查询方法。例如上文提到的,按照特定的机构级别信息进行查询。

  • 这是定制的服务层接口:MyOrgService
/**
 * 由于需求中明确,本项目中特定业务模型还有这个特定业务模型所需要的新的查询功能,
 * 那么可以根据这个特定行为要求,自行创建服务层接口
 * @author yinwenjie
 */
public interface MyOrgService {
  /**
   * 按照特定的机构级别信息,进行查询
   * @param level
   * @return
   */
  public List<MyOrg> findByLevel(String level);
}
  • 以下是接口实现:MyOrgServiceImpl
@Service
public class MyOrgServiceImpl implements MyOrgService {

  @Autowired
  private MyOrgRepository myOrgRepository;
  
  @Override
  public List<MyOrg> findByLevel(String level) {
    if(StringUtils.isBlank(level)) {
      return Lists.newArrayList();
    }
    
    List<MyOrgEntity> myOrgEntities = this.myOrgRepository.findByLevel(level);
    if(CollectionUtils.isEmpty(myOrgEntities)) {
      return Lists.newArrayList();
    }
    // 转换成业务模型后,进行返回
    return this.transform(myOrgEntities);
  }
  //  非必要代码省略
  // ......
}

注意,从以上示例代码中我们至少看到了以下几个现象:

  • 和自定义组织机构类型相关的特异性功能,统一放在名为MyOrgService的服务层接口定义中,且这个接口定义直接放置在Spring-Boot应用程序的脚手架中。也就是说这个服务层的接口只对该应用程序有效,组织机构模块的默认实现或者其它应用程序是无法看到更无法使用这些接口功能

  • 为了便于管理,除了那些需要进行业务抽象后统一管理的业务行为,其它在项目层面自定义的业务行为都被按照业务模型进行分类定义。也就是说项目层面上自定义的组织机构模型(MyOrg)其特有的业务行为被定义在MyOrgService接口中、项目层面上自定义的组织机构-用户模型(MyMappingUser)其特有的业务行为被定义在MyMappingUserService中。

6、示例总结

在这里插入图片描述

上图展示了本专题第6篇、第7篇、第8篇文章描述的组织机构模块的接口定义、模块默认实现、模块二次开发支持相关的所有工程结构细节。我们来总结一下整个示例所展示出来的关键细节:

  • 从需求提出到业务功能的具体技术解决方案,其过程包含一个重要的需求分析步骤。这个需求分析步骤主要是通过业务抽象的思想,分析出需求中所包括的不变部分和可变部分。可变部分又可以被分解成多个业务维度,这些业务维度都通过定义接口的方式提供出来供技术人员进行不同的落地实现。在组织机构功能的业务抽象后,我们找到了两个业务维度:组织机构类型和组织机构-用户的关联类型。

  • 经过业务抽象所形成的功能模块方案,一定会有一个接口定义层。在这个组织机构模块的实例中,集中形成的接口定义层,就是名叫org-sdk的脚手架。通过观察这个脚手架,组织机构模块的接口定义层除了最多可能包括一个默认的控制逻辑(具体的Class类)外,确实只有接口定义存在。包括业务模型的结构也只存在接口定义(如Organization接口、UserMapping接口)。

  • 这个接口定义层是降低模块耦合度的重要基础。通过这个接口定义层,可以控制不同的业务维度的具体实现最终组合成不同的业务逻辑线;通过这个接口定义层,可以稳定功能模块在应用系统中的技术分层,控制循环依赖;通过这个接口定义层,可以隔离需求变化导致的变更涟漪效应,保证需求变化只会使某种具体的业务逻辑在一定功能边界内受到影响。

  • 优先考虑实现而非继承是有现实意义的,例如本示例中由于需要扩展组织机构默认实现中的默认组织机构类型(在其中加入两个项目上特有的字段),所以ExtendDefaultOrg就继承了DefaultOrg。就这一个小小的设计,使整个应用程序付出了较高的维护成本,后文我们将介绍出现这种情况的优化思路,但都不能彻底将维护成本降低到原来的样子。

  • 在这个组织机构模块的示例讲解过程中,我们涉及到了几个设计名词:业务抽象、业务屈服度、业务维度、相交的业务维度和不相交的业务维度。本专题的后续文章中,会深入介绍业务抽象的理论细节,并会对这些设计名词进行详细的阐述。

  • 最后完整的示例代码可通过该处链接进行完整下载:此链接可以进行下载(https://download.csdn.net/download/yinwenjie/88617268?spm=1001.2014.3001.5501)。如各位读者在阅读完这三篇示例文章后,还是对“为什么要这样做”有疑问,可以将问题发在评论区。实际上对于如何通过业务抽象的思想降低模块间耦合强度,以及其中包含的设计优势,还需要在更深入了解业务抽象的理论后,才会有更深入的设计体会。后续文章我们将开始对设计理论进行逐个剖析。下图展示了一个根据业务抽象的设计理论完成的低耦合模块的应用程序分层依赖的效果图。
    在这里插入图片描述

如果读者观察上图时心中仍然还有**“如果访问权限模块需要获取角色信息,但访问权限模块又不允许依赖角色模块,那怎么调用角色模块的功能获取角色信息?”**这样的疑问,那确实需要继续阅读本专题后续的文章。后文本专题将展开更理论化的讲解。

文章来源:https://blog.csdn.net/yinwenjie/article/details/134891045
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。