摘要: 原创出处 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 实体。代码如下:
@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 图
数据流向如下:
在 App 下创建 AppNamespace 后,自动给 App 下每个 Cluster 创建 Namespace 。在 App 下创建 Cluster 后,根据 App 下 每个 AppNamespace 创建 Namespace 。可删除 Cluster 下的 Namespace 。总结来说:
AppNamespace 是 App 下的每个 Cluster 默认创建的 Namespace 。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 AppNamespaceCreationEventcom.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 CreationListenercom.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 NamespaceAPIcom.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 AppNamespaceRepositorycom.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 NamespaceRepositorycom.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 失败,则会出现不一致的情况。
---来自腾讯云社区的---芋道源码
微信扫一扫打赏
支付宝扫一扫打赏