Apollo 源码解析 —— Portal 创建 Item
总阅读量:6134次
摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-item/ 「芋道源码」欢迎转载,保留摘要,谢谢!
阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。
胖友可以点击「芋道源码」扫码关注,回复 git018 关键字
获得艿艿添加了中文注释的 Apollo 源码地址。阅读源码很孤单,加入源码交流群,一起坚持!
1. 概述
老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 。
Item ,配置项,是 Namespace 下最小颗粒度的单位。在 Namespace 分成五种类型:properties
yml
yaml
json
xml
。其中:
properties
:每一行配置对应一条 Item 记录。- 后四者:无法进行拆分,所以一个 Namespace 仅仅对应一条 Item 记录。
本文先分享 Portal 创建类型为 properties 的 Namespace 的 Item 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:
老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。
2. 实体
2.1 Item
com.ctrip.framework.apollo.biz.entity.Item
,继承 BaseEntity 抽象类,Item 实体。代码如下:
|
namespaceId
字段,Namespace 编号,指向对应的 Namespace 记录。key
字段,键。- 对于
properties
,使用 Item 的key
,对应每条配置项的键。 - 对于
yaml
等等,使用 Item 的key = content
,对应整个配置文件。
- 对于
lineNum
字段,行号,从一开始。主要用于properties
类型的配置文件。
2.2 Commit
com.ctrip.framework.apollo.biz.entity.Commit
,继承 BaseEntity 抽象类,Commit 实体,记录 Item 的 KV 变更历史。代码如下:
|
appId
+clusterName
+namespaceName
字段,可以确认唯一 Namespace 记录。changeSets
字段,Item 变更集合。JSON 格式化字符串,使用 ConfigChangeContentBuilder 构建。
2.2.1 ConfigChangeContentBuilder
com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder
,配置变更内容构建器。
构造方法
private static final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); |
-
createItems
字段,添加代码如下:public ConfigChangeContentBuilder createItem(Item item) {
if (!StringUtils.isEmpty(item.getKey())) {
createItems.add(cloneItem(item));
}
return this;
}-
调用
#cloneItem(Item)
方法,克隆 Item 对象。因为在#build()
方法中,会修改 Item 对象的属性。代码如下:Item cloneItem(Item source) {
Item target = new Item();
BeanUtils.copyProperties(source, target);
return target;
}- x
-
-
updateItems
字段,添加代码如下:public ConfigChangeContentBuilder updateItem(Item oldItem, Item newItem) {
if (!oldItem.getValue().equals(newItem.getValue())) {
ItemPair itemPair = new ItemPair(cloneItem(oldItem), cloneItem(newItem));
updateItems.add(itemPair);
}
return this;
}-
ItemPair ,Item 组,代码如下:
static class ItemPair {
Item oldItem; // 老
Item newItem; // 新
public ItemPair(Item oldItem, Item newItem) {
this.oldItem = oldItem;
this.newItem = newItem;
}
}- x
-
-
deleteItems
字段,添加代码如下:public ConfigChangeContentBuilder deleteItem(Item item) {
if (!StringUtils.isEmpty(item.getKey())) {
deleteItems.add(cloneItem(item));
}
return this;
}
hasContent
#hasContent()
方法,判断是否有变化。当且仅当有变化才记录 Commit。代码如下:
public boolean hasContent() { |
build
#build()
方法,构建 Item 变化的 JSON 字符串。代码如下:
public String build() { |
-
例子如下:
// 已经使用 http://tool.oschina.net/codeformat/json/ 进行格式化,实际是**紧凑型**
{
"createItems": [ ],
"updateItems": [
{
"oldItem": {
"namespaceId": 32,
"key": "key4",
"value": "value4123",
"comment": "123",
"lineNum": 4,
"id": 15,
"isDeleted": false,
"dataChangeCreatedBy": "apollo",
"dataChangeCreatedTime": "2018-04-27 16:49:59",
"dataChangeLastModifiedBy": "apollo",
"dataChangeLastModifiedTime": "2018-04-27 22:37:52"
},
"newItem": {
"namespaceId": 32,
"key": "key4",
"value": "value41234",
"comment": "123",
"lineNum": 4,
"id": 15,
"isDeleted": false,
"dataChangeCreatedBy": "apollo",
"dataChangeCreatedTime": "2018-04-27 16:49:59",
"dataChangeLastModifiedBy": "apollo",
"dataChangeLastModifiedTime": "2018-04-27 22:38:58"
}
}
],
"deleteItems": [ ]
}
3. Portal 侧
3.1 ItemController
在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.controller.ItemController
,提供 Item 的 API 。
在【添加配置项】的界面中,点击【提交】按钮,调用创建 Item 的 API 。
#createItem(appId, env, clusterName, namespaceName, ItemDTO)
方法,创建 Item 对象。代码如下:
1: |
-
POST
/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item
接口,Request Body 传递 JSON 对象。 -
@PreAuthorize(...)
注解,调用PermissionValidator#hasModifyNamespacePermission(appId, namespaceName)
方法,校验是否有修改 Namespace 的权限。后续文章,详细分享。 -
com.ctrip.framework.apollo.common.dto.ItemDTO
,Item DTO 。代码如下:public class ItemDTO extends BaseDTO {
/**
* Item 编号
*/
private long id;
/**
* Namespace 编号
*/
private long namespaceId;
/**
* 键
*/
private String key;
/**
* 值
*/
private String value;
/**
* 备注
*/
private String comment;
/**
* 行数
*/
private int lineNum;
} -
第 14 行:调用
#isValidItem(ItemDTO)
方法,校验 Item 格式正确。代码如下:private boolean isValidItem(ItemDTO item) {
return Objects.nonNull(item) // 非空
&& !StringUtils.isContainEmpty(item.getKey()); // 键非空
} -
第 16 至 18 行 && 第 23 至 25 行:防御性编程,这几个参数不需要从 Portal 传递。
-
第 19 至 22 行:设置 ItemDTO 的创建和修改人为当前管理员。
-
第 27 行:调用
ConfigService#createItem(appId, Env, clusterName, namespaceName, ItemDTO)
方法,保存 Item 到 Admin Service 中。
3.2 ItemService
在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.service.ItemService
,提供 Item 的 Service 逻辑。
#createItem(appId, env, clusterName, namespaceName, ItemDTO)
方法,创建并保存 Item 到 Admin Service 。代码如下:
1: |
- 第 7 至11 行:调用
NamespaceAPI#loadNamespace(appId, Env, clusterName, namespaceName)
方法,校验 Namespace 是否存在。若不存在,抛出 BadRequestException 异常。注意,此处是远程调用 Admin Service 的 API 。 - 第 12 行:设置 ItemDTO 的
namespaceId
。 - 第 15 行:调用
NamespaceAPI#createItem(appId, Env, clusterName, namespaceName, ItemDTO)
方法,保存 Item 到 Admin Service 。 - 第 17 行:【TODO 6001】Tracer 日志
3.3 ItemAPI
com.ctrip.framework.apollo.portal.api.ItemAPI
,实现 API 抽象类,封装对 Admin Service 的 Item 模块的 API 调用。代码如下:
4. Admin Service 侧
4.1 ItemController
在 apollo-adminservice
项目中, com.ctrip.framework.apollo.adminservice.controller.ItemController
,提供 Item 的 API 。
#create(appId, clusterName, namespaceName, ItemDTO)
方法,创建 Item ,并记录 Commit 。代码如下:
1: |
- POST
/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items
接口,Request Body 传递 JSON 对象。 - 第 16 行:调用
BeanUtils#transfrom(Class<T> clazz, Object src)
方法,将 ItemDTO 转换成 Item 对象。 - 第 18 行:创建 ConfigChangeContentBuilder 对象。
- 第 19 至 22 行:调用
ItemService#findOne(appId, clusterName, namespaceName, key)
方法,校验对应的 Item 是否已经存在。若是,抛出 BadRequestException 异常。 - 第 25 行:调用
ItemService#save(Item)
方法,保存 Item 对象。 - 第 27 行:调用
ConfigChangeContentBuilder#createItem(Item)
方法,添加到 ConfigChangeContentBuilder 中。 - 第 30 行:调用
BeanUtils#transfrom(Class<T> clazz, Object src)
方法,将 Item 转换成 ItemDTO 对象。 - 第 31 至 38 行:创建 Commit 对象。
- 第 40 行:调用
CommitService#save(Commit)
方法,保存 Commit 对象。
4.2 ItemService
在 apollo-biz
项目中,com.ctrip.framework.apollo.biz.service.ItemService
,提供 Item 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(Item)
方法,保存 Item 对象 。代码如下:
1: |
-
第 9 行:调用
#checkItemKeyLength(key)
方法,校验 Key 长度。- 可配置
"item.value.length.limit"
在 ServerConfig 配置最大长度。 - 默认最大长度为 128 。
- 可配置
-
第 11 行:调用
#checkItemValueLength(namespaceId, value)
方法,校验 Value 长度。- 全局可配置
"item.value.length.limit"
在 ServerConfig 配置最大长度。 - 自定义配置
"namespace.value.length.limit.override"
在 ServerConfig 配置最大长度。 - 默认最大长度为 20000 。
- 全局可配置
-
第 14 至 19 行:设置 Item 的行号,以 Namespace 下的 Item 最大行号 + 1 。
#findLastOne(namespaceId)
方法,获得最大行号的 Item 对象,代码如下:public Item findLastOne(long namespaceId) {
return itemRepository.findFirst1ByNamespaceIdOrderByLineNumDesc(namespaceId);
} -
第 21 行:调用
ItemRepository#save(Item)
方法,保存 Item 。 -
第 23 行:记录 Audit 到数据库中
4.3 ItemRepository
com.ctrip.framework.apollo.biz.repository.ItemRepository
,继承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 Item 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface ItemRepository extends PagingAndSortingRepository<Item, Long> { |
4.4 CommitService
在 apollo-biz
项目中,com.ctrip.framework.apollo.biz.service.CommitService
,提供 Commit 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(Commit)
方法,保存 Item 对象 。代码如下:
|
4.5 CommitRepository
com.ctrip.framework.apollo.biz.repository.CommitRepository
,继承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 Commit 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface CommitRepository extends PagingAndSortingRepository<Commit, Long> { |
666. 彩蛋
Commit 的设计,在我们日常的管理后台,对重要数据的变更,可以作为参考。