运维开发网

Apollo 源码解析 —— Portal 创建 Namespace

运维开发网 https://www.qedev.com 2021-01-22 12:08 出处:51CTO 作者:mb5ff80520dfa04
摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-namespace/ 「芋道源码」欢迎转载,保留摘要,谢谢!1. 概述2. 实体3. Portal 侧4. Admin Service 侧666. 彩蛋阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。胖友可以点击「芋道源码」扫码关注,回复 git018 关键字

摘要: 原创出处 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 ,如下图所示:

Apollo 源码解析 —— Portal 创建 Namespace

流程

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

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

2. 实体

2.1 AppNamespace

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

@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 实体,是配置项的集合,类似于一个配置文件的概念。代码如下:

@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 实体中,找不到 类型的字段呀?!通过如下逻辑判断:

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

代码如下:

  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 的 appId 和 name 非空。

  • 第 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 数据库。代码如下:

  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 为私有类型,校验 name 在App 唯一否则抛出 BadRequestException 异常。

  • 第 52 行:调用 AppNamespaceRepository#save(AppNamespace)方法,保存 AppNamespace 到数据库。

  • 第 54 行:初始化 Namespace 的 Role 们。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。


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

@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 。

代码如下:

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 创建事件。

代码如下:

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 举例子,代码如下:

@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 调用。代码如下:

Apollo 源码解析 —— Portal 创建 Namespace

NamespaceAPI

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

4. Admin Service 侧

4.1 AppNamespaceController

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

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

  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 对象到数据库中。代码如下:

  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 。代码如下:

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 对象到数据库中。代码如下:

  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 到数据库。代码如下:

@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 。代码如下:

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 失败,则会出现不一致的情况。

扫码领视频副本.gif

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号