前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apollo 源码解析 —— Portal 创建 Namespace

Apollo 源码解析 —— Portal 创建 Namespace

作者头像
芋道源码
发布2020-05-19 15:28:17
1.2K0
发布2020-05-19 15:28:17
举报
文章被收录于专栏:芋道源码1024芋道源码1024

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-namespace/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述
  • 2. 实体
  • 3. Portal 侧
  • 4. Admin Service 侧
  • 666. 彩蛋

阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。 胖友可以点击「芋道源码」扫码关注,回复 git018 关键字 获得艿艿添加了中文注释的 Apollo 源码地址。 阅读源码很孤单,加入源码交流群,一起坚持!

1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Apollo 官方 wiki 文档 —— 核心概念之“Namespace”》 。

本文分享 Portal 创建 Namespace 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:

流程

下面,我们先来看看 AppNamespace 和 Namespace 的实体结构

老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。

2. 实体

2.1 AppNamespace

apollo-common 项目中,com.ctrip.framework.apollo.common.entity.AppNamespace ,继承 BaseEntity 抽象类,App Namespace 实体。代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "AppNamespace")
@SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class AppNamespace extends BaseEntity {

    /**
     * AppNamespace 名
     */
    @Column(name = "Name", nullable = false)
    private String name;
    /**
     * App 编号
     */
    @Column(name = "AppId", nullable = false)
    private String appId;
    /**
     * 格式
     *
     * 参见 {@link ConfigFileFormat}
     */
    @Column(name = "Format", nullable = false)
    private String format;
    /**
     * 是否公用的
     */
    @Column(name = "IsPublic", columnDefinition = "Bit default '0'")
    private boolean isPublic = false;
    /**
     * 备注
     */
    @Column(name = "Comment")
    private String comment;
}
  • appId 字段,App 编号,指向对应的 App 。App : AppNamespace = 1 : N 。
  • format 字段,格式。在 com.ctrip.framework.apollo.core.enums.ConfigFileFormat 枚举类中,定义了五种类型。代码如下: public enum ConfigFileFormat { Properties("properties"), XML("xml"), JSON("json"), YML("yml"), YAML("yaml"); private String value; // ... 省略了无关的代码 }
  • isPublic 字段,是否公用的。 Namespace的获取权限分为两种: 这里的获取权限是相对于 Apollo 客户端来说的。
    • private (私有的):private 权限的 Namespace ,只能被所属的应用获取到。一个应用尝试获取其它应用 private 的 Namespace ,Apollo 会报 “404” 异常。
    • public (公共的):public 权限的 Namespace ,能被任何应用获取。

2.2 Namespace

apollo-biz 项目中, com.ctrip.framework.apollo.biz.entity.Namespace ,继承 BaseEntity 抽象类,Cluster Namespace 实体,是配置项的集合,类似于一个配置文件的概念。代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "Namespace")
@SQLDelete(sql = "Update Namespace set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Namespace extends BaseEntity {

    /**
     * App 编号 {@link com.ctrip.framework.apollo.common.entity.App#appId}
     */
    @Column(name = "appId", nullable = false)
    private String appId;
    /**
     * Cluster 名 {@link Cluster#name}
     */
    @Column(name = "ClusterName", nullable = false)
    private String clusterName;
    /**
     * AppNamespace 名 {@link com.ctrip.framework.apollo.common.entity.AppNamespace#name}
     */
    @Column(name = "NamespaceName", nullable = false)
    private String namespaceName;
    
}    

2.3 AppNamespace vs. Namespace

关系图如下:

ER 图

数据流向如下:

  1. 在 App 下创建 AppNamespace 后,自动给 App 下每个 Cluster 创建 Namespace 。
  2. 在 App 下创建 Cluster 后,根据 App 下 每个 AppNamespace 创建 Namespace 。
  3. 可删除 Cluster 下的 Namespace 。

总结来说:

  1. AppNamespace 是 App 下的每个 Cluster 默认创建的 Namespace 。
  2. Namespace 是 每个 Cluster 实际拥有的 Namespace 。

2.4 类型

Namespace 类型有三种:

  • 私有类型:私有类型的 Namespace 具有 private 权限。
  • 公共类型:公共类型的 Namespace 具有 public 权限。公共类型的 Namespace 相当于游离于应用之外的配置,且通过 Namespace 的名称去标识公共 Namespace ,所以公共的 Namespace 的名称必须全局唯一
  • 关联类型:关联类型又可称为继承类型,关联类型具有 private 权限。关联类型的Namespace 继承于公共类型的Namespace,用于覆盖公共 Namespace 的某些配置。

在 Namespace 实体中,找不到 类型的字段呀?!通过如下逻辑判断:

代码语言:javascript
复制
Namespace => AppNamespace
if (AppNamespace.isPublic) {
    return "公共类型";
}
if (Namespace.appId == AppNamespace.appId) {
    return "私有类型";
}
return "关联类型";

3. Portal 侧

3.1 NamespaceController

apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.NamespaceController ,提供 AppNamespace 和 Namespace 的 API

创建 Namespace的界面中,点击【提交】按钮,调用创建 AppNamespace 的 API

创建 Namespace

代码如下:

代码语言:javascript
复制
  1: @RestController
  2: public class NamespaceController {
  3: 
  4:     @Autowired
  5:     private ApplicationEventPublisher publisher;
  6:     @Autowired
  7:     private AppNamespaceService appNamespaceService;
  8:     @Autowired
  9:     private PortalConfig portalConfig;
 10: 
 11:     @PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
 12:     @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
 13:     public AppNamespace createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) {
 14:         // 校验 AppNamespace 的 `appId` 和 `name` 非空。
 15:         RequestPrecondition.checkArgumentsNotEmpty(appNamespace.getAppId(), appNamespace.getName());
 16:         // 校验 AppNamespace 的 `name` 格式正确。
 17:         if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
 18:             throw new BadRequestException(String.format("Namespace格式错误: %s",
 19:                     InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
 20:                             + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
 21:         }
 22:         // 保存 AppNamespace 对象到数据库
 23:         AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace);
 24:         // 赋予权限,若满足如下任一条件:
 25:         // 1. 公开类型的 AppNamespace 。
 26:         // 2. 私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。
 27:         if (portalConfig.canAppAdminCreatePrivateNamespace() || createdAppNamespace.isPublic()) {
 28:             // 授予 Namespace Role
 29:             assignNamespaceRoleToOperator(appId, appNamespace.getName());
 30:         }
 31:         // 发布 AppNamespaceCreationEvent 创建事件
 32:         publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace));
 33:         // 返回创建的 AppNamespace 对象
 34:         return createdAppNamespace;
 35:     }
 36: 
 37: }
  • POST apps/{appId}/appnamespaces 接口,Request Body 传递 JSON 对象。
  • @PreAuthorize(...) 注解,调用 PermissionValidator#hasCreateAppNamespacePermission(appId, appNamespace) 方法,校验是否有创建 AppNamespace 的权限。后续文章,详细分享。
  • 第 15 行:调用 RequestPrecondition#checkArgumentsNotEmpty(String... args) 方法,校验 AppNamespace 的 appIdname 非空。
  • 第 16 至 21 行:调用 InputValidator#isValidAppNamespace(name) 方法,校验 AppNamespace 的 name 格式正确,符合 [0-9a-zA-Z_.-]+"[a-zA-Z0-9._-]+(?<!\.(json|yml|yaml|xml|properties))$ 格式。
  • 第 23 行:调用 AppNamespaceService#createAppNamespaceInLocal(AppNamespace) 方法,保存 AppNamespace 对象到 Portal DB 数据库。在 「3.2 AppNamespaceService」 中,详细解析。
  • 第 27 至 30 行:调用 #assignNamespaceRoleToOperator(String appId, String namespaceName) 方法,授予 Namespace Role ,需要满足如下任一条件。
    • 1、 公开类型的 AppNamespace 。
    • 2、私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。 admin.createPrivateNamespace.switch 【在 ServerConfig 表】 是否允许项目管理员创建 private namespace 。设置为 true 允许创建,设置为 false 则项目管理员在页面上看不到创建 private namespace 的选项。并且,项目管理员不允许创建 private namespace
  • 第 32 行:调用 ApplicationEventPublisher#publishEvent(AppNamespaceCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent 事件。
  • 第 38 行:返回创建的 AppNamespace 对象。

3.2 AppNamespaceService

apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑。

#createAppNamespaceInLocal(AppNamespace) 方法,保存 AppNamespace 对象到 Portal DB 数据库。代码如下:

代码语言:javascript
复制
  1: @Autowired
  2: private UserInfoHolder userInfoHolder;
  3: @Autowired
  4: private AppNamespaceRepository appNamespaceRepository;
  5: @Autowired
  6: private RoleInitializationService roleInitializationService;
  7: @Autowired
  8: private AppService appService;
  9: 
 10: @Transactional
 11: public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace) {
 12:     String appId = appNamespace.getAppId();
 13:     // 校验对应的 App 是否存在。若不存在,抛出 BadRequestException 异常
 14:     // add app org id as prefix
 15:     App app = appService.load(appId);
 16:     if (app == null) {
 17:         throw new BadRequestException("App not exist. AppId = " + appId);
 18:     }
 19:     // 拼接 AppNamespace 的 `name` 属性。
 20:     StringBuilder appNamespaceName = new StringBuilder();
 21:     // add prefix postfix
 22:     appNamespaceName
 23:             .append(appNamespace.isPublic() ? app.getOrgId() + "." : "") // 公用类型,拼接组织编号
 24:             .append(appNamespace.getName())
 25:             .append(appNamespace.formatAsEnum() == ConfigFileFormat.Properties ? "" : "." + appNamespace.getFormat());
 26:     appNamespace.setName(appNamespaceName.toString());
 27:     // 设置 AppNamespace 的 `comment` 属性为空串,若为 null 。
 28:     if (appNamespace.getComment() == null) {
 29:         appNamespace.setComment("");
 30:     }
 31:     // 校验 AppNamespace 的 `format` 是否合法
 32:     if (!ConfigFileFormat.isValidFormat(appNamespace.getFormat())) {
 33:         throw new BadRequestException("Invalid namespace format. format must be properties、json、yaml、yml、xml");
 34:     }
 35:     // 设置 AppNamespace 的创建和修改人
 36:     String operator = appNamespace.getDataChangeCreatedBy();
 37:     if (StringUtils.isEmpty(operator)) {
 38:         operator = userInfoHolder.getUser().getUserId(); // 当前登录管理员
 39:         appNamespace.setDataChangeCreatedBy(operator);
 40:     }
 41:     appNamespace.setDataChangeLastModifiedBy(operator);
 42:     // 公用类型,校验 `name` 在全局唯一
 43:     // unique check
 44:     if (appNamespace.isPublic() && findPublicAppNamespace(appNamespace.getName()) != null) {
 45:         throw new BadRequestException(appNamespace.getName() + "已存在");
 46:     }
 47:     // 私有类型,校验 `name` 在 App 下唯一
 48:     if (!appNamespace.isPublic() && appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName()) != null) {
 49:         throw new BadRequestException(appNamespace.getName() + "已存在");
 50:     }
 51:     // 保存 AppNamespace 到数据库
 52:     AppNamespace createdAppNamespace = appNamespaceRepository.save(appNamespace);
 53:     // 初始化 Namespace 的 Role 们
 54:     roleInitializationService.initNamespaceRoles(appNamespace.getAppId(), appNamespace.getName(), operator);
 55:     return createdAppNamespace;
 56: }
  • 第 15 至 18 行:调用 AppService.load(appId) 方法,获得对应的 App 对象。当校验 App 不存在时,抛出 BadRequestException 异常。
  • 第 19 至 26 行:拼接并设置 AppNamespace 的 name 属性。
  • 第 27 至 30 行:设置 AppNamespace 的 comment 属性为空串,若为 null 。
  • 第 31 至 34 行:校验 AppNamespace 的 format 是否合法。
  • 第 35 至 41 行:设置 AppNamespace 的创建和修改人。
  • 第 42 至 46 行:若 AppNamespace 为公用类型,校验 name全局唯一,否则抛出 BadRequestException 异常。#findPublicAppNamespace(name) 方法,代码如下: public AppNamespace findPublicAppNamespace(String namespaceName) { return appNamespaceRepository.findByNameAndIsPublic(namespaceName, true); }
  • 第 47 至 50 行:若 AppNamespace 为私有类型,校验 nameApp 唯一否则抛出 BadRequestException 异常。
  • 第 52 行:调用 AppNamespaceRepository#save(AppNamespace) 方法,保存 AppNamespace 到数据库。
  • 第 54 行:初始化 Namespace 的 Role 们。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。

#createDefaultAppNamespace(appId) 方法,创建并保存 App 下默认的 "application" 的 AppNamespace 到数据库。代码如下:

代码语言:javascript
复制
@Transactional
public void createDefaultAppNamespace(String appId) {
    // 校验 `name` 在 App 下唯一
    if (!isAppNamespaceNameUnique(appId, ConfigConsts.NAMESPACE_APPLICATION)) {
        throw new BadRequestException(String.format("App already has application namespace. AppId = %s", appId));
    }
    // 创建 AppNamespace 对象
    AppNamespace appNs = new AppNamespace();
    appNs.setAppId(appId);
    appNs.setName(ConfigConsts.NAMESPACE_APPLICATION); // `application`
    appNs.setComment("default app namespace");
    appNs.setFormat(ConfigFileFormat.Properties.getValue());
    // 设置 AppNamespace 的创建和修改人为当前管理员
    String userId = userInfoHolder.getUser().getUserId();
    appNs.setDataChangeCreatedBy(userId);
    appNs.setDataChangeLastModifiedBy(userId);
    // 保存 AppNamespace 到数据库
    appNamespaceRepository.save(appNs);
}
  • 在 App 创建时,会调用该方法。

3.3 AppNamespaceRepository

apollo-portal 项目中,com.ctrip.framework.apollo.common.entity.App.AppNamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 AppNamespace 的数据访问,即 DAO

代码如下:

代码语言:javascript
复制
public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNamespace, Long> {

  AppNamespace findByAppIdAndName(String appId, String namespaceName);

  AppNamespace findByName(String namespaceName);

  AppNamespace findByNameAndIsPublic(String namespaceName, boolean isPublic);

  List<AppNamespace> findByIsPublicTrue();

}

3.4 AppNamespaceCreationEvent

com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent ,实现 org.springframework.context.ApplicationEvent 抽象类,AppNamespace 创建事件。

代码如下:

代码语言:javascript
复制
public class AppNamespaceCreationEvent extends ApplicationEvent {

  public AppNamespaceCreationEvent(Object source) {
    super(source);
  }

  public AppNamespace getAppNamespace() {
    Preconditions.checkState(source != null);
    return (AppNamespace) this.source;
  }

}
  • 构造方法,将 AppNamespace 对象作为方法参数传入。
  • #getAppNamespace() 方法,获得事件对应的 AppNamespace 对象。

3.4.1 CreationListener

com.ctrip.framework.apollo.portal.listener.CreationListener对象创建监听器,目前监听 AppCreationEvent 和 AppNamespaceCreationEvent 事件。

我们以 AppNamespaceCreationEvent 举例子,代码如下:

代码语言:javascript
复制
@EventListener
public void onAppNamespaceCreationEvent(AppNamespaceCreationEvent event) {
    // 将 AppNamespace 转成 AppNamespaceDTO 对象
    AppNamespaceDTO appNamespace = BeanUtils.transfrom(AppNamespaceDTO.class, event.getAppNamespace());
    // 获得有效的 Env 数组
    List<Env> envs = portalSettings.getActiveEnvs();
    // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 AppNamespace 对象。
    for (Env env : envs) {
        try {
            namespaceAPI.createAppNamespace(env, appNamespace);
        } catch (Throwable e) {
            logger.error("Create appNamespace failed. appId = {}, env = {}", appNamespace.getAppId(), env, e);
            Tracer.logError(String.format("Create appNamespace failed. appId = %s, env = %s", appNamespace.getAppId(), env), e);
        }
    }
}

3.5 NamespaceAPI

com.ctrip.framework.apollo.portal.api.NamespaceAPI ,实现 API 抽象类,封装对 Admin Service 的 AppNamespace 和 Namespace 两个模块的 API 调用。代码如下:

NamespaceAPI

  • 使用 restTemplate ,调用对应的 API 接口。

4. Admin Service 侧

4.1 AppNamespaceController

apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppNamespaceController ,提供 AppNamespace 的 API

#create(AppNamespaceDTO) 方法,创建 AppNamespace 。代码如下:

代码语言:javascript
复制
  1: @RestController
  2: public class AppNamespaceController {
  3: 
  4:     @Autowired
  5:     private AppNamespaceService appNamespaceService;
  8: 
  9:     /**
 10:      * 创建 AppNamespace
 11:      *
 12:      * @param appNamespace AppNamespaceDTO 对象
 13:      * @return AppNamespace 对象
 14:      */
 15:     @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
 16:     public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace) {
 17:         // 将 AppNamespaceDTO 转换成 AppNamespace 对象
 18:         AppNamespace entity = BeanUtils.transfrom(AppNamespace.class, appNamespace);
 19:         // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。
 20:         AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
 21:         if (managedEntity != null) {
 22:             throw new BadRequestException("app namespaces already exist.");
 23:         }
 24:         // 设置 AppNamespace 的 format 属性为 "properties",若为 null 。
 25:         if (StringUtils.isEmpty(entity.getFormat())) {
 26:             entity.setFormat(ConfigFileFormat.Properties.getValue());
 27:         }
 28:         // 保存 AppNamespace 对象到数据库
 29:         entity = appNamespaceService.createAppNamespace(entity);
 30:         // 将保存的 AppNamespace 对象,转换成 AppNamespaceDTO 返回
 31:         return BeanUtils.transfrom(AppNamespaceDTO.class, entity);
 32:     }
 33:     
 34:     // ... 省略其他接口和属性
 35: }
  • POST /apps/{appId}/appnamespaces 接口,Request Body 传递 JSON 对象。
  • 第 22 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 AppNamespaceDTO 转换成 AppNamespace对象。
  • 第 20 至 23 行:调用 AppNamespaceService#findOne(appId, name) 方法,校验 name 在 App 下,是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 BadRequestException 异常。
  • 第 24 至 27 行:设置 AppNamespace 的 format 属性为 "properties",若为 null 。
  • 第 29 行:调用 AppNamespaceService#createAppNamespace(AppNamespace) 方法,保存 AppNamespace 对象到数据库。
  • 第 30 至 32 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将保存的 AppNamespace 对象,转换成 AppNamespaceDTO 返回。

4.2 AppNamespaceService

apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppNamespaceService ,提供 AppNamespace 的 Service 逻辑给 Admin Service 和 Config Service 。

#save(AppNamespace) 方法,保存 AppNamespace 对象到数据库中。代码如下:

代码语言:javascript
复制
  1: @Autowired
  2: private AppNamespaceRepository appNamespaceRepository;
  3: @Autowired
  4: private NamespaceService namespaceService;
  5: @Autowired
  6: private ClusterService clusterService;
  7: @Autowired
  8: private AuditService auditService;
  9: 
 10: @Transactional
 11: public AppNamespace createAppNamespace(AppNamespace appNamespace) {
 12:     // 判断 `name` 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 ServiceException 异常。
 13:     String createBy = appNamespace.getDataChangeCreatedBy();
 14:     if (!isAppNamespaceNameUnique(appNamespace.getAppId(), appNamespace.getName())) {
 15:         throw new ServiceException("appnamespace not unique");
 16:     }
 17:     // 保护代码,避免 App 对象中,已经有 id 属性。
 18:     appNamespace.setId(0);// protection
 19:     appNamespace.setDataChangeCreatedBy(createBy);
 20:     appNamespace.setDataChangeLastModifiedBy(createBy);
 21:     // 保存 AppNamespace 到数据库
 22:     appNamespace = appNamespaceRepository.save(appNamespace);
 23:     // 创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。
 24:     instanceOfAppNamespaceInAllCluster(appNamespace.getAppId(), appNamespace.getName(), createBy);
 25:     // 记录 Audit 到数据库中
 26:     auditService.audit(AppNamespace.class.getSimpleName(), appNamespace.getId(), Audit.OP.INSERT, createBy);
 27:     return appNamespace;
 28: }
  • 第 12 至 16 行:调用 #isAppNamespaceNameUnique(appId, name) 方法,判断 name 在 App 下是否已经存在对应的 AppNamespace 对象。若已经存在,抛出 ServiceException 异常。代码如下: public boolean isAppNamespaceNameUnique(String appId, String namespaceName) { Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(namespaceName, "Namespace must not be null"); return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName)); }
  • 第 18 行:置“” AppNamespace 的编号,防御性编程,避免 AppNamespace 对象中,已经有 id 属性。
  • 第 22 行:调用 AppNamespaceRepository#save(AppNamespace) 方法,保存 AppNamespace 对象到数据库中。
  • 第 24 行:调用 #instanceOfAppNamespaceInAllCluster(appId, namespaceName, createBy) 方法,创建 AppNamespace 在 App 下,每个 Cluster 的 Namespace 对象。代码如下: private void instanceOfAppNamespaceInAllCluster(String appId, String namespaceName, String createBy) { // 获得 App 下所有的 Cluster 数组 List<Cluster> clusters = clusterService.findParentClusters(appId); // 循环 Cluster 数组,创建并保存 Namespace 到数据库 for (Cluster cluster : clusters) { Namespace namespace = new Namespace(); namespace.setClusterName(cluster.getName()); namespace.setAppId(appId); namespace.setNamespaceName(namespaceName); namespace.setDataChangeCreatedBy(createBy); namespace.setDataChangeLastModifiedBy(createBy); namespaceService.save(namespace); } }
  • 第 26 行:记录 Audit 到数据库中。

4.3 AppNamespaceRepository

com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 AppNamespace 的数据访问 给 Admin Service 和 Config Service 。代码如下:

代码语言:javascript
复制
public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNamespace, Long>{

  AppNamespace findByAppIdAndName(String appId, String namespaceName);

  List<AppNamespace> findByAppIdAndNameIn(String appId, Set<String> namespaceNames);

  AppNamespace findByNameAndIsPublicTrue(String namespaceName);

  List<AppNamespace> findByNameInAndIsPublicTrue(Set<String> namespaceNames);

  List<AppNamespace> findByAppIdAndIsPublic(String appId, boolean isPublic);

  List<AppNamespace> findByAppId(String appId);

  List<AppNamespace> findFirst500ByIdGreaterThanOrderByIdAsc(long id);

}

4.4 NamespaceService

apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.NamespaceService ,提供 Namespace 的 Service 逻辑给 Admin Service 和 Config Service 。

#save(Namespace) 方法,保存 Namespace 对象到数据库中。代码如下:

代码语言:javascript
复制
  1: @Autowired
  2: private NamespaceRepository namespaceRepository;
  3: @Autowired
  4: private AuditService auditService;
  5: 
  6: @Transactional
  7: public Namespace save(Namespace entity) {
  8:     // 判断是否已经存在。若是,抛出 ServiceException 异常。
  9:     if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) {
 10:         throw new ServiceException("namespace not unique");
 11:     }
 12:     // 保护代码,避免 Namespace 对象中,已经有 id 属性。
 13:     entity.setId(0);//protection
 14:     // 保存 Namespace 到数据库
 15:     Namespace namespace = namespaceRepository.save(entity);
 16:     // 记录 Audit 到数据库中
 17:     auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.INSERT, namespace.getDataChangeCreatedBy());
 18:     return namespace;
 19: }
  • 第 8 至 11 行:调用 #isNamespaceUnique(appId, cluster, namespace) 方法,校验是否已经存在。若是,抛出 ServiceException 异常。代码如下: public boolean isNamespaceUnique(String appId, String cluster, String namespace) { Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(cluster, "Cluster must not be null"); Objects.requireNonNull(namespace, "Namespace must not be null"); return Objects.isNull(namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, cluster, namespace)); }
  • 第 12 行:置“” Namespace 的编号,防御性编程,避免 Namespace 对象中,已经有 id 属性。
  • 第 15 行:调用 NamespaceRepository#save(AppNamespace) 方法,保存 Namespace 对象到数据库中。
  • 第 17 行:记录 Audit 到数据库中。

#instanceOfAppNamespaces(appId, clusterName, createBy) 方法,创建并保存 App 下指定 Cluster 的 Namespace 到数据库。代码如下:

代码语言:javascript
复制
@Transactional
public void instanceOfAppNamespaces(String appId, String clusterName, String createBy) {
    // 获得所有的 AppNamespace 对象
    List<AppNamespace> appNamespaces = appNamespaceService.findByAppId(appId);
    // 循环 AppNamespace 数组,创建并保存 Namespace 到数据库
    for (AppNamespace appNamespace : appNamespaces) {
        Namespace ns = new Namespace();
        ns.setAppId(appId);
        ns.setClusterName(clusterName);
        ns.setNamespaceName(appNamespace.getName());
        ns.setDataChangeCreatedBy(createBy);
        ns.setDataChangeLastModifiedBy(createBy);
        namespaceRepository.save(ns);
        // 记录 Audit 到数据库中
        auditService.audit(Namespace.class.getSimpleName(), ns.getId(), Audit.OP.INSERT, createBy);
    }
}
  • 在 App 创建时,传入 Cluster 为 default ,此时只有 1 个 AppNamespace 对象。
  • 在 Cluster 创建时,传入自己,此处可以有个 AppNamespace 对象。

4.5 NamespaceRepository

com.ctrip.framework.apollo.biz.repository.NamespaceRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 Namespace 的数据访问 给 Admin Service 和 Config Service 。代码如下:

代码语言:javascript
复制
public interface NamespaceRepository extends PagingAndSortingRepository<Namespace, Long> {

  List<Namespace> findByAppIdAndClusterNameOrderByIdAsc(String appId, String clusterName);

  Namespace findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, String namespaceName);

  @Modifying
  @Query("update Namespace set isdeleted=1,DataChange_LastModifiedBy = ?3 where appId=?1 and clusterName=?2")
  int batchDelete(String appId, String clusterName, String operator);

  List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName);

  List<Namespace> findByNamespaceName(String namespaceName, Pageable page);

  int countByNamespaceNameAndAppIdNot(String namespaceName, String appId);

}

666. 彩蛋

类似于 App 的创建,AppNamespace 也存在跨系统同步的一致性问题。但是,目前暂未提供补偿机制,如果 Portal 创建 AppNamespace 成功,而调用远程 Admin Service 失败,则会出现不一致的情况。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 芋道源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. 实体
    • 2.1 AppNamespace
      • 2.2 Namespace
        • 2.3 AppNamespace vs. Namespace
          • 2.4 类型
          • 3. Portal 侧
            • 3.1 NamespaceController
              • 3.2 AppNamespaceService
                • 3.3 AppNamespaceRepository
                  • 3.4 AppNamespaceCreationEvent
                    • 3.4.1 CreationListener
                  • 3.5 NamespaceAPI
                  • 4. Admin Service 侧
                    • 4.1 AppNamespaceController
                      • 4.2 AppNamespaceService
                        • 4.3 AppNamespaceRepository
                          • 4.4 NamespaceService
                            • 4.5 NamespaceRepository
                            • 666. 彩蛋
                            相关产品与服务
                            数据库
                            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档