Apollo 源码解析 —— Portal 创建 Namespace

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

@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的获取权限分为两种:

    • private (私有的):private 权限的 Namespace ,只能被所属的应用获取到。一个应用尝试获取其它应用 private 的 Namespace ,Apollo 会报 “404” 异常。
    • public (公共的):public 权限的 Namespace ,能被任何应用获取。

    这里的获取权限是相对于 Apollo 客户端来说的。

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

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);

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值