Apollo源码阅读记录(一)

最近公司项目用到了Apollo,花了几天功夫把Apollo的官方文档过了一遍,不得不说写的非常详细。基本的使用,已经简单的原理都介绍的明明白白的。
在文档上有这么句话:据说Apollo非常适合作为初学者第一个通读源码学习的分布式中间件产品
那么就开始吧。
这里我主要为了记录一些在阅读Apollo源码时所学习到的一些开发技巧,方便自己回顾。
我是一边参考:Apollo源码解析,一边自己阅读源码,有很多东西也是从那边粘过来的。

APP

首先我们看一下apollo-common模块中的com.ctrip.framework.apollo.common.entity.App

@Entity
@Table(name = "App")
@SQLDelete(sql = "Update App set isDeleted = 1 where id = ?") 
@Where(clause = "isDeleted = 0")
public class App extends BaseEntity {

  @NotBlank(message = "Name cannot be blank")
  @Column(name = "Name", nullable = false)
  private String name;

  ......

 public static class Builder {

    public Builder() {
    }

    private App app = new App();

    public Builder name(String name) {
      app.setName(name);
      return this;
    }
    ......
    
    public App build() {
      return app;
    }

  }

  public static Builder builder() {
    return new Builder();
  }
 }
  1. 使用@SQLDelete来实现逻辑删除,只对Hibernate起作用,它会在我们调用delete删除该对象的时候执行@SQLDelete注解中的sql。
  2. 使用@Where是为了配合逻辑删除做查询条件的,也是在Hibernate中起作用,它会在我们调用查询方法时,在where条件中追加@Where中的clause。
  3. 继承BaseEntity是为了抽取出Entity中的公共属性。减少重复代码。
  4. 使用建造者模式,将对象复杂的构建逻辑与展示代码分离。

顺着这条线,看一下com.ctrip.framework.apollo.common.entity.BaseEntity

@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "Id")
  private long id;

  @Column(name = "IsDeleted", columnDefinition = "Bit default '0'")
  protected boolean isDeleted = false;

  @Column(name = "DataChange_CreatedBy", nullable = false)
  private String dataChangeCreatedBy;

  @Column(name = "DataChange_CreatedTime", nullable = false)
  private Date dataChangeCreatedTime;

  @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();
  }
  1. @Inheritance注解是用在有继承关系的对象,strategy是对于继承关系,使用哪种策略映射数据表。SINGLE_TABLE:所有父子类的字段在一张表中;TABLE_PER_CLASS:每个父子类独立一张表,子类的表会包含父类和自己的全部字段;JOINED:每个父子类独立一张表,表与其对应的类字段完全一样,父子关系用外键来关联。
  2. @MappedSuperclass注解只能在类上,标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。
  3. @PrePersist:该对象持久化之前;@PreUpdate:该对象更新之前;@PreRemove:该对象删除之前,而且这里既然有@Prexxx,那必然会有@Postxxx,用于对象做完某件事之后的操作。

接下来我们追一下创建App的流程,apollo-portal模块下的com.ctrip.framework.apollo.portal.controller.AppController

@RestController
@RequestMapping("/apps")
public class AppController {

  private final AppService appService;
  private final ApplicationEventPublisher publisher;
  private final RolePermissionService rolePermissionService;

  @PostMapping
  public App create(@Valid @RequestBody AppModel appModel) {

    App app = transformToApp(appModel);

    App createdApp = appService.createAppInLocal(app);

    publisher.publishEvent(new AppCreationEvent(createdApp));

    Set admins = appModel.getAdmins();
    if (!CollectionUtils.isEmpty(admins)) {
      rolePermissionService
          .assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),
              admins, userInfoHolder.getUser().getUserId());
    }

    return createdApp;
  }
}
  1. 通过事件驱动异步的将创建App的信息同步到Admin模块,减少了主流程的执行时间,降低代码间的耦合度。

时间的监听者在这com.ctrip.framework.apollo.portal.listener.CreationListener

@EventListener
  public void onAppCreationEvent(AppCreationEvent event) {
    AppDTO appDTO = BeanUtils.transform(AppDTO.class, event.getApp());
    List envs = portalSettings.getActiveEnvs();
    for (Env env : envs) {
      try {
        appAPI.createApp(env, appDTO);
      } catch (Throwable e) {
        logger.error("Create app failed. appId = {}, env = {})", appDTO.getAppId(), env, e);
        Tracer.logError(String.format("Create app failed. appId = %s, env = %s", appDTO.getAppId(), env), e);
      }
    }
  }
  1. 这个方法就会监听AppCreationEvent事件,事件触发后会获取到App对象发送给Admin模块

项目中大量的使用了各种各样的实体对象,这里总结一点我自己的理解:

  1. Model对象只用于接受Controller层的入参
  2. VO对象只用于Controller层的出参
  3. DTO对象用于中间层的数据传输,比如service层之间、服务调用之间等
  4. PO只用于与数据库的交互

Cluster

同样也是追创建cluster的流程,先来看一下apollo-biz模块下的com.ctrip.framework.apollo.biz.entity.Cluster

@Entity
@Table(name = "Cluster")
@SQLDelete(sql = "Update Cluster set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Cluster extends BaseEntity implements Comparable {

  @Column(name = "Name", nullable = false)
  private String name;

  @Column(name = "AppId", nullable = false)
  private String appId;

  @Column(name = "ParentClusterId", nullable = false)
  private long parentClusterId;
  ......
} 
  1. parentClusterId这个字段用于灰度发布时记录回滚版本号

再看一下apollo-portal模块下的com.ctrip.framework.apollo.portal.controller.ClusterController

@RestController
public class ClusterController {

  private final ClusterService clusterService;
  private final UserInfoHolder userInfoHolder;

  @PreAuthorize(value = "@permissionValidator.hasCreateClusterPermission(#appId)")
  @PostMapping(value = "apps/{appId}/envs/{env}/clusters")
  public ClusterDTO createCluster(@PathVariable String appId, @PathVariable String env,
                                  @Valid @RequestBody ClusterDTO cluster) {
    String operator = userInfoHolder.getUser().getUserId();
    cluster.setDataChangeLastModifiedBy(operator);
    cluster.setDataChangeCreatedBy(operator);

    return clusterService.createCluster(Env.valueOf(env), cluster);
  }
  ......
}  
  1. 这里使用@PreAuthorize注解对方法做前置的权限校验

然后这里是apollo-adminservice模块中保存cluster的方法:

 @Transactional
  public Cluster saveWithoutInstanceOfAppNamespaces(Cluster entity) {
    if (!isClusterNameUnique(entity.getAppId(), entity.getName())) {
      throw new BadRequestException("cluster not unique");
    }
    entity.setId(0);//protection
    Cluster cluster = clusterRepository.save(entity);

    auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT,
                       cluster.getDataChangeCreatedBy());

    return cluster;
  }
  1. entity.setId(0);这里给对象id设置为0是防止被注入id导致更新对象

你可能感兴趣的:(随笔)