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<String> 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<Env> 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<Cluster> {

  @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導致更新對象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章