微服务配置中心 Apollo解析——Portal 创建 App

private String ownerEmail;

}

  • ORM 选用 Hibernate 框架。

  • @SQLDelete(…) + @Where(…) 注解,配合 BaseEntity.extends 字段,实现 App 的逻辑删除

  • 字段比较简单,胖友看下注释。

2.1 BaseEntity

com.ctrip.framework.apollo.common.entity.BaseEntity ,基础实体抽象类。代码如下:

@MappedSuperclass

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

public abstract class BaseEntity {

/**

* 编号

*/

@Id

@GeneratedValue

@Column(name = “Id”)

private long id;

/**

* 是否删除

*/

@Column(name = “IsDeleted”, columnDefinition = “Bit default ‘0’”)

protected boolean isDeleted = false;

/**

* 数据创建人

*

* 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段

*/

@Column(name = “DataChange_CreatedBy”, nullable = false)

private String dataChangeCreatedBy;

/**

* 数据创建时间

*/

@Column(name = “DataChange_CreatedTime”, nullable = false)

private Date dataChangeCreatedTime;

/**

* 数据最后更新人

*

* 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段

*/

@Column(name = “DataChange_LastModifiedBy”)

private String dataChangeLastModifiedBy;

/**

* 数据最后更新时间

*/

@Column(name = “DataChange_LastTime”)

private Date dataChangeLastModifiedTime;

/**

* 保存前置方法

*/

@PrePersist

protected void prePersist() {

if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();

if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();

}

/**

* 更新前置方法

*/

@PreUpdate

protected void preUpdate() {

this.dataChangeLastModifiedTime = new Date();

}

/**

* 删除前置方法

*/

@PreRemove

protected void preRemove() {

this.dataChangeLastModifiedTime = new Date();

}

// … 省略 setting / getting 方法

}

  • @MappedSuperclass 注解,见 《Hibernate 中 @MappedSuperclass 注解的使用说明》 文章。

  • @Inheritance(…) 注解,见 《Hibernate(11)映射继承关系二之每个类对应一张表(@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)》 文章。

  • id 字段,编号,Long 型,全局自增。

  • isDeleted 字段,是否删除,用于逻辑删除的功能。

  • dataChangeCreatedBy 和 dataChangeCreatedTime 字段,实现数据的创建人和时间的记录,方便追踪。

  • dataChangeLastModifiedBy 和 dataChangeLastModifiedTime 字段,实现数据的更新人和时间的记录,方便追踪。

  • @PrePersist、@PreUpdate、@PreRemove 注解,CRD 操作前,设置对应的时间字段

  • 在 Apollo 中,所有实体都会继承 BaseEntity ,实现公用字段统一定义。这种设计值得借鉴,特别是创建时间更新时间这两个字段,特别适合线上追踪问题和数据同步。

2.2 为什么需要同步

在文初的流程图中,我们看到 App 创建时,在 Portal Service 存储完成后,会异步同步到 Admin Service 中,这是为什么呢?

在 Apollo 的架构中,一个环境( Env ) 对应一套 Admin Service 和 Config Service 。

而 Portal Service 会管理所有环境( Env ) 。因此,每次创建 App 后,需要进行同步。

或者说,App 在 Portal Service 中,表示需要管理的 App 。而在 Admin Service 和 Config Service 中,表示存在的 App 。

3. Portal 侧

================

3.1 AppController

在 apollo-portal 项目中,

com.ctrip.framework.apollo.portal.controller.AppController ,提供 App 的 API 。

创建项目的界面中,点击【提交】按钮,调用创建 App 的 API 。

微服务配置中心 Apollo 源码解析——Portal 创建 App

创建项目

代码如下:

1: @RestController

2: @RequestMapping(“/apps”)

3: public class AppController {

4:

5: @Autowired

6: private UserInfoHolder userInfoHolder;

7: @Autowired

8: private AppService appService;

9: /**

10: * Spring 事件发布者

11: */

12: @Autowired

13: private ApplicationEventPublisher publisher;

14: @Autowired

15: private RolePermissionService rolePermissionService;

16:

17: /**

18: * 创建 App

19: *

20: * @param appModel AppModel 对象

21: * @return App 对象

22: */

23: @RequestMapping(value = “”, method = RequestMethod.POST)

24: public App create(@RequestBody AppModel appModel) {

25: // 将 AppModel 转换成 App 对象

26: App app = transformToApp(appModel);

27: // 保存 App 对象到数据库

28: App createdApp = appService.createAppInLocal(app);

29: // 发布 AppCreationEvent 创建事件

30: publisher.publishEvent(new AppCreationEvent(createdApp));

31: // 授予 App 管理员的角色

32: Set admins = appModel.getAdmins();

33: if (!CollectionUtils.isEmpty(admins)) {

34: rolePermissionService.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),

35: admins, userInfoHolder.getUser().getUserId());

36: }

37: // 返回 App 对象

38: return createdApp;

39: }

40:

41: // … 省略其他接口和属性

42: }

  • POST `apps` 接口,Request Body 传递 JSON 对象。

  • `com.ctrip.framework.apollo.portal.entity.model.AppModel` ,App Model 。在 com.ctrip.framework.apollo.portal.entity.model 包下,负责接收来自 Portal 界面的复杂请求对象。例如,AppModel 一方面带有创建 App 对象需要的属性,另外也带有需要授权管理员的编号集合 admins,即存在跨模块的情况。

  • 第 26 行:调用 `#transformToApp(AppModel)` 方法,将 AppModel 转换成 App 对象。 转换方法很简单,点击方法,直接查看。

  • 第 28 行:调用 AppService#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。在 「3.2 AppService」 中,详细解析。

  • 第 30 行:调用 ApplicationEventPublisher#publishEvent(AppCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppCreationEvent 事件。

  • 第 31 至 36 行:授予 App 管理员的角色。详细解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。

  • 第 38 行:返回创建的 App 对象。

3.2 AppService

在 apollo-portal 项目中,

com.ctrip.framework.apollo.portal.service.AppService ,提供 App 的 Service 逻辑。

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

1: @Autowired

2: private UserInfoHolder userInfoHolder;

3: @Autowired

4: private AppRepository appRepository;

5: @Autowired

6: private AppNamespaceService appNamespaceService;

7: @Autowired

8: private RoleInitializationService roleInitializationService;

9: @Autowired

10: private UserService userService;

11:

12: @Transactional

13: public App createAppInLocal(App app) {

14: String appId = app.getAppId();

15: // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。

16: App managedApp = appRepository.findByAppId(appId);

17: if (managedApp != null) {

18: throw new BadRequestException(String.format(“App already exists. AppId = %s”, appId));

19: }

20: // 获得 UserInfo 对象。若不存在,抛出 BadRequestException 异常

21: UserInfo owner = userService.findByUserId(app.getOwnerName());

22: if (owner == null) {

23: throw new BadRequestException(“Application’s owner not exist.”);

24: }

25: app.setOwnerEmail(owner.getEmail()); // Email

26: // 设置 App 的创建和修改人

27: String operator = userInfoHolder.getUser().getUserId();

28: app.setDataChangeCreatedBy(operator);

29: app.setDataChangeLastModifiedBy(operator);

30: // 保存 App 对象到数据库

31: App createdApp = appRepository.save(app);

32: // 创建 App 的默认命名空间 “application”

33: appNamespaceService.createDefaultAppNamespace(appId);

34: // 初始化 App 角色

35: roleInitializationService.initAppRoles(createdApp);

36: // 【TODO 6001】Tracer 日志

37: Tracer.logEvent(TracerEventType.CREATE_APP, appId);

38: return createdApp;

39: }

  • 第 15 至 19 行:调用 AppRepository#findByAppId(appId) 方法,判断 appId 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。

  • 第 20 至 25 行:调用 UserService#findByUserId(userId) 方法,获得 `com.ctrip.framework.apollo.portal.entity.bo.UserInfo` 对象。com.ctrip.framework.apollo.portal.entity.bo 包下,负责返回 Service 的业务对象。例如,UserInfo 只包含 com.ctrip.framework.apollo.portal.entity.po.UserPO 的部分属性:userId、username、email 。

  • 第 27 至 29 行:调用 UserInfoHolder#getUser()#getUserId() 方法,获得当前登录用户,并设置为 App 的创建和修改人。关于 UserInfoHolder ,后续文章,详细分享。

  • 第 31 行:调用 AppRepository#save(App) 方法,保存 App 对象到数据库中。

  • 第 33 行:调用 AppNameSpaceService#createDefaultAppNamespace(appId) 方法,创建 App 的默认 Namespace (命名空间) “application” 。对于每个 App ,都会有一个默认 Namespace 。具体的代码实现,我们在 《Apollo 源码解析 —— Portal 创建 Namespace》

  • 第 35 行:初始化 App 角色。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。

  • 第 37 行:【TODO 6001】Tracer 日志

3.3 AppRepository

在 apollo-portal 项目中,

com.ctrip.framework.apollo.common.entity.App.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问,即 DAO 。

代码如下:

public interface AppRepository extends PagingAndSortingRepository<App, Long> {

App findByAppId(String appId);

List findByOwnerName(String ownerName, Pageable page);

List findByAppIdIn(Set appIds);

}

基于 Spring Data JPA 框架,使用 Hibernate 实现。详细参见 《Spring Data JPA、Hibernate、JPA 三者之间的关系》 文章。

不熟悉 Spring Data JPA 的胖友,可以看下 《Spring Data JPA 介绍和使用》 文章。

3.4 AppCreationEvent

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

代码如下:

public class AppCreationEvent extends ApplicationEvent {

public AppCreationEvent(Object source) {

super(source);

}

public App getApp() {

Preconditions.checkState(source != null);

return (App) this.source;

}

}

  • 构造方法,将 App 对象作为方法参数传入。

  • #getApp() 方法,获得事件对应的 App 对象。

3.4.1 CreationListener

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

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

1: @Autowired

2: private PortalSettings portalSettings;

3: @Autowired

4: private AdminServiceAPI.AppAPI appAPI;

5:

6: @EventListener

7: public void onAppCreationEvent(AppCreationEvent event) {

8: // 将 App 转成 AppDTO 对象

9: AppDTO appDTO = BeanUtils.transfrom(AppDTO.class, event.getApp());

10: // 获得有效的 Env 数组

11: List envs = portalSettings.getActiveEnvs();

12: // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 App 对象。

13: for (Env env : envs) {

14: try {

15: appAPI.createApp(env, appDTO);

16: } catch (Throwable e) {

17: logger.error(“Create app failed. appId = {}, env = {})”, appDTO.getAppId(), env, e);

18: Tracer.logError(String.format(“Create app failed. appId = %s, env = %s”, appDTO.getAppId(), env), e);

19: }

20: }

21: }

  • @EventListener 注解 + 方法参数,表示 #onAppCreationEvent(…) 方法,监听 AppCreationEvent 事件。不了解的胖友,可以看下 《Spring 4.2框架中注释驱动的事件监听器详解》 文章。

  • 第 9 行:调用 BeanUtils#transfrom(Class clazz, Object src) 方法,将 App 转换成 com.ctrip.framework.apollo.common.dto.AppDTO 对象。com.ctrip.framework.apollo.common.dto 包下,提供 Controller 和 Service 层的数据传输。 笔者思考了下,Apollo 中,Model 和 DTO 对象很类似,差异点在 Model 更侧重 UI 界面提交“复杂”业务请求。另外 Apollo 中,还有 VO 对象,侧重 UI 界面返回复杂业务响应。整理如下图:

微服务配置中心 Apollo 源码解析——Portal 创建 App

  • 各种 Entity 整理

  • 老艿艿认为,PO 对象,可以考虑不暴露给 Controller 层,只在 Service 和 Repository 之间传递和返回。

  • 和彩笔老徐交流了下,实际项目可以简化,使用 VO + DTO + PO 。

  • 第 11 行:调用 PortalSettings#getActiveEnvs() 方法,获得有效的 Env 数组,例如 PROD UAT 等。后续文章,详细分享该方法。

  • 第 12 至 20 行:循环 Env 数组,调用 AppAPI#createApp(Env, AppDTO) 方法,调用对应的 Admin Service 的 API ,创建 App 对象,从而同步 App 到 Config DB

3.5 AdminServiceAPI

com.ctrip.framework.apollo.portal.api.AdminServiceAPI ,Admin Service API 集合,包含 Admin Service 所有模块 API 的调用封装。简化代码如下:

微服务配置中心 Apollo 源码解析——Portal 创建 App

代码

3.5.1 API

com.ctrip.framework.apollo.portal.api.API ,API 抽象类。代码如下:

public abstract class API {

@Autowired

protected RetryableRestTemplate restTemplate;

}

  • 提供统一的 restTemplate 的属性注入。对于 RetryableRestTemplate 的源码实现,我们放到后续文章分享。

3.5.2 AppAPI

com.ctrip.framework.apollo.portal.api.AdminServiceAPI.AppAPI ,实现 API 抽象类,封装对 Admin Service 的 App 模块的 API 调用。代码如下:

@Service

public static class AppAPI extends API {

public AppDTO loadApp(Env env, String appId) {

return restTemplate.get(env, “apps/{appId}”, AppDTO.class, appId);

}

public AppDTO createApp(Env env, AppDTO app) {

return restTemplate.post(env, “apps”, app, AppDTO.class);

}

public void updateApp(Env env, AppDTO app) {

restTemplate.put(env, “apps/{appId}”, app, app.getAppId());

}

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值